From 19c49ddf6201fc5963ab38266a943e05f4973be9 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 10:28:48 +0200 Subject: [PATCH 001/190] fix(config): unifying config manipulation All config manipulation now go through amend_golem_config. This allows to be sure that the !expr are not lost Close #709 --- R/config.R | 300 +++++++++-------- R/create_golem.R | 422 ++++++++++++------------ R/options.R | 422 +++++++++++------------- inst/shinyexample/inst/golem-config.yml | 2 +- tests/testthat/test-config.R | 108 ++++-- 5 files changed, 643 insertions(+), 611 deletions(-) diff --git a/R/config.R b/R/config.R index 64438199..cfd76718 100644 --- a/R/config.R +++ b/R/config.R @@ -1,125 +1,145 @@ +# This file contains everything related to the +# manipulation of the golem-config file + +# We first need something to guess where the file +# is. 99.99% of the time it will be +# ./inst/golem-config.yml but if for some reason +# you're somewhere else, functions still need to +# work + #' @importFrom attempt attempt is_try_error #' @importFrom fs path path_abs guess_where_config <- function( - path = ".", - file = "inst/golem-config.yml" + path = ".", + file = "inst/golem-config.yml" ) { - # Trying the path - ret_path <- path( - path, - file - ) - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } - # Trying maybe in the wd - ret_path <- "golem-config.yml" - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } - # Trying with pkgpath - ret_path <- attempt({ - path( - golem::pkg_path(), - "inst/golem-config.yml" - ) - }) - if ( - !is_try_error(ret_path) & - file_exists(ret_path) - ) { - return( - path_abs(ret_path) - ) - } - return(NULL) + # We'll try to guess where the path + # to the golem-config file is + + # This one should be correct in 99% of the case + # => current directory /inst/golem-config.yml + ret_path <- path( + path, + file + ) + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } + + # Maybe for some reason we are in inst/ + ret_path <- "golem-config.yml" + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } + + # Trying with pkg_path + ret_path <- attempt({ + path( + golem::pkg_path(), + "inst/golem-config.yml" + ) + }) + + if ( + !is_try_error(ret_path) & + file_exists(ret_path) + ) { + return( + path_abs(ret_path) + ) + } + return(NULL) } #' @importFrom fs file_copy path get_current_config <- function( - path = ".", - set_options = TRUE + path = ".", + set_options = TRUE ) { - # We check wether we can guess where the config file is - path_conf <- guess_where_config(path) - # We default to inst/ if this doesn't exist - if (is.null(path_conf)) { - path_conf <- path( - path, - "inst/golem-config.yml" - ) - } - - if (!file_exists(path_conf)) { - if (interactive()) { - ask <- yesno( - sprintf( - "The %s file doesn't exist, create?", - basename(path_conf) - ) - ) - # Return early if the user doesn't allow - if (!ask) { - return(NULL) - } - - file_copy( - path = golem_sys("shinyexample/inst/golem-config.yml"), - new_path = path( - path, - "inst/golem-config.yml" - ) - ) - file_copy( - path = golem_sys("shinyexample/R/app_config.R"), - new_path = file.path( - path, - "R/app_config.R" - ) - ) - replace_word( - path( - path, - "R/app_config.R" - ), - "shinyexample", - golem::pkg_name() - ) - if (set_options) { - set_golem_options() - } - } else { - stop( - sprintf( - "The %s file doesn't exist.", - basename(path_conf) - ) - ) - } - } - - return( - invisible(path_conf) - ) + # We check wether we can guess where the config file is + path_conf <- guess_where_config(path) + + # We default to inst/ if this doesn't exist + if (is.null(path_conf)) { + path_conf <- path( + path, + "inst/golem-config.yml" + ) + } + + if (!file_exists(path_conf)) { + if (interactive()) { + ask <- yesno( + sprintf( + "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", + basename(path_conf) + ) + ) + # Return early if the user doesn't allow + if (!ask) { + return(NULL) + } + + file_copy( + path = golem_sys("shinyexample/inst/golem-config.yml"), + new_path = path( + path, + "inst/golem-config.yml" + ) + ) + file_copy( + path = golem_sys("shinyexample/R/app_config.R"), + new_path = file.path( + path, + "R/app_config.R" + ) + ) + replace_word( + path( + path, + "R/app_config.R" + ), + "shinyexample", + golem::pkg_name() + ) + if (set_options) { + set_golem_options() + } + } else { + stop( + sprintf( + "The %s file doesn't exist.", + basename(path_conf) + ) + ) + } + } + + return( + invisible(path_conf) + ) } +# This function changes the name of the +# package in app_config when you need to +# set the {golem} name change_app_config_name <- function( - name, - path = get_golem_wd() + name, + path = get_golem_wd() ) { - pth <- fs::path(path, "R", "app_config.R") - app_config <- readLines(pth) - - where_system.file <- grep("system.file", app_config) - - app_config[ - where_system.file - ] <- sprintf( - ' system.file(..., package = "%s")', - name - ) - write(app_config, pth) + pth <- fs::path(path, "R", "app_config.R") + app_config <- readLines(pth) + + where_system.file <- grep("system.file", app_config) + + app_config[ + where_system.file + ] <- sprintf( + ' system.file(..., package = "%s")', + name + ) + write(app_config, pth) } @@ -127,20 +147,20 @@ change_app_config_name <- function( # used internally in `amend_golem_config` #' @importFrom utils modifyList find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) - conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) - - expr_list <- lapply(names(conf), function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - }) - names(expr_list) <- names(conf) - expr_list <- Filter(function(x) length(x) > 0, expr_list) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply(expr_list, add_expr_tag) - modifyList(conf, tagged_exprs) + conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) + conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) + + expr_list <- lapply(names(conf), function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + }) + names(expr_list) <- names(conf) + expr_list <- Filter(function(x) length(x) > 0, expr_list) + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply(expr_list, add_expr_tag) + modifyList(conf, tagged_exprs) } #' Amend golem config file @@ -156,23 +176,23 @@ find_and_tag_exprs <- function(conf_path) { #' #' @return Used for side effects. amend_golem_config <- function( - key, - value, - config = "default", - pkg = get_golem_wd(), - talkative = TRUE + key, + value, + config = "default", + pkg = get_golem_wd(), + talkative = TRUE ) { - conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) - invisible(TRUE) + conf_path <- get_current_config(pkg) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value + write_yaml( + conf, + conf_path + ) + invisible(TRUE) } diff --git a/R/create_golem.R b/R/create_golem.R index 35e9e8f2..874f40cb 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -32,220 +32,220 @@ #' #' @return The path, invisibly. create_golem <- function( - path, - check_name = TRUE, - open = TRUE, - overwrite = FALSE, - package_name = basename(path), - without_comments = FALSE, - project_hook = golem::project_hook, - with_git = FALSE, - ... + path, + check_name = TRUE, + open = TRUE, + overwrite = FALSE, + package_name = basename(path), + without_comments = FALSE, + project_hook = golem::project_hook, + with_git = FALSE, + ... ) { - path <- normalizePath(path, mustWork = FALSE) - - if (check_name) { - cat_rule("Checking package name") - getFromNamespace("check_package_name", "usethis")(package_name) - cat_green_tick("Valid package name") - } - - - if (dir.exists(path)) { - if (!isTRUE(overwrite)) { - stop( - paste( - "Project directory already exists. \n", - "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", - "Be careful this will restore a brand new golem. \n", - "You might be at risk of losing your work !" - ), - call. = FALSE - ) - } else { - cat_red_bullet("Overwriting existing project.") - } - } else { - cat_rule("Creating dir") - usethis::create_project( - path = path, - open = FALSE, - ) - cat_green_tick("Created package directory") - } - - - cat_rule("Copying package skeleton") - from <- golem_sys("shinyexample") - - # Copy over whole directory - dir_copy(path = from, new_path = path, overwrite = TRUE) - - # Listing copied files ***from source directory*** - copied_files <- list.files( - path = from, - full.names = FALSE, - all.files = TRUE, - recursive = TRUE - ) - - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path, f) - - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path, gsub("REMOVEME", "", f)) - } - - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } - - cat_green_tick("Copied app skeleton") - - - cat_rule("Setting the default config") - - yml_path <- file.path(path, "inst/golem-config.yml") - conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - yaml_golem_wd <- "here::here()" - attr(yaml_golem_wd, "tag") <- "!expr" - conf$dev$golem_wd <- yaml_golem_wd - conf$default$golem_name <- package_name - conf$default$golem_version <- "0.0.0.9000" - write_yaml(conf, yml_path) - - cat_green_tick("Configured app") - - - cat_rule("Running project hook function") - - old <- setwd(path) - # TODO fix - # for some weird reason test() fails here when using golem::create_golem - # and I don't have time to search why rn - if (substitute(project_hook) == "golem::project_hook") { - project_hook <- getFromNamespace("project_hook", "golem") - } - project_hook(path = path, package_name = package_name, ...) - setwd(old) - - cat_green_tick("All set") - - - if (isTRUE(without_comments)) { - files <- list.files( - path = c( - file.path(path, "dev"), - file.path(path, "R") - ), - full.names = TRUE - ) - for (file in files) { - remove_comments(file) - } - } - - - if (isTRUE(with_git)) { - cat_rule("Initializing git repository") - git_output <- system( - command = paste("git init", path), - ignore.stdout = TRUE, - ignore.stderr = TRUE - ) - if (git_output) { - cat_red_bullet("Error initializing git epository") - } else { - cat_green_tick("Initialized git repository") - } - } - - - old <- setwd(path) - use_latest_dependencies() - - # No .Rprofile for now - # cat_rule("Appending .Rprofile") - # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) - # write("home_profile <- file.path(", ".Rprofile", append = TRUE) - # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) - # write(" \".Rprofile\"", ".Rprofile", append = TRUE) - # write(")", ".Rprofile", append = TRUE) - # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) - # write(" source(home_profile)", ".Rprofile", append = TRUE) - # write("}", ".Rprofile", append = TRUE) - # write("rm(home_profile)", ".Rprofile", append = TRUE) - # - # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) - # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) - # cat_green_tick("Appended") - - setwd(old) - - - cat_rule("Done") - cat_line( - paste0( - "A new golem named ", - package_name, - " was created at ", - normalizePath(path), - " .\n", - "To continue working on your app, start editing the 01_start.R file." - ) - ) - - - if (isTRUE(open)) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { - rstudioapi::openProject(path = path) - } else { - setwd(path) - } - } - - - return( - invisible( - normalizePath(path) - ) - ) + path <- normalizePath(path, mustWork = FALSE) + + if (check_name) { + cat_rule("Checking package name") + getFromNamespace("check_package_name", "usethis")(package_name) + cat_green_tick("Valid package name") + } + + + if (dir.exists(path)) { + if (!isTRUE(overwrite)) { + stop( + paste( + "Project directory already exists. \n", + "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", + "Be careful this will restore a brand new golem. \n", + "You might be at risk of losing your work !" + ), + call. = FALSE + ) + } else { + cat_red_bullet("Overwriting existing project.") + } + } else { + cat_rule("Creating dir") + usethis::create_project( + path = path, + open = FALSE, + ) + cat_green_tick("Created package directory") + } + + + cat_rule("Copying package skeleton") + from <- golem_sys("shinyexample") + + # Copy over whole directory + dir_copy(path = from, new_path = path, overwrite = TRUE) + + # Listing copied files ***from source directory*** + copied_files <- list.files( + path = from, + full.names = FALSE, + all.files = TRUE, + recursive = TRUE + ) + + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } + + cat_green_tick("Copied app skeleton") + + + cat_rule("Setting the default config") + + yml_path <- file.path(path, "inst/golem-config.yml") + conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) + yaml_golem_wd <- "golem::pkg_path()" + attr(yaml_golem_wd, "tag") <- "!expr" + conf$dev$golem_wd <- yaml_golem_wd + conf$default$golem_name <- package_name + conf$default$golem_version <- "0.0.0.9000" + write_yaml(conf, yml_path) + + cat_green_tick("Configured app") + + + cat_rule("Running project hook function") + + old <- setwd(path) + # TODO fix + # for some weird reason test() fails here when using golem::create_golem + # and I don't have time to search why rn + if (substitute(project_hook) == "golem::project_hook") { + project_hook <- getFromNamespace("project_hook", "golem") + } + project_hook(path = path, package_name = package_name, ...) + setwd(old) + + cat_green_tick("All set") + + + if (isTRUE(without_comments)) { + files <- list.files( + path = c( + file.path(path, "dev"), + file.path(path, "R") + ), + full.names = TRUE + ) + for (file in files) { + remove_comments(file) + } + } + + + if (isTRUE(with_git)) { + cat_rule("Initializing git repository") + git_output <- system( + command = paste("git init", path), + ignore.stdout = TRUE, + ignore.stderr = TRUE + ) + if (git_output) { + cat_red_bullet("Error initializing git epository") + } else { + cat_green_tick("Initialized git repository") + } + } + + + old <- setwd(path) + use_latest_dependencies() + + # No .Rprofile for now + # cat_rule("Appending .Rprofile") + # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) + # write("home_profile <- file.path(", ".Rprofile", append = TRUE) + # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) + # write(" \".Rprofile\"", ".Rprofile", append = TRUE) + # write(")", ".Rprofile", append = TRUE) + # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) + # write(" source(home_profile)", ".Rprofile", append = TRUE) + # write("}", ".Rprofile", append = TRUE) + # write("rm(home_profile)", ".Rprofile", append = TRUE) + # + # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) + # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) + # cat_green_tick("Appended") + + setwd(old) + + + cat_rule("Done") + cat_line( + paste0( + "A new golem named ", + package_name, + " was created at ", + normalizePath(path), + " .\n", + "To continue working on your app, start editing the 01_start.R file." + ) + ) + + + if (isTRUE(open)) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { + rstudioapi::openProject(path = path) + } else { + setwd(path) + } + } + + + return( + invisible( + normalizePath(path) + ) + ) } # to be used in RStudio "new project" GUI create_golem_gui <- function(path, ...) { - dots <- list(...) - attempt::stop_if_not( - dots$project_hook, - ~ grepl("::", .x), - "{golem} project templates must be explicitely namespaced (pkg::fun)" - ) - splt <- strsplit(dots$project_hook, "::") - project_hook <- getFromNamespace( - splt[[1]][2], - splt[[1]][1] - ) - create_golem( - path = path, - open = FALSE, - without_comments = dots$without_comments, - project_hook = project_hook, - check_name = dots$check_name, - with_git = dots$with_git - ) + dots <- list(...) + attempt::stop_if_not( + dots$project_hook, + ~ grepl("::", .x), + "{golem} project templates must be explicitely namespaced (pkg::fun)" + ) + splt <- strsplit(dots$project_hook, "::") + project_hook <- getFromNamespace( + splt[[1]][2], + splt[[1]][1] + ) + create_golem( + path = path, + open = FALSE, + without_comments = dots$without_comments, + project_hook = project_hook, + check_name = dots$check_name, + with_git = dots$with_git + ) } diff --git a/R/options.R b/R/options.R index 134eb2a9..c639967f 100644 --- a/R/options.R +++ b/R/options.R @@ -31,303 +31,273 @@ #' #' @export #' @importFrom attempt stop_if_not -#' @importFrom yaml read_yaml write_yaml #' @importFrom usethis proj_set #' #' @return Used for side-effects for the setters, and values from the #' config in the getters. set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE ) { - change_app_config_name( - name = golem_name, - path = golem_wd - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } + # TODO here we'll run the + # golem_install_dev_pkg() function - conf_path <- get_current_config( - golem_wd, - set_options = FALSE - ) + if (talkative) { + cli::cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path + if (golem_wd == golem::pkg_path()) { + path <- "golem::pkg_path()" + attr(path, "tag") <- "!expr" + } else { + path <- golem_wd + } - cat_if_talk( - "Setting {golem} options in `golem-config.yml`", - fun = cli::cat_rule - ) + set_golem_wd( + path = path, + talkative = talkative + ) - conf <- read_yaml(conf_path, eval.expr = TRUE) + # Setting name of the golem + set_golem_name( + name = golem_name, + talkative = talkative + ) - # Setting wd - if (golem_wd == here::here()) { - path <- "here::here()" - attr(path, "tag") <- "!expr" - } else { - path <- golem_wd - } + # Setting golem_version + set_golem_version( + version = golem_version, + talkative = talkative + ) - cat_if_talk( - sprintf( - "Setting `golem_wd` to %s", - path - ) - ) - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) + # Setting app_prod + set_golem_things( + "app_prod", + app_prod, + path = golem_wd, + talkative = talkative + ) - conf$dev$golem_wd <- path + # This part is for {usethis} and {here} + if (talkative) { + cli::cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } - # Setting name of the golem - cat_if_talk( - sprintf( - "Setting `golem_name` to %s", - golem_name - ) - ) - conf$default$golem_name <- golem_name - - # Setting golem_version - cat_if_talk( - sprintf( - "Setting `golem_version` to %s", - golem_version - ) - ) - conf$default$golem_version <- as.character(golem_version) - - # Setting app_prod - cat_if_talk( - sprintf( - "Setting `app_prod` to %s", - app_prod - ) - ) - conf$default$app_prod <- app_prod - - # Export - write_yaml( - conf, - conf_path - ) - - cat_if_talk( - "Setting {usethis} project as `golem_wd`", - fun = cli::cat_rule - ) - proj_set(golem_wd) + proj_set(golem_wd) } -#' @importFrom yaml read_yaml write_yaml set_golem_things <- function( - key, - value, - path, - talkative, - config = "default" + key, + value, + path, + talkative, + config = "default" ) { - conf_path <- get_current_config(path, set_options = FALSE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } + conf_path <- get_current_config(path, set_options = FALSE) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + cat_if_talk <- function(..., fun = cat_green_tick) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } - conf <- read_yaml(conf_path, eval.expr = TRUE) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) + amend_golem_config( + key = key, + value = value, + config = config + ) - invisible(path) + invisible(path) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_wd <- function( - path = golem::pkg_path(), - talkative = TRUE + path = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) - # Setting wd + path <- path_abs(path) - if (path == here::here()) { - path <- "here::here()" - attr(path, "tag") <- "!expr" - } + if (path == golem::pkg_path()) { + path <- "golem::pkg_path()" + attr(path, "tag") <- "!expr" + } - set_golem_things( - "golem_wd", - path, - path, - talkative = talkative, - config = "dev" - ) + set_golem_things( + "golem_wd", + path, + path, + talkative = talkative, + config = "dev" + ) - invisible(path) + invisible(path) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_name <- function( - name = golem::pkg_name(), - path = golem::pkg_path(), - talkative = TRUE + name = golem::pkg_name(), + path = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) - # Changing in YAML - set_golem_things( - "golem_name", - name, - path, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = path - ) + path <- path_abs(path) + # Changing in YAML + set_golem_things( + "golem_name", + name, + path, + talkative = talkative + ) + # Changing in app-config.R + change_app_config_name( + name = name, + path = path + ) - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) + # Changing in DESCRIPTION + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(name) + invisible(name) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_version <- function( - version = golem::pkg_version(), - path = golem::pkg_path(), - talkative = TRUE + version = golem::pkg_version(), + path = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) - set_golem_things( - "golem_version", - as.character(version), - path, - talkative = talkative - ) - desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) + path <- path_abs(path) + set_golem_things( + "golem_version", + as.character(version), + path, + talkative = talkative + ) + desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(version) + invisible(version) } #' @importFrom config get get_golem_things <- function( - value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path + value, + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path ) { - conf_path <- get_current_config(path, set_options = TRUE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) + conf_path <- get_current_config(path, set_options = TRUE) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) } #' @export #' @rdname golem_opts get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() + use_parent = TRUE, + path = golem::pkg_path() ) { - get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) + get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) } #' @export #' @rdname golem_opts get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() ) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - nm + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + nm } #' @export #' @rdname golem_opts get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() ) { - get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) + get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) } diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml index 6ce2c787..879263c9 100644 --- a/inst/shinyexample/inst/golem-config.yml +++ b/inst/shinyexample/inst/golem-config.yml @@ -7,5 +7,5 @@ production: app_prod: yes dev: - golem_wd: !exp here::here() + golem_wd: !exp golem::golem_path() diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index c5d52490..69400c0f 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -1,36 +1,78 @@ test_that("config works", { - with_dir(pkg, { - expect_equal(get_golem_name(), fakename) - expect_equal(get_golem_version(), "0.0.0.9000") - expect_equal(normalizePath(get_golem_wd(), mustWork = FALSE), normalizePath(pkg, mustWork = FALSE)) - amend_golem_config( - key = "where", - value = "indev" - ) - amend_golem_config( - key = "where", - value = "inprod", - config = "production" - ) + with_dir(pkg, { + # We'll try to be sure that + # golem_wd: !expr golem::pkg_path() + # is kept along the way + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + get_golem_name(), + fakename + ) + expect_equal( + get_golem_version(), + "0.0.0.9000" + ) + expect_equal( + normalizePath(get_golem_wd(), mustWork = FALSE), + normalizePath(pkg, mustWork = FALSE) + ) + amend_golem_config( + key = "where", + value = "indev" + ) + amend_golem_config( + key = "where", + value = "inprod", + config = "production" + ) + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + config::get("where", file = "inst/golem-config.yml"), + "indev" + ) + expect_equal( + config::get("where", config = "production", file = "inst/golem-config.yml"), + "inprod" + ) + where_conf <- withr::with_envvar( + c("R_CONFIG_ACTIVE" = "production"), + { + config::get("where", file = "inst/golem-config.yml") + } + ) + expect_equal( + where_conf, + "inprod" + ) + set_golem_name("plop") + expect_equal( + get_golem_name(), + "plop" + ) + set_golem_name(fakename) + set_golem_version("0.0.0.9001") + expect_equal( + get_golem_version(), + "0.0.0.9001" + ) + set_golem_version("0.0.0.9000") - expect_equal(config::get("where", file = "inst/golem-config.yml"), "indev") - expect_equal(config::get("where", config = "production", file = "inst/golem-config.yml"), "inprod") - where_conf <- withr::with_envvar( - c("R_CONFIG_ACTIVE" = "production"), - { - config::get("where", file = "inst/golem-config.yml") - } - ) - expect_equal(where_conf, "inprod") - set_golem_name("plop") - expect_equal(get_golem_name(), "plop") - set_golem_name(fakename) - set_golem_version("0.0.0.9001") - expect_equal(get_golem_version(), "0.0.0.9001") - set_golem_version("0.0.0.9000") - - set_golem_wd(normalizePath("inst")) - expect_equal(normalizePath(get_golem_wd()), normalizePath("inst")) - set_golem_wd(pkg) - }) + set_golem_wd(normalizePath("inst")) + expect_equal( + normalizePath(get_golem_wd()), + normalizePath("inst") + ) + set_golem_wd(pkg) + # Be sure that after setting the stuff the wd is still here::here() + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + }) }) From d2554bc3f4add0003abde03fc99e1de9ef9a4def Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 12:42:08 +0200 Subject: [PATCH 002/190] fix(CI): correct path check --- inst/mantests/build.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 8257554c..f297869a 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -248,7 +248,7 @@ withr::with_tempdir({ golem::set_golem_options() expect_equal( golem::get_golem_wd(), - here::here() + golem::golem_path() ) expect_equal( golem::get_golem_name(), From 7ab7efabb0af35025351ae0ccbc9183620ee87b9 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 12:52:40 +0200 Subject: [PATCH 003/190] fix(typo): pkg_path instead of golem_path --- inst/mantests/build.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index f297869a..e78800cf 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -248,7 +248,7 @@ withr::with_tempdir({ golem::set_golem_options() expect_equal( golem::get_golem_wd(), - golem::golem_path() + golem::pkg_path() ) expect_equal( golem::get_golem_name(), From b79c7b381268436048884483c939d7dc85535e75 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 19:05:53 +0200 Subject: [PATCH 004/190] feature(creation): create golem only set the app name There is no need to set things that are already set in the yaml --- R/config.R | 21 +++++++++ R/create_golem.R | 112 +++++++++++++++++++++++++++-------------------- R/options.R | 4 +- 3 files changed, 88 insertions(+), 49 deletions(-) diff --git a/R/config.R b/R/config.R index cfd76718..bedce9dd 100644 --- a/R/config.R +++ b/R/config.R @@ -188,6 +188,27 @@ amend_golem_config <- function( is.null, "Unable to retrieve golem config file." ) + cat_if_talk <- function(..., fun = cat_green_tick) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } + conf <- find_and_tag_exprs(conf_path) conf[[config]][[key]] <- value write_yaml( diff --git a/R/create_golem.R b/R/create_golem.R index 874f40cb..7af15cb9 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -1,3 +1,36 @@ +replace_package_name <- function( + copied_files, + package_name, + path_to_golem +) { + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path_to_golem, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path_to_golem, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } +} + + #' Create a package for a Shiny App using `{golem}` #' #' @param path Name of the folder to create the package in. @@ -42,7 +75,7 @@ create_golem <- function( with_git = FALSE, ... ) { - path <- normalizePath(path, mustWork = FALSE) + path_to_golem <- normalizePath(path, mustWork = FALSE) if (check_name) { cat_rule("Checking package name") @@ -51,7 +84,7 @@ create_golem <- function( } - if (dir.exists(path)) { + if (dir.exists(path_to_golem)) { if (!isTRUE(overwrite)) { stop( paste( @@ -68,7 +101,7 @@ create_golem <- function( } else { cat_rule("Creating dir") usethis::create_project( - path = path, + path = path_to_golem, open = FALSE, ) cat_green_tick("Created package directory") @@ -79,7 +112,11 @@ create_golem <- function( from <- golem_sys("shinyexample") # Copy over whole directory - dir_copy(path = from, new_path = path, overwrite = TRUE) + dir_copy( + path = from, + new_path = path_to_golem, + overwrite = TRUE + ) # Listing copied files ***from source directory*** copied_files <- list.files( @@ -89,59 +126,41 @@ create_golem <- function( recursive = TRUE ) - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path, f) + replace_package_name( + copied_files, + package_name, + path_to_golem + ) - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path, gsub("REMOVEME", "", f)) - } - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } cat_green_tick("Copied app skeleton") + old <- setwd(path_to_golem) - cat_rule("Setting the default config") + cat_rule("Changing the app name") - yml_path <- file.path(path, "inst/golem-config.yml") - conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - yaml_golem_wd <- "golem::pkg_path()" - attr(yaml_golem_wd, "tag") <- "!expr" - conf$dev$golem_wd <- yaml_golem_wd - conf$default$golem_name <- package_name - conf$default$golem_version <- "0.0.0.9000" - write_yaml(conf, yml_path) + set_golem_name( + package_name, + path_to_golem + ) cat_green_tick("Configured app") - cat_rule("Running project hook function") - old <- setwd(path) # TODO fix # for some weird reason test() fails here when using golem::create_golem # and I don't have time to search why rn if (substitute(project_hook) == "golem::project_hook") { project_hook <- getFromNamespace("project_hook", "golem") } - project_hook(path = path, package_name = package_name, ...) + project_hook( + path = path_to_golem, + package_name = package_name, + ... + ) + setwd(old) cat_green_tick("All set") @@ -150,8 +169,8 @@ create_golem <- function( if (isTRUE(without_comments)) { files <- list.files( path = c( - file.path(path, "dev"), - file.path(path, "R") + file.path(path_to_golem, "dev"), + file.path(path_to_golem, "R") ), full.names = TRUE ) @@ -164,7 +183,7 @@ create_golem <- function( if (isTRUE(with_git)) { cat_rule("Initializing git repository") git_output <- system( - command = paste("git init", path), + command = paste("git init", path_to_golem), ignore.stdout = TRUE, ignore.stderr = TRUE ) @@ -176,7 +195,7 @@ create_golem <- function( } - old <- setwd(path) + old <- setwd(path_to_golem) use_latest_dependencies() # No .Rprofile for now @@ -197,14 +216,14 @@ create_golem <- function( setwd(old) - cat_rule("Done") + cat_line( paste0( "A new golem named ", package_name, " was created at ", - normalizePath(path), + path_to_golem, " .\n", "To continue working on your app, start editing the 01_start.R file." ) @@ -219,10 +238,9 @@ create_golem <- function( } } - return( invisible( - normalizePath(path) + path_to_golem ) ) } diff --git a/R/options.R b/R/options.R index c639967f..9685ae5e 100644 --- a/R/options.R +++ b/R/options.R @@ -149,11 +149,11 @@ set_golem_wd <- function( path = golem::pkg_path(), talkative = TRUE ) { - path <- path_abs(path) - if (path == golem::pkg_path()) { path <- "golem::pkg_path()" attr(path, "tag") <- "!expr" + } else { + path <- path_abs(path) } set_golem_things( From f79301717e2f1e3b999053c2fd144496b8feafbf Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 19:11:47 +0200 Subject: [PATCH 005/190] feat(create_golem): No name change in the YAML The name is already set by the function that gsub in the files so there is no need to do it one extra time --- R/create_golem.R | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/R/create_golem.R b/R/create_golem.R index 7af15cb9..b531cc77 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -132,21 +132,10 @@ create_golem <- function( path_to_golem ) - - cat_green_tick("Copied app skeleton") old <- setwd(path_to_golem) - cat_rule("Changing the app name") - - set_golem_name( - package_name, - path_to_golem - ) - - cat_green_tick("Configured app") - cat_rule("Running project hook function") # TODO fix From 818987ad14fda87e21858c420b2af61d2f307a25 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 19:23:16 +0200 Subject: [PATCH 006/190] fix(option-setting): don't change to expr before passing to set_golem_wd() The attr is not set before being passed to set_golem_wd() in set_golem_options() --- R/options.R | 55 ++++++++++++++--------------------------------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/R/options.R b/R/options.R index 9685ae5e..97d9895f 100644 --- a/R/options.R +++ b/R/options.R @@ -40,7 +40,8 @@ set_golem_options <- function( golem_version = golem::pkg_version(), golem_wd = golem::pkg_path(), app_prod = FALSE, - talkative = TRUE + talkative = TRUE, + config_file = path(golem_wd, "inst/golem-config.yml") ) { # TODO here we'll run the @@ -57,12 +58,6 @@ set_golem_options <- function( # to keep the wd as an expr if it is the # same as golem::pkg_path(), otherwise # we use the explicit path - if (golem_wd == golem::pkg_path()) { - path <- "golem::pkg_path()" - attr(path, "tag") <- "!expr" - } else { - path <- golem_wd - } set_golem_wd( path = path, @@ -103,40 +98,15 @@ set_golem_things <- function( key, value, path, - talkative, + talkative = TRUE, config = "default" ) { - conf_path <- get_current_config(path, set_options = FALSE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } - - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) - - if (key == "golem_wd") { - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) - } - amend_golem_config( key = key, value = value, - config = config + config = config, + pkg = path, + talkative = talkative ) invisible(path) @@ -149,16 +119,19 @@ set_golem_wd <- function( path = golem::pkg_path(), talkative = TRUE ) { - if (path == golem::pkg_path()) { - path <- "golem::pkg_path()" - attr(path, "tag") <- "!expr" + if ( + path == "golem::pkg_path()" | + path == golem::pkg_path() + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" } else { - path <- path_abs(path) + golem_yaml_path <- path_abs(path) } set_golem_things( "golem_wd", - path, + golem_yaml_path, path, talkative = talkative, config = "dev" From 3f9fcb39ba2e022e072eaaff1350498df2df8227 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:35:23 +0200 Subject: [PATCH 007/190] chore(file): file splitting & renaming The yaml manimpulation functions are now separated in various files --- R/golem-yaml-get.R | 70 +++++++++++ R/golem-yaml-set.R | 130 ++++++++++++++++++++ R/golem-yaml-utils.R | 110 +++++++++++++++++ R/options.R | 276 ------------------------------------------ R/set_golem_options.R | 97 +++++++++++++++ 5 files changed, 407 insertions(+), 276 deletions(-) create mode 100644 R/golem-yaml-get.R create mode 100644 R/golem-yaml-set.R create mode 100644 R/golem-yaml-utils.R delete mode 100644 R/options.R create mode 100644 R/set_golem_options.R diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R new file mode 100644 index 00000000..3c93ae29 --- /dev/null +++ b/R/golem-yaml-get.R @@ -0,0 +1,70 @@ + +#' @importFrom config get +get_golem_things <- function( + value, + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path +) { + conf_path <- get_current_config(path, set_options = TRUE) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) +} + + +#' @export +#' @rdname golem_opts +get_golem_wd <- function( + use_parent = TRUE, + path = golem::pkg_path() +) { + get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) +} + +#' @export +#' @rdname golem_opts +get_golem_name <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() +) { + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + nm +} + +#' @export +#' @rdname golem_opts +get_golem_version <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() +) { + get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) +} diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R new file mode 100644 index 00000000..441f74ab --- /dev/null +++ b/R/golem-yaml-set.R @@ -0,0 +1,130 @@ + +#' Set things in golem config +#' +#' @param key,value entries in the yaml +#' @param path path to the config file +#' @param talkative Should things be printed? +#' @param config config context to write to +#' +#' @noRd +#' +# set_golem_things <- function( +# key, +# value, +# config_file, +# talkative = TRUE, +# config = "default" +# ) { +# amend_golem_config( +# key = key, +# value = value, +# config = config, +# pkg = config_file, +# talkative = talkative +# ) + +# invisible(path) +# } + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_wd <- function( + golem_wd = golem::pkg_path(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + if ( + golem_wd == "golem::pkg_path()" | + golem_wd == golem::pkg_path() + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" + } else { + golem_yaml_path <- path_abs(golem_wd) + } + + amend_golem_config( + key = "golem_wd", + value = golem_yaml_path, + config = "dev", + pkg = pkg, + talkative = talkative + ) + + invisible(path) +} + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_name <- function( + name = golem::pkg_name(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + # Changing in YAML + amend_golem_config( + key = "golem_name", + value = name, + config = "default", + pkg = pkg, + talkative = talkative + ) + # Changing in app-config.R + change_app_config_name( + name = name, + path = pkg + ) + + # Changing in DESCRIPTION + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) + + invisible(name) +} + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_version <- function( + version = golem::pkg_version(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + path <- path_abs(path) + + # Changing in YAML + amend_golem_config( + key = "golem_version", + value = as.character(version), + config = "default", + pkg = pkg, + talkative = talkative + ) + + desc <- desc::description$new( + file = fs::path( + pkg, + "DESCRIPTION" + ) + ) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) + + invisible(version) +} diff --git a/R/golem-yaml-utils.R b/R/golem-yaml-utils.R new file mode 100644 index 00000000..0e025655 --- /dev/null +++ b/R/golem-yaml-utils.R @@ -0,0 +1,110 @@ +# This file contains all the necessary functions +# required to manipualte the golem-config file. + +# find and tag expressions in a yaml +# used internally in `amend_golem_config` +# This is an utilitary function to prevent +# the !expr from being lost in translation +# when manipulating the yaml +# +#' @importFrom utils modifyList +find_and_tag_exprs <- function(conf_path) { + conf <- yaml::read_yaml( + conf_path, + eval.expr = FALSE + ) + conf.eval <- yaml::read_yaml( + conf_path, + eval.expr = TRUE + ) + + expr_list <- lapply( + names(conf), + function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + } + ) + + names(expr_list) <- names(conf) + + expr_list <- Filter( + function(x) length(x) > 0, + expr_list + ) + + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply( + expr_list, + add_expr_tag + ) + + modifyList( + conf, + tagged_exprs + ) +} + +#' Amend golem config file +#' +#' @param key key of the value to add in `config` +#' @inheritParams config::get +#' @inheritParams add_module +#' @inheritParams set_golem_options +#' +#' @export +#' @importFrom yaml read_yaml write_yaml +#' @importFrom attempt stop_if +#' +#' @return Used for side effects. +amend_golem_config <- function( + key, + value, + config = "default", + pkg = golem::pkg_path(), + talkative = TRUE +) { + conf_path <- get_current_config(pkg) + + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + + cat_if_talk <- function( + ..., + fun = cat_green_tick + ) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } + + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value + + write_yaml( + conf, + conf_path + ) + + invisible(TRUE) +} diff --git a/R/options.R b/R/options.R deleted file mode 100644 index 97d9895f..00000000 --- a/R/options.R +++ /dev/null @@ -1,276 +0,0 @@ -#' `{golem}` options -#' -#' Set and get a series of options to be used with `{golem}`. -#' These options are found inside the `golem-config.yml` file, found in most cases -#' inside the `inst` folder. -#' -#' @section Set Functions: -#' + `set_golem_options()` sets all the options, with the defaults from the functions below. -#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. -#' + `set_golem_name()` defaults `golem::pkg_name()` -#' + `set_golem_version()` defaults `golem::pkg_version()` -#' -#' @section Get Functions: -#' Reads the information from `golem-config.yml` -#' + `get_golem_wd()` -#' + `get_golem_name()` -#' + `get_golem_version()` -#' -#' @param golem_name Name of the current golem. -#' @param golem_version Version of the current golem. -#' @param golem_wd Working directory of the current golem package. -#' @param app_prod Is the `{golem}` in prod mode? -#' @param path The path to set the golem working directory. -#' Note that it will be passed to `normalizePath`. -#' @param talkative Should the messages be printed to the console? -#' @param name The name of the app -#' @param version The version of the app -#' @inheritParams config::get -#' -#' @rdname golem_opts -#' -#' @export -#' @importFrom attempt stop_if_not -#' @importFrom usethis proj_set -#' -#' @return Used for side-effects for the setters, and values from the -#' config in the getters. -set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE, - config_file = path(golem_wd, "inst/golem-config.yml") -) { - - # TODO here we'll run the - # golem_install_dev_pkg() function - - if (talkative) { - cli::cat_rule( - "Setting {golem} options in `golem-config.yml`" - ) - } - - # Let's start with wd - # Basically here the idea is to be able - # to keep the wd as an expr if it is the - # same as golem::pkg_path(), otherwise - # we use the explicit path - - set_golem_wd( - path = path, - talkative = talkative - ) - - # Setting name of the golem - set_golem_name( - name = golem_name, - talkative = talkative - ) - - # Setting golem_version - set_golem_version( - version = golem_version, - talkative = talkative - ) - - # Setting app_prod - set_golem_things( - "app_prod", - app_prod, - path = golem_wd, - talkative = talkative - ) - - # This part is for {usethis} and {here} - if (talkative) { - cli::cat_rule( - "Setting {usethis} project as `golem_wd`" - ) - } - - proj_set(golem_wd) -} - -set_golem_things <- function( - key, - value, - path, - talkative = TRUE, - config = "default" -) { - amend_golem_config( - key = key, - value = value, - config = config, - pkg = path, - talkative = talkative - ) - - invisible(path) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_wd <- function( - path = golem::pkg_path(), - talkative = TRUE -) { - if ( - path == "golem::pkg_path()" | - path == golem::pkg_path() - ) { - golem_yaml_path <- "golem::pkg_path()" - attr(golem_yaml_path, "tag") <- "!expr" - } else { - golem_yaml_path <- path_abs(path) - } - - set_golem_things( - "golem_wd", - golem_yaml_path, - path, - talkative = talkative, - config = "dev" - ) - - invisible(path) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_name <- function( - name = golem::pkg_name(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - # Changing in YAML - set_golem_things( - "golem_name", - name, - path, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = path - ) - - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(name) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_version <- function( - version = golem::pkg_version(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - set_golem_things( - "golem_version", - as.character(version), - path, - talkative = talkative - ) - desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(version) -} - -#' @importFrom config get -get_golem_things <- function( - value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path -) { - conf_path <- get_current_config(path, set_options = TRUE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) -} - - -#' @export -#' @rdname golem_opts -get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) -} - -#' @export -#' @rdname golem_opts -get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - nm -} - -#' @export -#' @rdname golem_opts -get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) -} diff --git a/R/set_golem_options.R b/R/set_golem_options.R new file mode 100644 index 00000000..c35ac8cc --- /dev/null +++ b/R/set_golem_options.R @@ -0,0 +1,97 @@ +#' `{golem}` options +#' +#' Set and get a series of options to be used with `{golem}`. +#' These options are found inside the `golem-config.yml` file, found in most cases +#' inside the `inst` folder. +#' +#' @section Set Functions: +#' + `set_golem_options()` sets all the options, with the defaults from the functions below. +#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. +#' + `set_golem_name()` defaults `golem::pkg_name()` +#' + `set_golem_version()` defaults `golem::pkg_version()` +#' +#' @section Get Functions: +#' Reads the information from `golem-config.yml` +#' + `get_golem_wd()` +#' + `get_golem_name()` +#' + `get_golem_version()` +#' +#' @param golem_name Name of the current golem. +#' @param golem_version Version of the current golem. +#' @param golem_wd Working directory of the current golem package. +#' @param app_prod Is the `{golem}` in prod mode? +#' @param path The path to set the golem working directory. +#' Note that it will be passed to `normalizePath`. +#' @param talkative Should the messages be printed to the console? +#' @param name The name of the app +#' @param version The version of the app +#' @inheritParams config::get +#' +#' @rdname golem_opts +#' +#' @export +#' @importFrom attempt stop_if_not +#' @importFrom usethis proj_set +#' +#' @return Used for side-effects for the setters, and values from the +#' config in the getters. +set_golem_options <- function( + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) +) { + + # TODO here we'll run the + # golem_install_dev_pkg() function + + if (talkative) { + cli::cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } + + # Let's do this in the order of the + # parameters + # Setting name of the golem + set_golem_name( + name = golem_name, + talkative = talkative + ) + + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path + + set_golem_wd( + path = golem_wd, + talkative = talkative + ) + + # Setting golem_version + set_golem_version( + version = golem_version, + talkative = talkative + ) + + # Setting app_prod + set_golem_things( + "app_prod", + app_prod, + path = golem_wd, + talkative = talkative + ) + + # This part is for {usethis} and {here} + if (talkative) { + cli::cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } + + proj_set(golem_wd) +} From a7c04cf48b0e161c1289672b0a8de0eefe482667 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:37:12 +0200 Subject: [PATCH 008/190] fix(config): Use the correct env var in get_golem_* The get_golem_* didn't rely on the GOLEM_CONFIG_ACTIVE env_var --- R/golem-yaml-get.R | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index 3c93ae29..976b1a7c 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -2,7 +2,13 @@ #' @importFrom config get get_golem_things <- function( value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + config = config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path ) { @@ -38,7 +44,13 @@ get_golem_wd <- function( #' @export #' @rdname golem_opts get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { @@ -57,7 +69,13 @@ get_golem_name <- function( #' @export #' @rdname golem_opts get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { From b420bd5854fb7a52f25b8e71095d9737c7412102 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:37:46 +0200 Subject: [PATCH 009/190] fix(config): golem_wd is now golem::pkg_path() --- inst/shinyexample/inst/golem-config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml index 879263c9..3e66f3fb 100644 --- a/inst/shinyexample/inst/golem-config.yml +++ b/inst/shinyexample/inst/golem-config.yml @@ -1,11 +1,11 @@ -default: +default: golem_name: shinyexample golem_version: 0.0.0.9000 app_prod: no -production: +production: app_prod: yes - -dev: - golem_wd: !exp golem::golem_path() + +dev: + golem_wd: !expr golem::pkg_path() From 411f952e9d0993bae6d4e13edd5b5124ff7a1beb Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:38:50 +0200 Subject: [PATCH 010/190] chore(style): Change the indentation of param --- R/golem-yaml-get.R | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index 976b1a7c..d4f23bd3 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -2,13 +2,13 @@ #' @importFrom config get get_golem_things <- function( value, - config = config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path ) { @@ -45,12 +45,12 @@ get_golem_wd <- function( #' @rdname golem_opts get_golem_name <- function( config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { @@ -70,12 +70,12 @@ get_golem_name <- function( #' @rdname golem_opts get_golem_version <- function( config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { From 30ec5d236ff2a71583e53f7d7fe3e3629e24383b Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:44:23 +0200 Subject: [PATCH 011/190] feat(config): get_golem_* now tries to guess if not found in yaml + get_golem_wd tries golem::pkg_path + get_golem_name tries pkg_name + get_golem_version tries pkg_version --- R/golem-yaml-get.R | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index d4f23bd3..b6c82eae 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -12,7 +12,10 @@ get_golem_things <- function( use_parent = TRUE, path ) { - conf_path <- get_current_config(path, set_options = TRUE) + conf_path <- get_current_config( + path, + set_options = TRUE + ) stop_if( conf_path, is.null, @@ -33,12 +36,16 @@ get_golem_wd <- function( use_parent = TRUE, path = golem::pkg_path() ) { - get_golem_things( + pth <- get_golem_things( value = "golem_wd", config = "dev", use_parent = use_parent, path = path ) + if (is.null(pth)) { + pth <- golem::pkg_path() + } + return(pth) } #' @export @@ -63,7 +70,7 @@ get_golem_name <- function( if (is.null(nm)) { nm <- golem::pkg_name() } - nm + return(nm) } #' @export @@ -79,10 +86,14 @@ get_golem_version <- function( use_parent = TRUE, path = golem::pkg_path() ) { - get_golem_things( + vers <- get_golem_things( value = "golem_version", config = config, use_parent = use_parent, path = path ) + if (is.null(vers)) { + vers <- golem::pkg_version() + } + return(vers) } From 05033a3a9098ecc54167797889ef034faac83b89 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Tue, 5 Jul 2022 17:30:52 +0200 Subject: [PATCH 012/190] fix(test): no recommended tests --- inst/mantests/build.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index e78800cf..d17fec96 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -299,7 +299,7 @@ withr::with_tempdir({ dir.exists("tests") ) - golem::use_recommended_deps() + # golem::use_recommended_deps() golem::use_utils_ui(with_test = TRUE) expect_true( @@ -432,4 +432,4 @@ withr::with_tempdir({ # unlink(temp_lib, TRUE, TRUE) cli::cat_rule("Completed") -}) \ No newline at end of file +}) From c07cf9ca4d911fe1448e1713f31834000c6d7a0c Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Tue, 5 Jul 2022 17:36:40 +0200 Subject: [PATCH 013/190] feat(config): get_current_config no longer set options --- R/config.R | 95 ++++---------------------------- vignettes/a_start.Rmd | 10 +--- vignettes/b_dev.Rmd | 2 +- vignettes/c_deploy.Rmd | 2 +- vignettes/d_js.Rmd | 2 +- vignettes/e_config.Rmd | 2 +- vignettes/f_extending_golem.Rmd | 46 ++++++++-------- vignettes/z_golem_cheatsheet.Rmd | 2 +- 8 files changed, 40 insertions(+), 121 deletions(-) diff --git a/R/config.R b/R/config.R index bedce9dd..1740adb4 100644 --- a/R/config.R +++ b/R/config.R @@ -51,11 +51,18 @@ guess_where_config <- function( return(NULL) } +#' Get the path to the current config File +#' +#' This function tries to guess where the golem-config file is located. +#' If it can't find it, this function asks the +#' user if they want to set the golem skeleton. +#' #' @importFrom fs file_copy path -get_current_config <- function( - path = ".", - set_options = TRUE -) { +#' +#' @param path Path to start looking for the config +#' +#' @export +get_current_config <- function(path = ".") { # We check wether we can guess where the config file is path_conf <- guess_where_config(path) @@ -103,9 +110,7 @@ get_current_config <- function( "shinyexample", golem::pkg_name() ) - if (set_options) { - set_golem_options() - } + # TODO This should also create the dev folder } else { stop( sprintf( @@ -141,79 +146,3 @@ change_app_config_name <- function( ) write(app_config, pth) } - - -# find and tag expressions in a yaml -# used internally in `amend_golem_config` -#' @importFrom utils modifyList -find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) - conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) - - expr_list <- lapply(names(conf), function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - }) - names(expr_list) <- names(conf) - expr_list <- Filter(function(x) length(x) > 0, expr_list) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply(expr_list, add_expr_tag) - modifyList(conf, tagged_exprs) -} - -#' Amend golem config file -#' -#' @param key key of the value to add in `config` -#' @inheritParams config::get -#' @inheritParams add_module -#' @inheritParams set_golem_options -#' -#' @export -#' @importFrom yaml read_yaml write_yaml -#' @importFrom attempt stop_if -#' -#' @return Used for side effects. -amend_golem_config <- function( - key, - value, - config = "default", - pkg = get_golem_wd(), - talkative = TRUE -) { - conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } - - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) - - if (key == "golem_wd") { - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) - } - - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) - invisible(TRUE) -} diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 002671ec..ac306ea8 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -1,5 +1,5 @@ --- -title: "Getting Started with {golem}" +title: "a_start" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -157,14 +157,6 @@ golem::use_recommended_tests() About [tests in a package](https://r-pkgs.org/tests.html). -### Use Recommended Packages - -This will add "shiny", "DT", "attempt", "glue", "htmltools", and "golem" as a dependency to your package. - -```{r} -golem::use_recommended_deps() -``` - ### Add various tools + If you want to change the default favicon diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index aaa3dd82..9865465f 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -1,5 +1,5 @@ --- -title: "Day to Day Dev with {golem}" +title: "b_dev" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index 4021cd03..d3bccc78 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -1,5 +1,5 @@ --- -title: "Deploying Apps with {golem}" +title: "c_deploy" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/d_js.Rmd b/vignettes/d_js.Rmd index 72629009..757b3a42 100644 --- a/vignettes/d_js.Rmd +++ b/vignettes/d_js.Rmd @@ -1,5 +1,5 @@ --- -title: "Using {golem} js functions" +title: "d_js" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 5339ec26..384de1ca 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -1,5 +1,5 @@ --- -title: "Using golem config" +title: "config" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{config} diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index ab3569d8..b3b6bf61 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -1,5 +1,5 @@ --- -title: "Extending {golem}" +title: "f_extending_golem" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{f_extending_golem} @@ -10,7 +10,7 @@ vignette: > ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>", + comment = "#>", eval = FALSE ) ``` @@ -70,8 +70,8 @@ These parameters might not be used inside your own hook, but __they need to be s Here is an example of a function that can be used to remove the `dev/` folder: ```{r} -no_dev <- function(path, package_name, ...){ - fs::dir_delete("dev") +no_dev <- function(path, package_name, ...) { + fs::dir_delete("dev") } create_golem("ici", project_hook = no_dev) ``` @@ -79,22 +79,20 @@ create_golem("ici", project_hook = no_dev) This one will create a CSS: ```{r} -new_css <- function(path, package_name, ...){ - +new_css <- function(path, package_name, ...) { css_path <- fs::path_abs("inst/app/www/custom.css") - + fs::file_create(css_path) - - write_there <- function(...){ + + write_there <- function(...) { write(..., file = css_path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") - + cli::cat_bullet("CSS generated") - } create_golem("ici", project_hook = new_css) @@ -137,16 +135,16 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(name, path, export, ...){ - # Define a template that only write the name of the - # module in the file - write(name, path) +my_tmpl <- function(name, path, export, ...) { + # Define a template that only write the name of the + # module in the file + write(name, path) } golem::add_module(name = "custom", module_template = my_tmpl) -my_other_tmpl <- function(name, path, ...){ - # Copy and paste a file from somewhere else - file.copy(..., path) +my_other_tmpl <- function(name, path, ...) { + # Copy and paste a file from somewhere else + file.copy(..., path) } golem::add_module(name = "custom", module_template = my_other_tmpl) ``` @@ -186,13 +184,13 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(path, ...){ - # Define a template that only write the name of the - # module in the file - write_there <- function(...){ +my_tmpl <- function(path, ...) { + # Define a template that only write the name of the + # module in the file + write_there <- function(...) { write(..., file = path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") diff --git a/vignettes/z_golem_cheatsheet.Rmd b/vignettes/z_golem_cheatsheet.Rmd index 4c1c9199..10c3967b 100644 --- a/vignettes/z_golem_cheatsheet.Rmd +++ b/vignettes/z_golem_cheatsheet.Rmd @@ -1,5 +1,5 @@ --- -title: "{golem} cheatsheet" +title: "e_golem_cheatsheet" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{e_golem_cheatsheet} From 709a382746ce445e6b082691065d6d078add948d Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Wed, 13 Jul 2022 14:34:36 +0200 Subject: [PATCH 014/190] feat: add support for docker & {renv} (#882) Co-authored-by: Colin Fay --- DESCRIPTION | 8 +- NAMESPACE | 3 + NEWS.md | 10 + R/add_dockerfiles.R | 411 ++++++++++++++++------- R/add_dockerfiles_renv.R | 331 ++++++++++++++++++ R/utils.R | 4 +- README.Rmd | 2 +- README.md | 64 ++-- inst/shinyexample/dev/02_dev.R | 1 + inst/shinyexample/dev/03_deploy.R | 6 +- man/dockerfiles.Rd | 106 +++++- man/document_and_reload.Rd | 2 +- man/figures/logo.png | Bin 103244 -> 0 bytes tests/testthat/helper-config.R | 13 +- tests/testthat/test-add_deploy_helpers.R | 52 --- tests/testthat/test-renv_stuff.R | 33 ++ vignettes/a_start.Rmd | 4 +- vignettes/b_dev.Rmd | 4 +- vignettes/c_deploy.Rmd | 76 +++++ 19 files changed, 885 insertions(+), 245 deletions(-) create mode 100644 R/add_dockerfiles_renv.R delete mode 100644 man/figures/logo.png create mode 100644 tests/testthat/test-renv_stuff.R diff --git a/DESCRIPTION b/DESCRIPTION index a64825f8..d63fc9b1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.2 +Version: 0.3.3 Authors@R: c(person(given = "Colin", family = "Fay", @@ -68,7 +68,7 @@ Suggests: rlang, covr, devtools, - dockerfiler (>= 0.1.4), + dockerfiler (>= 0.2.0), knitr, pkgbuild, pkgdown, @@ -82,11 +82,11 @@ Suggests: testthat, tools, withr, - attachment + attachment (>= 0.2.5) VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.0 diff --git a/NAMESPACE b/NAMESPACE index 7f527830..f31af341 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,9 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) +export(add_dockerfile_with_renv) +export(add_dockerfile_with_renv_heroku) +export(add_dockerfile_with_renv_shinyproxy) export(add_fct) export(add_html_template) export(add_js_file) diff --git a/NEWS.md b/NEWS.md index 5d483926..6d0f6614 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,15 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. +# golem 0.3.3 + +## New functions + ++ `add_dockerfile_with_renv()`, `add_dockerfile_with_renv_heroku()` and `add_dockerfile_with_renv_shinyproxy()` build Dockerfiles that rely on `{renv}` + +### Soft deprecated + ++ `add_dockerfile`, `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` now recommend to switch to their `_with_renv_` counterpart + # golem 0.3.2 ### Soft deprecated diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 6bd0dc07..df3bb111 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -1,7 +1,18 @@ +talk_once <- function(.f, msg = "") { + talk <- TRUE + function(...) { + if (talk) { + talk <<- FALSE + cat_red_bullet(msg) + } + .f(...) + } +} + #' Create a Dockerfile for your App #' -#' Build a container containing your Shiny App. `add_dockerfile()` creates -#' a generic Dockerfile, while `add_dockerfile_shinyproxy()` and +#' Build a container containing your Shiny App. `add_dockerfile()` and `add_dockerfile_with_renv()` creates +#' a generic Dockerfile, while `add_dockerfile_shinyproxy()`, `add_dockerfile_with_renv_shinyproxy()` and #' `add_dockerfile_heroku()` creates platform specific Dockerfile. #' #' @inheritParams add_module @@ -9,7 +20,12 @@ #' @param path path to the DESCRIPTION file to use as an input. #' @param output name of the Dockerfile output. #' @param from The FROM of the Dockerfile. Default is -#' FROM rocker/r-ver:`R.Version()$major`.`R.Version()$minor`. +#' +#' FROM rocker/verse +#' +#' without renv.lock file passed +#' `R.Version()$major`.`R.Version()$minor` is used as tag +#' #' @param as The AS of the Dockerfile. Default it NULL. #' @param port The `options('shiny.port')` on which to run the App. #' Default is 80. @@ -18,13 +34,12 @@ #' @param sysreqs boolean. If TRUE, the Dockerfile will contain sysreq installation. #' @param repos character. The URL(s) of the repositories to use for `options("repos")`. #' @param expand boolean. If `TRUE` each system requirement will have its own `RUN` line. -#' @param open boolean. Should the Dockerfile be open after creation? Default is `TRUE`. +#' @param open boolean. Should the Dockerfile/README be open after creation? Default is `TRUE`. #' @param build_golem_from_source boolean. If `TRUE` no tar.gz is created and #' the Dockerfile directly mount the source folder. #' @param update_tar_gz boolean. If `TRUE` and `build_golem_from_source` is also `TRUE`, #' an updated tar.gz is created. #' @param extra_sysreqs character vector. Extra debian system requirements. -#' Will be installed with apt-get install. #' #' @export #' @rdname dockerfiles @@ -40,10 +55,28 @@ #' if (interactive()) { #' add_dockerfile() #' } +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock", # uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } #' # Add a Dockerfile for ShinyProxy #' if (interactive()) { #' add_dockerfile_shinyproxy() #' } +#' +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem with ShinyProxy using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock",# uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } +#' #' # Add a Dockerfile for Heroku #' if (interactive()) { #' add_dockerfile_heroku() @@ -55,7 +88,7 @@ add_dockerfile <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -71,54 +104,96 @@ add_dockerfile <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - check_is_installed("dockerfiler") - - required_version("dockerfiler", "0.1.4") - - where <- path(pkg, output) - - usethis::use_build_ignore(path_file(where)) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, + port = port, + host = host, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$EXPOSE(port) +add_dockerfile_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + check_is_installed("dockerfiler") - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", - port, - host, - read.dcf(path)[1] + required_version("dockerfiler", "0.1.4") + + where <- path(pkg, output) + + usethis::use_build_ignore(path_file(where)) + + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs ) - ) - dock$write(output) + dock$EXPOSE(port) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + read.dcf(path)[1] + ) + ) + + dock$write(output) + + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv() instead." +) #' @export #' @rdname dockerfiles @@ -128,7 +203,7 @@ add_dockerfile_shinyproxy <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -142,47 +217,84 @@ add_dockerfile_shinyproxy <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - check_is_installed("dockerfiler") - required_version("dockerfiler", "0.1.4") - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_shinyproxy_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} + +add_dockerfile_shinyproxy_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + check_is_installed("dockerfiler") + required_version("dockerfiler", "0.1.4") + where <- path(pkg, output) - dock$EXPOSE(3838) - dock$CMD(sprintf( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", - read.dcf(path)[1] - )) - dock$write(output) + usethis::use_build_ignore(output) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) + + dock$EXPOSE(3838) + dock$CMD(sprintf( + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", + read.dcf(path)[1] + )) + dock$write(output) + + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - alert_build( - path, - output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path, + output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile_shinyproxy() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_shinyproxy() instead." +) #' @export #' @rdname dockerfiles @@ -192,7 +304,7 @@ add_dockerfile_heroku <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -206,77 +318,116 @@ add_dockerfile_heroku <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - check_is_installed("dockerfiler") - required_version("dockerfiler", "0.1.4") - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_heroku_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", - read.dcf(path)[1] +add_dockerfile_heroku_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + check_is_installed("dockerfiler") + required_version("dockerfiler", "0.1.4") + where <- path(pkg, output) + + usethis::use_build_ignore(output) + + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs ) - ) - dock$write(output) - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + read.dcf(path)[1] + ) + ) + dock$write(output) - apps_h <- gsub( - "\\.", - "-", - sprintf( - "%s-%s", - read.dcf(path)[1], - read.dcf(path)[1, ][["Version"]] + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source ) - ) - cat_rule("From your command line, run:") - cat_line("heroku container:login") - cat_line( - sprintf("heroku create %s", apps_h) - ) - cat_line( - sprintf("heroku container:push web --app %s", apps_h) - ) - cat_line( - sprintf("heroku container:release web --app %s", apps_h) - ) - cat_line( - sprintf("heroku open --app %s", apps_h) - ) - cat_red_bullet("Be sure to have the heroku CLI installed.") - cat_red_bullet( - sprintf("You can replace %s with another app name.", apps_h) - ) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + read.dcf(path)[1], + read.dcf(path)[1, ][["Version"]] + ) + ) + + cat_rule("From your command line, run:") + cat_line("heroku container:login") + cat_line( + sprintf("heroku create %s", apps_h) + ) + cat_line( + sprintf("heroku container:push web --app %s", apps_h) + ) + cat_line( + sprintf("heroku container:release web --app %s", apps_h) + ) + cat_line( + sprintf("heroku open --app %s", apps_h) + ) + cat_red_bullet("Be sure to have the heroku CLI installed.") + cat_red_bullet( + sprintf("You can replace %s with another app name.", apps_h) + ) + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - usethis::use_build_ignore(files = output) - return(invisible(dock)) -} + usethis::use_build_ignore(files = output) + return(invisible(dock)) + }, + " +golem::add_dockerfile_heroku() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_heroku() instead. +" +) alert_build <- function( path, @@ -294,4 +445,4 @@ alert_build <- function( ) ) } -} \ No newline at end of file +} diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R new file mode 100644 index 00000000..80fb9d61 --- /dev/null +++ b/R/add_dockerfiles_renv.R @@ -0,0 +1,331 @@ +add_dockerfile_with_renv_ <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + FROM = "rocker/verse", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + update_tar_gz = TRUE + # build_golem_from_source = TRUE, +) { + check_is_installed("renv") + check_is_installed("dockerfiler") + required_version("dockerfiler", "0.2.0") + check_is_installed("attachment") + + # Small hack to prevent warning from rlang::lang() in tests + # This should be managed in {attempt} later on + x <- suppressWarnings({ + rlang::lang(print) + }) + + dir.create(output_dir) + + # add output_dir in Rbuildignore if the output is inside the golem + if (normalizePath(dirname(output_dir)) == normalizePath(source_folder)) { + usethis::use_build_ignore(output_dir) + } + + if (is.null(lockfile)) { + lockfile <- attachment::create_renv_for_prod(path = source_folder, output = file.path(output_dir, "renv.lock.prod")) + } + + file.copy(from = lockfile, to = output_dir) + + socle <- dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + repos = repos, + AS = AS, + sysreqs = sysreqs, + expand = expand, + extra_sysreqs = extra_sysreqs + ) + + socle$write(as = file.path(output_dir, "Dockerfile_base")) + + my_dock <- dockerfiler::Dockerfile$new(FROM = paste0(golem::get_golem_name(), "_base")) + + my_dock$COPY("renv.lock.prod", "renv.lock") + + my_dock$RUN("R -e 'renv::restore()'") + + if (update_tar_gz) { + old_version <- list.files(path = output_dir, pattern = paste0(golem::get_golem_name(), "_*.*.tar.gz"), full.names = TRUE) + # file.remove(old_version) + if (length(old_version) > 0) { + lapply(old_version, file.remove) + lapply(old_version, unlink, force = TRUE) + cat_red_bullet( + sprintf( + "%s were removed from folder", + paste( + old_version, + collapse = ", " + ) + ) + ) + } + + if ( + isTRUE( + requireNamespace( + "pkgbuild", + quietly = TRUE + ) + ) + ) { + out <- pkgbuild::build( + path = ".", + dest_path = output_dir, + vignettes = FALSE + ) + if (missing(out)) { + cat_red_bullet("Error during tar.gz building") + } else { + cat_green_tick( + sprintf( + " %s created.", + out + ) + ) + } + } else { + stop("please install {pkgbuild}") + } + } + + # we use an already built tar.gz file + my_dock$COPY( + from = + paste0(golem::get_golem_name(), "_*.tar.gz"), + to = "/app.tar.gz" + ) + my_dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\",upgrade=\"never\")'") + my_dock$RUN("rm /app.tar.gz") + my_dock +} + +#' @param source_folder path to the Package/golem source folder to deploy. +#' default is current folder '.' +#' @param lockfile path to the renv.lock file to use. default is `NULL` +#' @param output_dir folder to export everything deployment related. +#' @param distro One of "focal", "bionic", "xenial", "centos7", or "centos8". +#' See available distributions at https://hub.docker.com/r/rstudio/r-base/. +#' @param dockerfile_cmd What is the CMD to add to the Dockerfile. If NULL, the default, +#' the CMD will be `R -e "options('shiny.port'={port},shiny.host='{host}');{appname}::run_app()\` +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +add_dockerfile_with_renv <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL +) { + base_dock <- add_dockerfile_with_renv_( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz + ) + if (!is.null(port)) { + base_dock$EXPOSE(port) + } + if (is.null(dockerfile_cmd)) { + dockerfile_cmd <- sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + golem::get_golem_name() + ) + } + base_dock$CMD( + dockerfile_cmd + ) + base_dock + base_dock$write(as = file.path(output_dir, "Dockerfile")) + + out <- sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s . +docker build -f Dockerfile --progress=plain -t %s . +docker run -p %s:%s %s +# then go to 127.0.0.1:%s", + paste0(golem::get_golem_name(), "_base"), + paste0(golem::get_golem_name(), ":latest"), + port, + port, + paste0(golem::get_golem_name(), ":latest"), + port + ) + + cat(out, file = file.path(output_dir, "README")) + + open_or_go_to( + where = file.path(output_dir, "README"), + open_file = open + ) +} + +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_shinyproxy <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = 3838, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = open, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"", + golem::get_golem_name() + ) + ) +} + +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_heroku <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = NULL, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = FALSE, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + golem::get_golem_name() + ) + ) + + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + golem::get_golem_name(), + golem::get_golem_version() + ) + ) + + readme_output <- file.path(output_dir, "README") + + write_there <- function(...) { + write(..., file = readme_output, append = TRUE) + } + + write_there("From your command line, run:\n") + + write_there( + sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s .", + paste0(golem::get_golem_name(), "_base") + ) + ) + + write_there( + sprintf( + "docker build -f Dockerfile --progress=plain -t %s .\n", + paste0(golem::get_golem_name(), ":latest") + ) + ) + + write_there("Then, to push on heroku:\n") + + write_there("heroku container:login") + write_there( + sprintf("heroku create %s", apps_h) + ) + write_there( + sprintf("heroku container:push web --app %s", apps_h) + ) + write_there( + sprintf("heroku container:release web --app %s", apps_h) + ) + write_there( + sprintf("heroku open --app %s\n", apps_h) + ) + write_there("> Be sure to have the heroku CLI installed.") + + write_there( + sprintf("> You can replace %s with another app name.", apps_h) + ) + + # The open is deported here just to be sure + # That we open the README once it has been populated + open_or_go_to( + where = readme_output, + open_file = open + ) +} diff --git a/R/utils.R b/R/utils.R index e95d3b2d..ae3200ea 100644 --- a/R/utils.R +++ b/R/utils.R @@ -17,8 +17,8 @@ darkgrey <- function(x) { } #' @importFrom fs dir_exists file_exists -dir_not_exist <- Negate(dir_exists) -file_not_exist <- Negate(file_exists) +dir_not_exist <- Negate(fs::dir_exists) +file_not_exist <- Negate(fs::file_exists) #' @importFrom fs dir_create file_create create_if_needed <- function( diff --git a/README.Rmd b/README.Rmd index c06f5466..ad948985 100644 --- a/README.Rmd +++ b/README.Rmd @@ -88,7 +88,7 @@ This package is part of a series of tools for Shiny, which includes: These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - -- +- - - diff --git a/README.md b/README.md index 8ceefc47..19bddce1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ - [![Lifecycle: @@ -24,33 +23,33 @@ You’re reading the doc about version : ``` r desc::desc_get_version() -#> [1] '0.3.2' +#> [1] '0.3.3' ``` ## Tool series This package is part of a series of tools for Shiny, which includes: - - `{golem}` - - - `{shinipsum}` - - - `{fakir}` - - - `{shinysnippets}` - +- `{golem}` - +- `{shinipsum}` - +- `{fakir}` - +- `{shinysnippets}` - ## Resources ### The Book : - - - - [paper version of the book “Engineering Production-Grade Shiny +- +- [paper version of the book “Engineering Production-Grade Shiny Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) ### Blog posts : *Building Big Shiny Apps* - - Part 1: +- Part 1: - - Part 2: +- Part 2: [*Make a Fitness App from @@ -58,34 +57,34 @@ scratch*](https://towardsdatascience.com/production-grade-r-shiny-with-golem-pro ### Slide decks - - useR\! 2019 : [A Framework for Building Robust & Production Ready +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://github.com/VincentGuyader/user2019/raw/master/golem_Vincent_Guyader_USER!2019.pdf) - - ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with +- ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with {golem}](https://speakerdeck.com/colinfay/production-grade-shiny-apps-with-golem) - - rstudio::conf(2020) : [Production-grade Shiny Apps with +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) - - barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps +- barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps with {golem}](https://www.barcelonar.org/presentations/BarcelonaR_Building_Production_Grade_Shiny_Apps_with_golem.pdf) ### Video - - [{golem} and Effective Shiny Development +- [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) - - [Hands-on demonstration of +- [Hands-on demonstration of {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) - - useR\! 2019 : [A Framework for Building Robust & Production Ready +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://youtu.be/tCAan6smrjs) - - 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) - - rstudio::conf(2020) : [Production-grade Shiny Apps with +- 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) - - 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec +- 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) ### Cheatsheet - - [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) +- [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) ### Examples apps @@ -93,31 +92,27 @@ These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - - - - - - - - +- +- +- +- You can also find apps at: - - - - +- +- ## Installation - - You can install the stable version from CRAN with: - - +- You can install the stable version from CRAN with: ``` r install.packages("golem") ``` - - You can install the development version from +- You can install the development version from [GitHub](https://github.com/Thinkr-open/golem) with: - - ``` r # install.packages("remotes") remotes::install_github("Thinkr-open/golem") @@ -125,8 +120,7 @@ remotes::install_github("Thinkr-open/golem") ## Launch the project -Create a new package with the project -template: +Create a new package with the project template: diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index 82478726..67699ea7 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -15,6 +15,7 @@ ## Dependencies ---- ## Amend DESCRIPTION with dependencies read from package code parsing +## install.package('attachment') # if needed. attachment::att_amend_desc() ## Add modules ---- diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index 464d59fd..2f9595ef 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -33,10 +33,8 @@ golem::add_shinyserver_file() ## Docker ---- ## If you want to deploy via a generic Dockerfile -golem::add_dockerfile() +golem::add_dockerfile_with_renv() ## If you want to deploy to ShinyProxy -golem::add_dockerfile_shinyproxy() +golem::add_dockerfile_with_renv_shinyproxy() -## If you want to deploy to Heroku -golem::add_dockerfile_heroku() diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index bbeb97b4..53129521 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,16 +1,19 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_dockerfiles.R +% Please edit documentation in R/add_dockerfiles.R, R/add_dockerfiles_renv.R \name{add_dockerfile} \alias{add_dockerfile} \alias{add_dockerfile_shinyproxy} \alias{add_dockerfile_heroku} +\alias{add_dockerfile_with_renv} +\alias{add_dockerfile_with_renv_shinyproxy} +\alias{add_dockerfile_with_renv_heroku} \title{Create a Dockerfile for your App} \usage{ add_dockerfile( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", @@ -27,7 +30,7 @@ add_dockerfile_shinyproxy( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -42,7 +45,7 @@ add_dockerfile_heroku( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -52,6 +55,54 @@ add_dockerfile_heroku( build_golem_from_source = TRUE, extra_sysreqs = NULL ) + +add_dockerfile_with_renv( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL +) + +add_dockerfile_with_renv_shinyproxy( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) + +add_dockerfile_with_renv_heroku( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -61,7 +112,12 @@ add_dockerfile_heroku( \item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{from}{The FROM of the Dockerfile. Default is -FROM rocker/r-ver:\code{R.Version()$major}.\code{R.Version()$minor}.} + +\if{html}{\out{
}}\preformatted{FROM rocker/verse + +without renv.lock file passed +`R.Version()$major`.`R.Version()$minor` is used as tag +}\if{html}{\out{
}}} \item{as}{The AS of the Dockerfile. Default it NULL.} @@ -77,7 +133,7 @@ Default is 0.0.0.0.} \item{expand}{boolean. If \code{TRUE} each system requirement will have its own \code{RUN} line.} -\item{open}{boolean. Should the Dockerfile be open after creation? Default is \code{TRUE}.} +\item{open}{boolean. Should the Dockerfile/README be open after creation? Default is \code{TRUE}.} \item{update_tar_gz}{boolean. If \code{TRUE} and \code{build_golem_from_source} is also \code{TRUE}, an updated tar.gz is created.} @@ -85,15 +141,27 @@ an updated tar.gz is created.} \item{build_golem_from_source}{boolean. If \code{TRUE} no tar.gz is created and the Dockerfile directly mount the source folder.} -\item{extra_sysreqs}{character vector. Extra debian system requirements. -Will be installed with apt-get install.} +\item{extra_sysreqs}{character vector. Extra debian system requirements.} + +\item{source_folder}{path to the Package/golem source folder to deploy. +default is current folder '.'} + +\item{lockfile}{path to the renv.lock file to use. default is \code{NULL}} + +\item{output_dir}{folder to export everything deployment related.} + +\item{distro}{One of "focal", "bionic", "xenial", "centos7", or "centos8". +See available distributions at https://hub.docker.com/r/rstudio/r-base/.} + +\item{dockerfile_cmd}{What is the CMD to add to the Dockerfile. If NULL, the default, +the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}');\{appname\}::run_app()\\}} } \value{ The \code{{dockerfiler}} object, invisibly. } \description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a generic Dockerfile, while \code{add_dockerfile_shinyproxy()} and +Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} creates +a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} and \code{add_dockerfile_heroku()} creates platform specific Dockerfile. } \examples{ @@ -102,10 +170,28 @@ a generic Dockerfile, while \code{add_dockerfile_shinyproxy()} and if (interactive()) { add_dockerfile() } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock", # uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} # Add a Dockerfile for ShinyProxy if (interactive()) { add_dockerfile_shinyproxy() } + +# Crete a 'deploy' folder containing everything needed to deploy +# the golem with ShinyProxy using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock",# uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} + # Add a Dockerfile for Heroku if (interactive()) { add_dockerfile_heroku() diff --git a/man/document_and_reload.Rd b/man/document_and_reload.Rd index 8d0c84ec..95b60561 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -24,7 +24,7 @@ which defaults to \code{c("collate", "namespace", "rd")}.} \item{load_code}{A function used to load all the R code in the package directory. The default, \code{NULL}, uses the strategy defined by -the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load]{load_pkgload()}}. +the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load_pkgload]{load_pkgload()}}. See \link[roxygen2]{load} for more details.} \item{clean}{If \code{TRUE}, roxygen will delete all files previously diff --git a/man/figures/logo.png b/man/figures/logo.png deleted file mode 100644 index a149d47866c88809bce0d6dd26bc354b516d498b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103244 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ቛ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

Iw`cPY_ozCrlIKG8yo#XoSbV&&wTQN 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 diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index d9af3ab9..9273c45d 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -3,6 +3,12 @@ library(golem) library(withr) old_usethis.quiet <- getOption("usethis.quiet") options("usethis.quiet" = TRUE) +# Small hack to prevent warning from rlang::lang() in tests +# This should be managed in {attempt} later on +x <- suppressWarnings({ + rlang::lang(print) +}) + ### Funs remove_file <- function(path) { if (file.exists(path)) unlink(path, force = TRUE) @@ -56,13 +62,16 @@ fakename <- sprintf( gsub("[ :-]", "", Sys.time()) ) + +## random dir +randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") + tpdir <- normalizePath(tempdir()) unlink(file.path(tpdir, fakename), recursive = TRUE) create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) -## random dir -randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") + fp <- file.path("inst/app", randir) dir.create(file.path(pkg, fp), recursive = TRUE) diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 8aef7553..7057e65a 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -76,60 +76,8 @@ test_that("add_dockerfiles repos variation", { } }) }) -test_that("add_dockerfiles multi repos", { - skip_if_not_installed("dockerfiler", "0.1.4") - - repos <- c( - bioc1 = "https://bioconductor.org/packages/3.10/data/annotation", - bioc2 = "https://bioconductor.org/packages/3.10/data/experiment", - CRAN = "https://cran.rstudio.com" - ) - - - with_dir(pkg, { - for (fun in list( - add_dockerfile, - add_dockerfile_heroku, - add_dockerfile_shinyproxy - )) { - burn_after_reading( - "Dockerfile", - { - output <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = repos, - output = "Dockerfile" - ) - ) - - expect_exists("Dockerfile") - - - test <- stringr::str_detect( - output, - "Dockerfile created at Dockerfile" - ) - expect_true(test) - to_find <- "RUN echo \"options(repos = c(bioc1 = 'https://bioconductor.org/packages/3.10/data/annotation', bioc2 = 'https://bioconductor.org/packages/3.10/data/experiment', CRAN = 'https://cran.rstudio.com'), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - # for R <= 3.4 - to_find_old <- "RUN echo \"options(repos = structure(c('https://bioconductor.org/packages/3.10/data/annotation', 'https://bioconductor.org/packages/3.10/data/experiment', 'https://cran.rstudio.com'), .Names = c('bioc1', 'bioc2', 'CRAN')), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - - expect_true( - sum( - readLines(con = "Dockerfile") %in% c(to_find, to_find_old) - ) == 1 - ) - } - ) - } - }) -}) - test_that("add_rstudio_files", { with_dir(pkg, { for (fun in list( diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R new file mode 100644 index 00000000..43d74c15 --- /dev/null +++ b/tests/testthat/test-renv_stuff.R @@ -0,0 +1,33 @@ +test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all output file are present", { + skip_if_not_installed("dockerfiler", "0.2.0") + + with_dir(pkg, { + for (fun in list( + add_dockerfile_with_renv, + add_dockerfile_with_renv_shinyproxy, + add_dockerfile_with_renv_heroku + )) { + deploy_folder <- file.path( + tempdir(), + make.names( + paste0( + "deploy", + round( + runif(1, min = 0, max = 99999) + ) + ) + ) + ) + + fun(output_dir = deploy_folder, open = FALSE) + + expect_exists(file.path(deploy_folder, "Dockerfile")) + expect_exists(file.path(deploy_folder, "Dockerfile_base")) + expect_exists(file.path(deploy_folder, "README")) + expect_exists(file.path(deploy_folder, "renv.lock.prod")) + + expect_length(list.files(path = deploy_folder, pattern = "tar.gz$"), 1) + unlink(deploy_folder, force = TRUE, recursive = TRUE) + } + }) +}) diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 002671ec..3d93116e 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -122,7 +122,7 @@ golem::fill_desc( ) ``` -About [the DESCRIPTION file](https://r-pkgs.org/description.html). +About [the DESCRIPTION file](https://r-pkgs.org/Metadata.html#sec-description). ### Add `{golem}` options @@ -155,7 +155,7 @@ Create a template for tests: golem::use_recommended_tests() ``` -About [tests in a package](https://r-pkgs.org/tests.html). +About [tests in a package](https://r-pkgs.org/testing-basics.html). ### Use Recommended Packages diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index aaa3dd82..052bbf95 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -45,7 +45,7 @@ Note that the `{attachment}` package should be installed on your machine. attachment::att_amend_desc() ``` -About [package dependencies](https://r-pkgs.org/namespace.html). +About [package dependencies](https://r-pkgs.org/Metadata.html#sec-namespace). ### Add modules @@ -139,7 +139,7 @@ Add more tests to your application: usethis::use_test("app") ``` -About [testing a package](https://r-pkgs.org/tests.html). +About [testing a package](https://r-pkgs.org/testing-basics.html). ## Documentation diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index 4021cd03..cf434e2f 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -69,6 +69,10 @@ golem::add_shinyserver_file() ### Docker +#### without using {renv} + + + ```{r} # If you want to deploy via a generic Dockerfile golem::add_dockerfile() @@ -80,3 +84,75 @@ golem::add_dockerfile_shinyproxy() golem::add_dockerfile_heroku() ``` +#### using {renv} + + +#### CASE 1 : you didn't use renv during developpment process + + +> this functions will create a "deploy" folder containing : + +```{txt} +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file + + +```{r} +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy") + +``` + +If you would like to use {renv} during developpement, you can init a renv.lock file with + +```{r} +attachment::create_renv_for_dev(dev_pkg = c("renv", "devtools", "roxygen2", + "usethis", "pkgload", "testthat", "remotes", "covr", "attachment", + "pak", "dockerfiler","golem")) +``` +an activate {renv} with + +```{r} +renv::activate() +``` + + + + + +#### CASE 2 : you already have a renv.lock file for your project + + +```{r} + +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy",lockfile = "renv.lock") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy",lockfile = "renv.lock") + + +``` + +> this functions will create a "deploy" folder containing : + +```{txt} +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file \ No newline at end of file From 210db93161fd17b7d68ce097de716d6972fa365a Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 8 Aug 2022 21:28:52 +0200 Subject: [PATCH 015/190] feat: add support for docker & {renv} (#882) (#889) Co-authored-by: Colin Fay Co-authored-by: vincent guyader --- DESCRIPTION | 8 +- NAMESPACE | 3 + NEWS.md | 11 +- R/add_dockerfiles.R | 417 +++++++++++++++-------- R/add_dockerfiles_renv.R | 331 ++++++++++++++++++ R/utils.R | 4 +- README.Rmd | 2 +- README.md | 64 ++-- inst/shinyexample/dev/02_dev.R | 1 + inst/shinyexample/dev/03_deploy.R | 6 +- man/dockerfiles.Rd | 106 +++++- man/document_and_reload.Rd | 2 +- man/figures/logo.png | Bin 103244 -> 0 bytes tests/testthat/helper-config.R | 13 +- tests/testthat/test-add_deploy_helpers.R | 52 --- tests/testthat/test-renv_stuff.R | 33 ++ vignettes/a_start.Rmd | 4 +- vignettes/b_dev.Rmd | 4 +- vignettes/c_deploy.Rmd | 76 +++++ 19 files changed, 884 insertions(+), 253 deletions(-) create mode 100644 R/add_dockerfiles_renv.R delete mode 100644 man/figures/logo.png create mode 100644 tests/testthat/test-renv_stuff.R diff --git a/DESCRIPTION b/DESCRIPTION index 5117b403..eb60aa0e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.2.9004 +Version: 0.3.3 Authors@R: c(person(given = "Colin", family = "Fay", @@ -66,7 +66,7 @@ Imports: Suggests: covr, devtools, - dockerfiler (>= 0.1.4), + dockerfiler (>= 0.2.0), knitr, pkgload, pkgbuild, @@ -82,11 +82,11 @@ Suggests: testthat, tools, withr, - attachment + attachment (>= 0.2.5) VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.0 diff --git a/NAMESPACE b/NAMESPACE index 468dedd2..d16aa169 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,9 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) +export(add_dockerfile_with_renv) +export(add_dockerfile_with_renv_heroku) +export(add_dockerfile_with_renv_shinyproxy) export(add_fct) export(add_html_template) export(add_js_file) diff --git a/NEWS.md b/NEWS.md index 4fc05971..13a672bb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. -# golem 0.3.2.9000+ +# golem 0.3.3.9000+ ### Soft deprecated @@ -25,6 +25,15 @@ ## Internal changes +# golem 0.3.3 + +## New functions + ++ `add_dockerfile_with_renv()`, `add_dockerfile_with_renv_heroku()` and `add_dockerfile_with_renv_shinyproxy()` build Dockerfiles that rely on `{renv}` + +### Soft deprecated + ++ `add_dockerfile`, `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` now recommend to switch to their `_with_renv_` counterpart # golem 0.3.2 (CRAN VERSION) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index a94e9c00..df3bb111 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -1,7 +1,18 @@ +talk_once <- function(.f, msg = "") { + talk <- TRUE + function(...) { + if (talk) { + talk <<- FALSE + cat_red_bullet(msg) + } + .f(...) + } +} + #' Create a Dockerfile for your App #' -#' Build a container containing your Shiny App. `add_dockerfile()` creates -#' a generic Dockerfile, while `add_dockerfile_shinyproxy()` and +#' Build a container containing your Shiny App. `add_dockerfile()` and `add_dockerfile_with_renv()` creates +#' a generic Dockerfile, while `add_dockerfile_shinyproxy()`, `add_dockerfile_with_renv_shinyproxy()` and #' `add_dockerfile_heroku()` creates platform specific Dockerfile. #' #' @inheritParams add_module @@ -9,7 +20,12 @@ #' @param path path to the DESCRIPTION file to use as an input. #' @param output name of the Dockerfile output. #' @param from The FROM of the Dockerfile. Default is -#' FROM rocker/r-ver:`R.Version()$major`.`R.Version()$minor`. +#' +#' FROM rocker/verse +#' +#' without renv.lock file passed +#' `R.Version()$major`.`R.Version()$minor` is used as tag +#' #' @param as The AS of the Dockerfile. Default it NULL. #' @param port The `options('shiny.port')` on which to run the App. #' Default is 80. @@ -18,13 +34,12 @@ #' @param sysreqs boolean. If TRUE, the Dockerfile will contain sysreq installation. #' @param repos character. The URL(s) of the repositories to use for `options("repos")`. #' @param expand boolean. If `TRUE` each system requirement will have its own `RUN` line. -#' @param open boolean. Should the Dockerfile be open after creation? Default is `TRUE`. +#' @param open boolean. Should the Dockerfile/README be open after creation? Default is `TRUE`. #' @param build_golem_from_source boolean. If `TRUE` no tar.gz is created and #' the Dockerfile directly mount the source folder. #' @param update_tar_gz boolean. If `TRUE` and `build_golem_from_source` is also `TRUE`, #' an updated tar.gz is created. #' @param extra_sysreqs character vector. Extra debian system requirements. -#' Will be installed with apt-get install. #' #' @export #' @rdname dockerfiles @@ -40,10 +55,28 @@ #' if (interactive()) { #' add_dockerfile() #' } +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock", # uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } #' # Add a Dockerfile for ShinyProxy #' if (interactive()) { #' add_dockerfile_shinyproxy() #' } +#' +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem with ShinyProxy using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock",# uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } +#' #' # Add a Dockerfile for Heroku #' if (interactive()) { #' add_dockerfile_heroku() @@ -55,7 +88,7 @@ add_dockerfile <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -71,56 +104,96 @@ add_dockerfile <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(path_file(where)) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, + port = port, + host = host, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} + +add_dockerfile_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + check_is_installed("dockerfiler") - dock$EXPOSE(port) + required_version("dockerfiler", "0.1.4") - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", - port, - host, - read.dcf(path)[1] + where <- path(pkg, output) + + usethis::use_build_ignore(path_file(where)) + + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs ) - ) - dock$write(output) + dock$EXPOSE(port) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + read.dcf(path)[1] + ) + ) + + dock$write(output) + + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv() instead." +) #' @export #' @rdname dockerfiles @@ -130,7 +203,7 @@ add_dockerfile_shinyproxy <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -144,50 +217,84 @@ add_dockerfile_shinyproxy <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_shinyproxy_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$EXPOSE(3838) - dock$CMD(sprintf( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", - read.dcf(path)[1] - )) - dock$write(output) +add_dockerfile_shinyproxy_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + check_is_installed("dockerfiler") + required_version("dockerfiler", "0.1.4") + where <- path(pkg, output) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + usethis::use_build_ignore(output) + + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) + + dock$EXPOSE(3838) + dock$CMD(sprintf( + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", + read.dcf(path)[1] + )) + dock$write(output) + + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - alert_build( - path, - output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path, + output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile_shinyproxy() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_shinyproxy() instead." +) #' @export #' @rdname dockerfiles @@ -197,7 +304,7 @@ add_dockerfile_heroku <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -211,80 +318,116 @@ add_dockerfile_heroku <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_heroku_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", - read.dcf(path)[1] +add_dockerfile_heroku_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + check_is_installed("dockerfiler") + required_version("dockerfiler", "0.1.4") + where <- path(pkg, output) + + usethis::use_build_ignore(output) + + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs ) - ) - dock$write(output) - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + read.dcf(path)[1] + ) + ) + dock$write(output) - apps_h <- gsub( - "\\.", - "-", - sprintf( - "%s-%s", - read.dcf(path)[1], - read.dcf(path)[1, ][["Version"]] + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source ) - ) - cat_rule("From your command line, run:") - cat_line("heroku container:login") - cat_line( - sprintf("heroku create %s", apps_h) - ) - cat_line( - sprintf("heroku container:push web --app %s", apps_h) - ) - cat_line( - sprintf("heroku container:release web --app %s", apps_h) - ) - cat_line( - sprintf("heroku open --app %s", apps_h) - ) - cat_red_bullet("Be sure to have the heroku CLI installed.") - cat_red_bullet( - sprintf("You can replace %s with another app name.", apps_h) - ) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + read.dcf(path)[1], + read.dcf(path)[1, ][["Version"]] + ) + ) + + cat_rule("From your command line, run:") + cat_line("heroku container:login") + cat_line( + sprintf("heroku create %s", apps_h) + ) + cat_line( + sprintf("heroku container:push web --app %s", apps_h) + ) + cat_line( + sprintf("heroku container:release web --app %s", apps_h) + ) + cat_line( + sprintf("heroku open --app %s", apps_h) + ) + cat_red_bullet("Be sure to have the heroku CLI installed.") + cat_red_bullet( + sprintf("You can replace %s with another app name.", apps_h) + ) + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - usethis::use_build_ignore(files = output) - return(invisible(dock)) -} + usethis::use_build_ignore(files = output) + return(invisible(dock)) + }, + " +golem::add_dockerfile_heroku() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_heroku() instead. +" +) alert_build <- function( path, diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R new file mode 100644 index 00000000..80fb9d61 --- /dev/null +++ b/R/add_dockerfiles_renv.R @@ -0,0 +1,331 @@ +add_dockerfile_with_renv_ <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + FROM = "rocker/verse", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + update_tar_gz = TRUE + # build_golem_from_source = TRUE, +) { + check_is_installed("renv") + check_is_installed("dockerfiler") + required_version("dockerfiler", "0.2.0") + check_is_installed("attachment") + + # Small hack to prevent warning from rlang::lang() in tests + # This should be managed in {attempt} later on + x <- suppressWarnings({ + rlang::lang(print) + }) + + dir.create(output_dir) + + # add output_dir in Rbuildignore if the output is inside the golem + if (normalizePath(dirname(output_dir)) == normalizePath(source_folder)) { + usethis::use_build_ignore(output_dir) + } + + if (is.null(lockfile)) { + lockfile <- attachment::create_renv_for_prod(path = source_folder, output = file.path(output_dir, "renv.lock.prod")) + } + + file.copy(from = lockfile, to = output_dir) + + socle <- dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + repos = repos, + AS = AS, + sysreqs = sysreqs, + expand = expand, + extra_sysreqs = extra_sysreqs + ) + + socle$write(as = file.path(output_dir, "Dockerfile_base")) + + my_dock <- dockerfiler::Dockerfile$new(FROM = paste0(golem::get_golem_name(), "_base")) + + my_dock$COPY("renv.lock.prod", "renv.lock") + + my_dock$RUN("R -e 'renv::restore()'") + + if (update_tar_gz) { + old_version <- list.files(path = output_dir, pattern = paste0(golem::get_golem_name(), "_*.*.tar.gz"), full.names = TRUE) + # file.remove(old_version) + if (length(old_version) > 0) { + lapply(old_version, file.remove) + lapply(old_version, unlink, force = TRUE) + cat_red_bullet( + sprintf( + "%s were removed from folder", + paste( + old_version, + collapse = ", " + ) + ) + ) + } + + if ( + isTRUE( + requireNamespace( + "pkgbuild", + quietly = TRUE + ) + ) + ) { + out <- pkgbuild::build( + path = ".", + dest_path = output_dir, + vignettes = FALSE + ) + if (missing(out)) { + cat_red_bullet("Error during tar.gz building") + } else { + cat_green_tick( + sprintf( + " %s created.", + out + ) + ) + } + } else { + stop("please install {pkgbuild}") + } + } + + # we use an already built tar.gz file + my_dock$COPY( + from = + paste0(golem::get_golem_name(), "_*.tar.gz"), + to = "/app.tar.gz" + ) + my_dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\",upgrade=\"never\")'") + my_dock$RUN("rm /app.tar.gz") + my_dock +} + +#' @param source_folder path to the Package/golem source folder to deploy. +#' default is current folder '.' +#' @param lockfile path to the renv.lock file to use. default is `NULL` +#' @param output_dir folder to export everything deployment related. +#' @param distro One of "focal", "bionic", "xenial", "centos7", or "centos8". +#' See available distributions at https://hub.docker.com/r/rstudio/r-base/. +#' @param dockerfile_cmd What is the CMD to add to the Dockerfile. If NULL, the default, +#' the CMD will be `R -e "options('shiny.port'={port},shiny.host='{host}');{appname}::run_app()\` +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +add_dockerfile_with_renv <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL +) { + base_dock <- add_dockerfile_with_renv_( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz + ) + if (!is.null(port)) { + base_dock$EXPOSE(port) + } + if (is.null(dockerfile_cmd)) { + dockerfile_cmd <- sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + golem::get_golem_name() + ) + } + base_dock$CMD( + dockerfile_cmd + ) + base_dock + base_dock$write(as = file.path(output_dir, "Dockerfile")) + + out <- sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s . +docker build -f Dockerfile --progress=plain -t %s . +docker run -p %s:%s %s +# then go to 127.0.0.1:%s", + paste0(golem::get_golem_name(), "_base"), + paste0(golem::get_golem_name(), ":latest"), + port, + port, + paste0(golem::get_golem_name(), ":latest"), + port + ) + + cat(out, file = file.path(output_dir, "README")) + + open_or_go_to( + where = file.path(output_dir, "README"), + open_file = open + ) +} + +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_shinyproxy <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = 3838, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = open, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"", + golem::get_golem_name() + ) + ) +} + +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_heroku <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = NULL, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = FALSE, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + golem::get_golem_name() + ) + ) + + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + golem::get_golem_name(), + golem::get_golem_version() + ) + ) + + readme_output <- file.path(output_dir, "README") + + write_there <- function(...) { + write(..., file = readme_output, append = TRUE) + } + + write_there("From your command line, run:\n") + + write_there( + sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s .", + paste0(golem::get_golem_name(), "_base") + ) + ) + + write_there( + sprintf( + "docker build -f Dockerfile --progress=plain -t %s .\n", + paste0(golem::get_golem_name(), ":latest") + ) + ) + + write_there("Then, to push on heroku:\n") + + write_there("heroku container:login") + write_there( + sprintf("heroku create %s", apps_h) + ) + write_there( + sprintf("heroku container:push web --app %s", apps_h) + ) + write_there( + sprintf("heroku container:release web --app %s", apps_h) + ) + write_there( + sprintf("heroku open --app %s\n", apps_h) + ) + write_there("> Be sure to have the heroku CLI installed.") + + write_there( + sprintf("> You can replace %s with another app name.", apps_h) + ) + + # The open is deported here just to be sure + # That we open the README once it has been populated + open_or_go_to( + where = readme_output, + open_file = open + ) +} diff --git a/R/utils.R b/R/utils.R index 92f2fdff..6e368690 100644 --- a/R/utils.R +++ b/R/utils.R @@ -17,8 +17,8 @@ darkgrey <- function(x) { } #' @importFrom fs dir_exists file_exists -dir_not_exist <- Negate(dir_exists) -file_not_exist <- Negate(file_exists) +dir_not_exist <- Negate(fs::dir_exists) +file_not_exist <- Negate(fs::file_exists) #' @importFrom fs dir_create file_create create_if_needed <- function( diff --git a/README.Rmd b/README.Rmd index c06f5466..ad948985 100644 --- a/README.Rmd +++ b/README.Rmd @@ -88,7 +88,7 @@ This package is part of a series of tools for Shiny, which includes: These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - -- +- - - diff --git a/README.md b/README.md index 8ceefc47..19bddce1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ - [![Lifecycle: @@ -24,33 +23,33 @@ You’re reading the doc about version : ``` r desc::desc_get_version() -#> [1] '0.3.2' +#> [1] '0.3.3' ``` ## Tool series This package is part of a series of tools for Shiny, which includes: - - `{golem}` - - - `{shinipsum}` - - - `{fakir}` - - - `{shinysnippets}` - +- `{golem}` - +- `{shinipsum}` - +- `{fakir}` - +- `{shinysnippets}` - ## Resources ### The Book : - - - - [paper version of the book “Engineering Production-Grade Shiny +- +- [paper version of the book “Engineering Production-Grade Shiny Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) ### Blog posts : *Building Big Shiny Apps* - - Part 1: +- Part 1: - - Part 2: +- Part 2: [*Make a Fitness App from @@ -58,34 +57,34 @@ scratch*](https://towardsdatascience.com/production-grade-r-shiny-with-golem-pro ### Slide decks - - useR\! 2019 : [A Framework for Building Robust & Production Ready +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://github.com/VincentGuyader/user2019/raw/master/golem_Vincent_Guyader_USER!2019.pdf) - - ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with +- ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with {golem}](https://speakerdeck.com/colinfay/production-grade-shiny-apps-with-golem) - - rstudio::conf(2020) : [Production-grade Shiny Apps with +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) - - barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps +- barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps with {golem}](https://www.barcelonar.org/presentations/BarcelonaR_Building_Production_Grade_Shiny_Apps_with_golem.pdf) ### Video - - [{golem} and Effective Shiny Development +- [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) - - [Hands-on demonstration of +- [Hands-on demonstration of {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) - - useR\! 2019 : [A Framework for Building Robust & Production Ready +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://youtu.be/tCAan6smrjs) - - 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) - - rstudio::conf(2020) : [Production-grade Shiny Apps with +- 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) - - 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec +- 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) ### Cheatsheet - - [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) +- [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) ### Examples apps @@ -93,31 +92,27 @@ These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - - - - - - - - +- +- +- +- You can also find apps at: - - - - +- +- ## Installation - - You can install the stable version from CRAN with: - - +- You can install the stable version from CRAN with: ``` r install.packages("golem") ``` - - You can install the development version from +- You can install the development version from [GitHub](https://github.com/Thinkr-open/golem) with: - - ``` r # install.packages("remotes") remotes::install_github("Thinkr-open/golem") @@ -125,8 +120,7 @@ remotes::install_github("Thinkr-open/golem") ## Launch the project -Create a new package with the project -template: +Create a new package with the project template: diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index 82478726..67699ea7 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -15,6 +15,7 @@ ## Dependencies ---- ## Amend DESCRIPTION with dependencies read from package code parsing +## install.package('attachment') # if needed. attachment::att_amend_desc() ## Add modules ---- diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index 464d59fd..2f9595ef 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -33,10 +33,8 @@ golem::add_shinyserver_file() ## Docker ---- ## If you want to deploy via a generic Dockerfile -golem::add_dockerfile() +golem::add_dockerfile_with_renv() ## If you want to deploy to ShinyProxy -golem::add_dockerfile_shinyproxy() +golem::add_dockerfile_with_renv_shinyproxy() -## If you want to deploy to Heroku -golem::add_dockerfile_heroku() diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index bbeb97b4..53129521 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,16 +1,19 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_dockerfiles.R +% Please edit documentation in R/add_dockerfiles.R, R/add_dockerfiles_renv.R \name{add_dockerfile} \alias{add_dockerfile} \alias{add_dockerfile_shinyproxy} \alias{add_dockerfile_heroku} +\alias{add_dockerfile_with_renv} +\alias{add_dockerfile_with_renv_shinyproxy} +\alias{add_dockerfile_with_renv_heroku} \title{Create a Dockerfile for your App} \usage{ add_dockerfile( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", @@ -27,7 +30,7 @@ add_dockerfile_shinyproxy( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -42,7 +45,7 @@ add_dockerfile_heroku( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -52,6 +55,54 @@ add_dockerfile_heroku( build_golem_from_source = TRUE, extra_sysreqs = NULL ) + +add_dockerfile_with_renv( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL +) + +add_dockerfile_with_renv_shinyproxy( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) + +add_dockerfile_with_renv_heroku( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -61,7 +112,12 @@ add_dockerfile_heroku( \item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{from}{The FROM of the Dockerfile. Default is -FROM rocker/r-ver:\code{R.Version()$major}.\code{R.Version()$minor}.} + +\if{html}{\out{

}}\preformatted{FROM rocker/verse + +without renv.lock file passed +`R.Version()$major`.`R.Version()$minor` is used as tag +}\if{html}{\out{
}}} \item{as}{The AS of the Dockerfile. Default it NULL.} @@ -77,7 +133,7 @@ Default is 0.0.0.0.} \item{expand}{boolean. If \code{TRUE} each system requirement will have its own \code{RUN} line.} -\item{open}{boolean. Should the Dockerfile be open after creation? Default is \code{TRUE}.} +\item{open}{boolean. Should the Dockerfile/README be open after creation? Default is \code{TRUE}.} \item{update_tar_gz}{boolean. If \code{TRUE} and \code{build_golem_from_source} is also \code{TRUE}, an updated tar.gz is created.} @@ -85,15 +141,27 @@ an updated tar.gz is created.} \item{build_golem_from_source}{boolean. If \code{TRUE} no tar.gz is created and the Dockerfile directly mount the source folder.} -\item{extra_sysreqs}{character vector. Extra debian system requirements. -Will be installed with apt-get install.} +\item{extra_sysreqs}{character vector. Extra debian system requirements.} + +\item{source_folder}{path to the Package/golem source folder to deploy. +default is current folder '.'} + +\item{lockfile}{path to the renv.lock file to use. default is \code{NULL}} + +\item{output_dir}{folder to export everything deployment related.} + +\item{distro}{One of "focal", "bionic", "xenial", "centos7", or "centos8". +See available distributions at https://hub.docker.com/r/rstudio/r-base/.} + +\item{dockerfile_cmd}{What is the CMD to add to the Dockerfile. If NULL, the default, +the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}');\{appname\}::run_app()\\}} } \value{ The \code{{dockerfiler}} object, invisibly. } \description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a generic Dockerfile, while \code{add_dockerfile_shinyproxy()} and +Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} creates +a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} and \code{add_dockerfile_heroku()} creates platform specific Dockerfile. } \examples{ @@ -102,10 +170,28 @@ a generic Dockerfile, while \code{add_dockerfile_shinyproxy()} and if (interactive()) { add_dockerfile() } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock", # uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} # Add a Dockerfile for ShinyProxy if (interactive()) { add_dockerfile_shinyproxy() } + +# Crete a 'deploy' folder containing everything needed to deploy +# the golem with ShinyProxy using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock",# uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} + # Add a Dockerfile for Heroku if (interactive()) { add_dockerfile_heroku() diff --git a/man/document_and_reload.Rd b/man/document_and_reload.Rd index 8d0c84ec..95b60561 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -24,7 +24,7 @@ which defaults to \code{c("collate", "namespace", "rd")}.} \item{load_code}{A function used to load all the R code in the package directory. The default, \code{NULL}, uses the strategy defined by -the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load]{load_pkgload()}}. +the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load_pkgload]{load_pkgload()}}. See \link[roxygen2]{load} for more details.} \item{clean}{If \code{TRUE}, roxygen will delete all files previously diff --git a/man/figures/logo.png b/man/figures/logo.png deleted file mode 100644 index a149d47866c88809bce0d6dd26bc354b516d498b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103244 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ቛ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

Iw`cPY_ozCrlIKG8yo#XoSbV&&wTQN z38x{aoH%)s5@saG_oGSnV}Fb5t-{IeHk7jp_!nY~u3ou((Z5Sdc6ZRdjx{?3oSbX2 zZjivxSobX$$}ko~t6a{Xapf3T>AwMu?APd40^=M*)3KpPa;gQ55u!|UWAuA_TZ>eb z@0Wx7cd0W)@bwfdHgUKiOTwo4A5Tk`D@G>3YImaO3o066isR!!-69zJE@0^2K-wBA zRKsErnE*xp_vD=_N1fl%O-tZpB|yyq4l`_;y$VA=01TZ~0v-c~o;O4=^k|&C4*vBw z4z$SiYnVQ3)+eX;tGY*ha`LWc=9s+eT$lPZpB!aPx!!^0Z8O&2S!?ms!fD!ohnx!v3gcFBmqpayxv#hByv{Z-mQ#T1{^QN0P zj`E^MecW?rTz)I2_H0bL5twxR5qFE0!WWA92we1}klnv`DC3Ku~ z51x-fO2B+Z6-kMI5e>gSFZ0yLgHFE>7Xf_TX$StT0yi)zEzvLvU&l^4CN3VB7XT}c^E@y zlzR+%-ik*)^3N{@hOQ^CB|;yz2aQCzM~G!u|NIJXog@rG44r#;;^Yi^JJ5#pU~j$5 ze78#0gWDn>n}2*PRmdj~5jliGz?MI5s*Ila35ieaL*8}M)n+(A&Vh7cyAce$X0wOB zwtDHJ$1C=1YomLeIJp_Pth%Y6jv;};v$h}OBKr8F57WpRav$mdd;y+s>}cVyRJ{Xx zWbGIA8Fp-T%#P8q?R0FXgN|)m72E2#V_O}w;|eOaZB0G@nd_aIZ*Z=v`#yW`wSH?K zwrV#Uv*=`2$GDo>xCgEZK5Ytf$+c8?k)JVrZ)CswRQ{l+J%6zF_F1*tabi9cHeNRf&BNM_$szZMX#~&tQP2+THNWc-u z<)z zBt@>C(jDuN{Y8+oVLwd>LL&imL+afjpH1szKMes;pC>kcW#XR(s(Q%#@&x2|K3#1u ze23dewkiaYjlX+oA31?-L(d4Hcn{$@&Nb?YynKR{L9I-57VM7yC+?AOP0YAxNlgz|vM<0+&bd8*ZKzMy( zDcw8w@lxD5A&A>;+h+=xXUV@_unOBS8eo(7H<6P1z*>*oPKw$Nf~yldL=>qMP+fAh zP)mRF(yM;k>gHRCrJx@iCyJ@i>|L-+0{c8O~EG%HPE{z*_VZ(Z4hJ+)S5 z1J)VLjWXNmcaw>KBNvDH8n5JOfBZ;}d^!E$=F`|Be{g0e#>@|OY+PODJz3E=Ds(Dn zk5NH$A@m(FqE`Q}Md=ca zVC8B`cId*jeJfZgwJC-OW;{r!C5qppxIFaw9s~c`6ZD1@ReNcjWEt6=pGM_f8}Nh_^oyqveSelt7m+BG{tfDmbnYdpC%A?# z{sl3m&$pWqE>+TlYOWB_TrYRAvQ$jgoTz-2s=UV(F*g$&sjZ}TabML1&Tr-}FPxtPa|iHrzTqFQjhCW474fy2 z@g`Ui=dRRwqULpj0rZ;5(6nvy{|z%*EnL<^ml=7x|7LlKH?d}Bpiya(H32LA*4GE@ z)H^~@KIpt|!&bSlXloapoqwdoQto~KPUUD8W++U?=xSegtb1tc;FA7kC!GrlJ-R=i z`ldMDP>PJT&zwW({i{Dlv~O+ndvd}_mNC6^2hizLd$q1_`$-gw1}_@}QRp0fL`##& zs1ANuv9D=7!bnF=-kikEv715)p{VsU2FAk?lf|H;2}{x_EuJysjG{;uaFmy=i^Z2??)UE9?P@wT0Kv0tp<0Z3M1{*2ql{}h#>W)X8A!{WV;GAyp& z@v)^PW;9kY7+E@1E2 zsn)O8JQzCkLw>C<>x&Sn5_rCLnpb0epRwxk&Q0irc72wASxR*~6b)1*ZJ{_E)iio@PgHm!)p z69mEfpqfS5j-`eI3<;seRRQN@Nt@GLuY=(G80549vW6&qPk|3~Jl^F!c2CO1ay9 zQGsfjSrcQ{u&ah;kH&r3_ag%TUy5a`g$)hJ?D;jUhaICaNThOdcEluH2eKTs|B~E(b8ABmOa7P5{STeh z?qRnMwNlL{Yy&v3zRA(>b7)#)VE;NeRe2s_G3rLK8;9RWpY{+Tme@(FHTT7xu{r7v9w~uih7^*SC_Lpo$q&?0Xv~ zIotuLVVg?sc-PuTMjP+*6YG^w)iFk!@ffu<&e&2LVnL^@7y@pc9f*?ti85?~YG?WE zsN~9q(x%UC1W#sc)?XATc7{&ZEzGgWdi3w6B%qx{;e+=3h_+kSXX2mfT{#}JIBNCV zJ!tsqn=_-~(t%!os^HfG|FR5q3PF5VKoaV&20Z3Ej#-EXNh9|9vj3wI&Pbt9OYRzf zkF#sn6|TyVNffvv9u@y2C^x#Dt>msRU;FE8JxkTv`Sy+ix++ti{nN)=!;QEheutBD`@Dww~Wfz)M167%@j21+jt^? zQ9Z)JAv}4DlWOO@3CDT!>wdeT)I8Eog~zL{%F5Y0ZZD32#tGq0i+Ff!F|O}VT-FHn z7ZN%fT`QQFmCAG`?@&^!91!-5i02aXV-PFi5%l%FCBe$=w7@+n%G3uJsL?{a{xz`n z_M_}uBeIc2g#@TxuAAbfZ^;5osI-4pI8Xj*kw9SNE^!as)gUwAFPvs-5T2TfEnT-uYl+I7fqA z+y39t{2FoGfX*d0`Edzb;CNSCh)}U3V&DqmComO&C}{DL?s6X2w?4DW%_c{T+xfM_ zI?N<`_Sw-S7R8s^A8f|GeGjo{M_b6l=(3 z^P_V_yt$#z&%E$FzujNpb^x45_6-lMq)_dV92Im~!RPHR4kpgBuxM3Rp9N;<+I~mh zoIn@u%~l?*Y1~6ob2ORSuI~i=z7#sv9ot)utpO)a=_nhNm0=(CsPY9cmsk1I*KcX% z5~YuHe~Q?01@~W8>GT|J%L=F;9sAzosS=XaG7Xr+jozai*n|6;m51Ybr1P)| z+?v6mAHs(&TK*Hb$m`H}%jFRAwaFR7&c5qkRI`4x?If49oD^W6FE67Q#fRCj5ibn| zjPqU5rt4r~?X7+v1g;k(aNYuyNQ~kcqg)j-vSa%N+Bz;ZWgxOtX7lI|mt^w;5i{bG zt#T?rgzQ_fJ*ST+e0$r(%{P~GsBYT2`}=)kA$8-K_2U%uPEi$r?O0!oyOhM|*lLHH zF|_SHPJha)Q%-p!Uw9hSDXEV0rD{Dp8Szu%?LQ22O5IH0qf*JE(o(ab$>|JuuJVe&E8WNtwQ;X zs*83AYUttWYlX9pVOPu*U+C;_2v^_@p>w{y;tIV78*Ep37WoGbqPL*W_N>X2? z(+UN|gdhQS*53vxiSln01PfzHdUuMkJz8BgKi$ zYh_|9y7H8S?zf4fe~)iuOyshtDCsvK@-~)01>T^jp#}_H{%kVx)nMrJn_oJOdqX@L zlC9cR_Ub440`U!?Kbyqb$)R;bG!?U(HU{fkqi#y zn%A8Y-*f60nnCyf8TBcEV3)`qInCn5%au?9Gg8jQ1-uer$55Hk&=f{IJ<#3>8b7-( zlA2j4r4A~t~7 zAIIzQ=+jy8M)Z;I(+|$dlDmpF{d7s4K?yS|YkUj4ys=+CkTCG#ey@o!)A_)Hid0fv zpuAN?KKD_Rl-4DU)0~roCK%{}5BaI3$ztGn{D&LQu9!ml@qD{EJpK0?Sz_Su4BVK= z{ZVNYxKCReY~JWQbjab~z0s?OnflTr1eG)XhTsQC^ZKctfST5|*Ysn{e+&!;_4N3O z;~TZgBM5|J_`d6Go&E!RXG6s>&%;N>_Rpsy&I2x@u~oEh+=>S1pC)4Q_5+$w>VPkd z*EN{e8}CNf`Y#&Ph@asanARa9qp5(XkmJjk^qKxC6L~kl;|Ja#`z~8o#B$tkw837F zsO33lCnBtWaieT(n-sOewc721keS=5)2YjZP+~K&eD+iVu=ti}C$`t0WEtMov zyQe~oo>b$;K6{hu&nEU=#iYD}#vZ(%Wq3U@GAe!Wahz%Ty^iD4U z61CQC3dreS#s{%$gC3V_JAAiL2(|%6d?cM(G1Rw1T;Ie8(^1XvgQ($J0Jr1$(U;-* zk%DvzQ2gBThkx5mY<*z?IUTXLBgi7;;fBP+GnGG|%Obwt-I*F6sqaZ-$e_-%qcD-; zdO`QhM5@b|=FCx8oR)EL-~TJtD?*h%s_{r?9#zV? zcWmqz9<5}}quCt1YC`YE;!5d;#RuOx`I3>v$4ld=k#NBc(4f3`Logs$z7$JSHFZ1nP+ic~ z4gMKWYl!(;FV(z)^bQKZ>?0Bv(JL>N6WhSp5%LHRO`-wRpTW}JV%C!z%(?YfW9oOS zcC~IWx$il(PQL(P@?Oi96$RyOd(3{Q&FjYXd|{`Zu4<72Nd1*ee}o6@o|RhU@bDfU zep46kv`|ZZ+X<1Xki5qpj#U>EY$?Cv9nONG#=WNAO;E}kv$mv-Tu8UFMEvH_akf>o zipLh*N^x*tu|L!~UOpNfU7NnM)2@Edfr5cx873t0h3$o{eEBt2iVK9nY^Hx@VuD5G zCeh+)x>Hj4B@Ih+038$`tBt)|u-sUL_LJ5#v9hZ<$@q3a^pDfskrSzAK+%^rCSIkO)(^_*V%&FO7)i-Z*~vRLUIv}Lh@UESvkSJ^+m*Gbl%~`K2U%cdgI8@0p)9< zk&pNou!h#x9&7V(^=h1@g8|^7ci4Tiz_z8DWkedWeQPys{J5DtR)nLwtno zT@Ku&hkbXYKfpi~-=wa1K=n1z()1$!Q`oJ!!N^@xJya{61f5*2Mt5_;HOh3svu)0_#AY#j?jeNEq$)t%_PeYnN3=?ll0dqz z!I{_Y(B~v&q~|}$J|dUJPHx?!YBC1z3Q8QS^ebjj;W;rS_pjxW_URtreSKx84Esu* z*%~J@Tn7xty8K|Q>DW+7ZYrf7Y_((PP>Qx640n~)BRBH+lDZ0;)^`6cu`}L94%hxq zM@e-@@ON2k2n(QJZ|$CSt+nY`GE9De5K7QIg7$RuZnP;i$+ycFG9Y3|>+R{a-HdBr zDWbt9$Mhrs`(nyXs<#gaJr}S&c~w_#R|DMKQN5K02K3i%x`OT6w5`8S78-!@o$72Zy@$N++{R<^lw zoM>`BlkJYxA*oE?l*9-(aP;~s5N*(cW!AOdjoLOU7Bs96$&ZOOk22ERh?9bnjlQbI z6TnvAyrP|`NR((kkRQb|IxxyDL_UvymAP=^uDh-$3m+RA{u|*7*3J|O-G zZb3L`T-ipTunq^Ub^@M7L(3=^3>0n$(&fT7 zh+M~iPjir;{sfyT3na8jnGE!1c7ExsD6!R;9`dx4H^U!$@!7#SH%jFV18@54VoSPv z0gAaUnNsMc7C4%- zXZRAfWksQIDG$|WZHL=H z;Y2J$NwHQ*kP8~Ph%4iyfC50b8$PrvkFX#3q9&9Qo)*W^bk?IN&hB(WmUBuwG5qWx zz~XPZl+}uNc@QSCIe+r^ZoV!luw^D<(b1!K*%B<3@7FG-;avI(`7B5%+l75IJK*1G^;_d3&j{d`7uBMSJ=QJTI z(S>F|%c>Q7vM2i}OKNN^(-&*fK1z*-lNz<_!X5P(K$2o?R~9;+CSF0QY9JY1RGEPn zyukSb9pR^a=vh?V980P)2nJzNI!jkdNdHp7L|#-z=9Cp}(xSwhxkf?39X?<#&X!@$S6ERx3;8&Au@^^?gPD z-#}oPZT~5d(ZDm{`NP0XeXlLwO=mvnyU%Mio^Ih|_7U84RH_M0tgUy)du;mQC1j5f z4NU-JWX(RKvCDPjOo>=o{2GN#Zp5K?STpaR4{sN>5SXn8v!cd8E75wglVud5+st<+ z)wLx$uTkFVE51ZUm?6aih=`q8S3()gtw`Smm3}h*0yB(m`}`}b>-G1hr<&&;o`zeh zx?R8TEnTsH=`*j=T=K`lFX!dTkc7O7@x%TBZrRy~{BY-%xw@%UJBcscY*E|1&i;Jq z{~-d(@0PR8#45QJ4#2aJA_0^A{IBIR ztcps8iVJAkfr+o82 z6X$qRF+BDYOQ`ol)IlBskg^Z{RxS#(OVe<68lj8ktvY|-Y)b?>6+PeH(B zTw%yd0+tNrQL&^hOdzX^>uD~MQyN!zZ1dz;m*OHY zabl61 z1k_O;TCiWod4wTQ%H5q9PC9{ z%wriu6<+39cB|gy`V9G46ozgMUpzhUn-E5eaFiw!6JYqFB_2J@;%zbQF$;WG@{QgI zW&5aV+Hezeyb6dqkGV}aGbrPNBkh2ozpdeOYz6+D?<3>sx~S61@OsyY7U6-2nAbRQBNYWn z)5`nxHXIO@9YQ`*)jV<7JIRhkBMpgDJ<3EH-tPl-?o8Tc3ZxOF=^^Pq9?yFkd2o6* z3MihSrCGdRafE%Tn{mR}v5rVx07z*}s%1QR%Xz2Cq4CAkuECs$=s%%8Z*NM!-v>Y& zIb0kifq2r*o^t5*v|*>Wl?Afw{m3C@vf6i2HFloI?RJEGI$!|qZ2wTmAWW8O|QEG0mZ+;MJ-|ZTBrn95Q zt=FyuiGbP6g?5CJ4I*602~@*;jva7W#mrj?htY(4bLP}H_U1)$;-+Zy-o=Icv6+mf z1(R4w8LQzns+oH6+m0?o{bQ#w#+FN9x@AMz|DCh*7iAAM@io&ogUXX6|2a&a-!M&L z!oRievS%^LTBdz9=fyGzML6C%d7qQ>hvjlYjhp9xDlGq8oN+d8{9!TYOkw{OE1k?L zlb03(ixQ$7dkLnU8mI}o^Z#|2r%$qTeZ`lU* zr-fVlTXnfcNkIt|0}C|ce96t2LR#y>q`+6<=N}6sqX>HYKXX8T5ug!RcR1h`F7}2Y z3HN%Jb02D}{cnd#oFCW6>GP3H@ZHedJ3ZgUuj&zuPW~N#U-Ew*RKu-=sxsP zbUuD~imIl4sab4jH~%u)F9-;0>8_Tf9}lceFXzzT54Y%34IejxLx6AjN(pj*p2)Ov z{;63-)%L@e7`XKSt=b{= zFQ)71QDR8~&&vmHmZ#;OUrE?ek{BKKWzr!;s^f;*_hMtyRmdV_YLB^A z?y@b!lAOS<1H$t)6szB*sw2x$vzKtGGu1kfO;DayR96ehCO$;$?o|{hM6DPE=B?_n z82OdyF9Dm8oSb*WG@2s5`e3-z#%5h1&;_!^i$|%^drVgdWne%~{!|t$sH(tN4(b6l zc(436B15STHe>}E1xpX^a>mdk^xR?nhp5ode}8|kgYljly+EEHU2QYV3t9YwW@+YB zYUxW{%uyw{zM#zzPU)9gC%{L5ce;~^h8CL6Gf5c8moIGx|A@DG#+PA@CtmMU9+WSip zu?W$f+s89ju*AgPW>Ee%(b{rT(0s-nvF5g@r(_(!y=u z*ejUHhxn|$zWx141JIcMl>C~>FTUJ{2e;fwlO{Q+J{K{6fxeXjM$VL=Gzty0_Tz}2&;22 z?kOTo@1M9CAM14sG3FyS{+e!9$^L|CVjkBLh&2JZ)G1`S%z(zpL-?D44Pwdr0n!kc z8$p$~)YvYE?-qg%z_HNBT)t7^qoYJSk1mLTGS*;2sk?5|v6Xw#h*+4WFd^IU)m0?U^(*>-_2-Kl z;Gcdj?ph;DGYoB*O9+Z>HH~e&;jPv_lf#>T5anNJFBsd_GHtj&jXN2o?M^}3G$i&4 zls=PmP(yZ2(YH`-gS*bu+Ap}+T=;8$_eE0|6$QGy8<(>x2i?}8cRKyzJel7++NRrO zp!8bhEH5<&o5R533XX|9DT4u7oYUvcKl-QY(_Ag5$@{+X#LM+a*HdV?I+j2-DM6Rl ziu`^L<`j`dQG^TMueiRrj3*GwkcE@q)2OQFwry z1P@Z?Q`zDneq(jWxw%D&*t1)V}t^SXooUB=KJb@I4YZ52J55a3gk4~OG7m})4+|O5=A(w5up{qjRwvOK zm=MoHfdz@N+QvN@p2ZaAQD|v8;!L2qt0afKXpc$UxLvvVPeR6(vZOZ1QwdQV$_#YHqtrOMjfnp$NSgD~mVB7PHF zVi2rww0Q^~fg=u`H%}`SH_tiSQj`SVrA0bG7oPSc?AST=?WvcC? zpZt+3UtQ4$Y;`G#(fo{pFCEa=+M>`)kd_>)2j5-z887*)y4xWjotH@`x#pL3FF)&_ zNIl_~c@z~SGPU7%FY1&cU$w&U;Q%G~uZI8z64NlhWfF^^$#o5CvJm}FCN^1`xn@>dp3=Iq}5GOp?yv3YZ2H>11j zh-w&5xYJVV89dCRn&sC_sg~w;mGQ^R#MN&o{9^{;cbn2%L7-HG&zw@5ED+VkY^JN zJ`cCDrJ0~1DI*VU>L9jNxHI=eQx-?VUP|=LsE`4OqB_?K5eUbS^%#KYN$gH+^eG9KlofELuJxdzAEm+ZiV*)I%& zY65O+C6&*C*h^mfJ`4aAomx@M!rNqwS7lVjV!{c+@q)N~=d|KqW)aoSr#c&C6xyJx z8h!(JGv|~b;)9mO3A~Rs*UjnoL@z_+v{p-%lip^SsuXaCp(&?D`YMizAIZ^h{IwwQ zen?JyD$&Ypv;6BHj)s{y_or4ucfe!WKY-0k>rn4gt%>|NN?EZ=;sDT&Z1Im-_HtGq z2$!}D5``^;E>csvK6UU10;8Xt*jjq@ZnBXm@eR_Jdw^E4Hx}qRQriN%3)<_5slfc) zHOl8Dz3(5tD%3=(3;2c({$*gjW;GhLScKlhl6}AEOs0scPx3W>7`mt6FbgN_ z7I?kTQFR<)@AEE2Dl5WpCxqD~q~L+zD(}MJ+YEUR{9iaHn0Bw8)eEuCa5c~m%VlqM zN}7fHHbEU?P+te!#OiOvEZa+|3kVidQB*`wXFx`?q8b?~&j#&~OeMv0w07aA4+cr4 ziMrfj>YXm0@5>9j^5|nDm8WP$+8m&zHro=37_V{_hf~K6w{BI^N)cogX@a-~V7OX^ zBmE}J`Vv2a@h3JhUNhMXVk~3|%!E^JUoma1upTXXq0EK9xQ!lWxQ&jTg=79{8C1b! z^o5jEXU2Nu&4=uLmCCWe!}hG=QMLi~-uu-PYcp(QV5qKa&?#NmrPS8&z^ApMmWDcq}eIHYqlTp}1{5URGqVmh;~+|`1b zLKv%*(8il>Oih}S=1_!8hwMVW_~M2JIJ~hL%7fDK^*o$=DB~ae1oDQA?O^;GHj$$-a;9uKG0a?j343Vyyw*u zkzuBr&qRcHd4Bm~?N#4+Kacq|H`GG=zInz6iloj8qA*c<#F&vYF@M8-5_lOb;ErZ; zc84}s2ujXAc05g>6#gF_Huy?FQ-P51Bx?#CN9@*$yUK+v&Axw8 z*S0ilb5fgQS6*aN$vPFKv>}r~P4l5#a0``kN3k?J43<qTAdv(`^ZU*L!GYQCRD$A@R-vJ`dE z+zPWT#M_*+JXc)2_-F6P(Ca>7@q3i;R_mxd={thD=Do!271Al(}giBn95`pMN z5F5Zw`Ol`Zc>jS#cFSuo(&9&8Np8eu5yvs2M5U3dAfSRw*YDhEDr~9#ZYDjwbxzW+QA?VvDOrQxnBa+!M6UP1v4k`D@kUx?)y35c z&{d`o-qWxh=GI)iII)oKUb)|0Ei|JQ)9av+f5#xmmqvAhEhtEsyk!p=p?V#rW9c&b zbbO6UyFostH4o0{RRi93BCRI<6E+!yg0xY>1w*gfjWc_4uE?%SEGJZVCk!8{M=`DB z2R+Dk7#*;y+P0(ef~_$N%I{GZ`;Il(d-Xl>PN44U8RuJ1Pm6+oPtT8wHB=LVa;5PL z2&wu3_l$ie$PB?E;A_IZ0-duhZi)|a*J*Za!8^ZA8cSPilc$GP&Vdxc$`{&m?Y3=MNPf zd7>X4xPG$YoZKZ$SJ=(cNBpu-y)8or@{aqbw64yOzbDH=f~wWU;EeD#FAXs^Tm2d1 z5@C0Yf&i8Zl9*L?xc?4oF?=@Yc0Pfh;YEvyYSWhY#VVM-Ha$Gk?@$yV6;^X@{4MwT zBe@yIjwCK!s9NDDL>rt)N>@O@``XSCM12f{EFhbIf1B9tCKsHaucR&jlsAcgyC3hi z*WE5NH3VAFRFc^(S=%HId}ngn#BDksr#?{BG-zXY~V;QF@T7xx`7XJaALk7v744( zF$v`Ua~AKUyhr1VhrDP~h0?HCW{0#=1=$XwcqlK2`2&TaWOwY9TK$0pYOjInuHXAw z@Gh&3rA*Es{|?`- zuhnxt1N!qv%koLK`gM~xkjJaJSkYeJ0-O}{w1J$^^FvE(1u~88@m=TD?cZPab+9@e=m;Y z)l}V)O`dHHw^G5={_RDR)a2@>C-LZlNxm`D=$7_xDOD!{%>$4jVdlg$8>PHBCiKQ5 z^))1@QDB4_{e0kQTJ3?*2B_sE7Z4gt9RG)(`#U24cwxDC;U**9DoO{w3UK>%$BJt8 z(sSYeUsYUsFFgkZSb+v6qwW4E$YXcT4UZ>IbF|_Yx0}-kCx27N6j$e9bnN6PWS6N) z5@_27auVLK6t{;NBQ#{`<(IS)Vppv+JR_=7~tib~+)6uK$6MEJ70zMV$WBG{t zos8Yo5D6)6OM{+Kf}AO(C}Ynl7Qg{pXj{jYg~TV+D&KwYc)0$_fFZx*$HBZj>JKf_ ze(7@)qsOi`(`0ir{S!2M=5*37ElQuYU~o3_#9bkhSLCfrj@QLRoWuS^Cn-DLSa6he z$1vVDi!p1T(FWiL{TmqDhq|EgxshcaeIY@hE2*7`bbxDc1x9d@`TpZ&j>3O<@K6Ln zic?lbDxAp`f>CUWxJd>Sx74~{+lJDETq_4j1zOU9P8a*uTce%XRR1g@Og!W%Kvy8| ztDl*o))CoV=G^55wOG*9%6bk024WBR*OP5fxmi?@x?6QgVO93FO>-DL7FFK*`uUx* z`pO*!6Qq5wPRk@e#|l!X)muwd7o?=uZyq`Z zHfqeZPsw^~=}(Jbl~^_}?6DieO68dUj~zix**fc6j&Z2<*%l^+ zhhnjyxMGV*cA+c>Yk37JBKe+gUrNke%iywMI(ARlYj{XyReRldi1W!`2^rlxw%dc6 zZKvn|nhh~o|I&2fSMJiY_EvW>&C)}{_QhHl)`?Qg^wZ7C$tR&Sq4w@y+@I9N+?b~hHhx+OCAs|=F9Z&u#NmFWFcHL>rM<}=Es zA^tqHuFplV$u@rf0125yxkz zrLv5wLs+4W*N7SV_1z5TinUeI&5wn|b0IeWg@8bxla&xvU$O1))MV0&iSdd}R{PR{ zLJaN_{ElMw>v}PaSX+URV zUuFAjDhLXuukX-xz#)X;6R@yUWAaf#xe?Mv;s7iSSt^cF)RvXEHWuPC3%+q|<`I3yQ~93Bj{Q^5`%%sl_O zlCSO9_EH`vUq-linEgetwxCqe$5n)A^=l0;M5Gi#74jzDgyG?Oeo)9Q3kLoEBFjAw z|MgMc_}008#G>zos~FMuROzCIGrF7urUOT%-i_h6a~Adw9({=tG|=fc#TxC67(Yko zH*|0|P8S^9n^fNeLel`v6_~P~U+UgzA?*vmIuSlNZ3{&rE6CSLYH3CZCb)mwRZltB zHObIV08+XXw3;AobCIq4G7I3L&FU*^Resd%GjpC@e)kQi7jg~PbAW#M) z#rxYN)J2?-zJxgkpgiVyf`UGQEWx{M;sEVQ55xD-C!py1^>M=o0d`44NA0!C>-LEl zTz$fe|EoS!5gJD75>TathLN@Wo9A!@E0OYKA-Q_$DLyHZZ*Nllmc(ESz@(&6Fg zTRIt>9{HmGVHLsC(_y_590mtI*%9lf9aR-sb}v@6@&!Qd4{;ssm(?$=Fuy3XyiS*@ zTOw24FJdMf!nU~xan~auRHtSvyLEcTV5_2azZt0N`6)lxLuVopY>G1rIyZ+TK@J%r z4^2Nr%OW_RA5ne9xfHwLvu+Z16YYIeG?mNlQ+3!3nya8T`4@^aJ?z?I9$m6aYJ|hz zW(?xRD(;$M`A6>Uhl|a-Mk@lV5dkPq*pDqlpeRz2psv0Il+)%AnLFgBkj}wstrGR? zRHk*^;l5%%OgsKi_<5u7->(mLZfvaZ|5D7QgD(fV0o`er{IAkbC_@`SXG6UvwMe`$nP z^!ULa-RFrGxo4%!%=~Ezdh;VBf=ue`r7e?N=wAPIG;q82_pg`C=sb^CQ^DQlfD%+1ljeX@$_oIl0;YrE;j5NDEY2hU z%%~znGXC!=arLt+dA+raUVHrT1)%0%l-_OhH6lM)Mf7&%dTG#AYiv|9;RwwHjB=Cn z?JTU;+gST{BAWdp0L%B1`Hba(n`@%c$7Zjl`dcc~`YDd`92b$8sDOWbfxGh@^kSWDA~IrvI{1vqHpjLrHwo~`X@VbQ6sl_EEGfws_2VgCfv zww+wq{wUtw@NT6G(C1|{Wb;!zhEf)(+LFNGj~|rm3jy6p=^rHB{CYmfBH%$$=0(+7 z3u0La-jCIo`B4@oqX-#Y9uw*#n3jipZvd-Wr`El>ED5dNq1cG_ao?hRHJv(P=p9mq z$lwf)UiESlETn3JtIw)DeGjrP+8(hv`C|RBs3`5 z63s9SvYSC+ERT6cGQ4-*_w)Yo{(gVo&pG$pd(XY+d(Qd(ymD8`*BfbVeQX|}*PhUh z#^pw$dvhOLUI{+`BW)R{=$ig82`8=-`jT7_-)NAXIe4V0YP`<)A-rEl)O~?5OJKdT zcz#m7tLkO6aIf?igFXrC5~(p=Yrr&q*L&N6uEGtUdu?JuR&F`bxCfGT>e$8N0??=5 zn+;kQu2JplMu4jN4U$%p#ppdLCfk8e4P%$nWpo8tQd+nY8*4khURF@EhKa^_<7O&c zz6t&SRF6`qN~)D-{?h;zkirJEE6);3BdZ5C^y!LuniPSAC%FTc3c8yPE>}%~?O|-p zige6HlE@Qh)$N*`vQrLR*RUVOXcEs&r%Ac~})5{vzlGoEqfOt#~#3&fUhOl)IlUYKi zkSNpm)JmID1%~fJgFyR|&Em72VEImQvjSNI&@G56pRz)sMT2bxy(9~sxzyBPN3)S} z=IHlD-J)VGZ-|P);c{0xEZ`63f~xy9pT?a_G#q4H@8r3MFgZVU9Lv!x5jH0;!^60Q zaOYoG<`u1~Q4F`io~hS#??eQowfRiY(6Wd7d!G~5I&L`*V&IK@z#G5}pm%$d!(3X329ltF!6KiA{A zMjsa@_ynFZbYjO$Ga>uzrcQLd?p0*@9kIH{q)P{=cqqST&}0O^Xcd3x4X7Jy4Cx{n z3h4SM)yD_ylg|G67bshy!u4M8?erpO9p(l#%QFJ~pv4N#UKvI6c&hnRSk86f3iDK( z(6PQaY(>2g)ZW=sHSS$?2f^x!O<)o%{mgDB{AUTnxTwd>&w8xNn6miuYHeYAwK-`@ zY|hp-2lW!p=())%ke-S{yWkX#ce$z_WKEYyX8*EvlPcF48Pl3@v28Ojjd---hC)CP zNi*1%!bZ-<+`t6bRdo1+v1HSKig!1!$3Jlz8wf$;3{#0dg zK`*Ic(px9pxL|?ba3^NYnBYX871jtlsb&c-hpeMVd8ZLcY8EOPWCw@^k=A)KD6_h@ z-uO#8-on$d{>DMOk&8IojJ2T%w?X>)a6g5;+HU#rvrU=L#Reeg0A>83hSUbWOsd)C z?N)`{kgG$Ejal)hu)7K&c|q#{GRWqqDb7c^a7;L_1sm$8zAeBMUA@Rrt*uKFwzrUV zB}Emx3(n#g`|1@3jwOHJSB_Zud-drj$|DV(x{fHWzV%lL8+nc85UActBM3|kKcE67 zHK%uJloH=*`|N!_f$)S;jUIAhMW`kj=^}1OPTtfk9Tka|QBeJ^%h2)O z5dU`N%lR3mY?r696Sif9u88mk+Y9nzHq9__)3FpDw}L7=%|ixzO5YA36z)e{|D$41 z?F)qqOOr)SuXsE=wj7ac-~7!Fxuv8XcMyI0gX}+q!dJE2idR|B)c2@EuKsBzdVc4* zx=)EY{US-fSwBC?-6fdzYwFVV z>P@B@w+7(13&31yeNcPf#|zQqS(XeCeZC`|j`)2M)ILfmW3 zU$RSA^~V2PpL2t{K?gx@Dtf`cD(j_sBnB=rv+%hDSc;eud&(HH_jt`cu9dbGZh__t zP$!56UhnWcms~eY!h`0+zWrN|+PmV+vCVY&d>{Z5#=i-a*^t$#=b>YxD|4RFzmO@9 zc#^j{>LRLvO7*PZ@Tu(;bYhpIYro~IopM8@D6V0yinzuN#mc6a&g93Hj|F3R&(y#wEVXM5KAe=L>0(SeTo{k0l|7LTIbpve||S(UWD z_yHhm-s}9_k;n<2=t{7x*kptS`L6_>le$JQm`n diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index d9af3ab9..9273c45d 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -3,6 +3,12 @@ library(golem) library(withr) old_usethis.quiet <- getOption("usethis.quiet") options("usethis.quiet" = TRUE) +# Small hack to prevent warning from rlang::lang() in tests +# This should be managed in {attempt} later on +x <- suppressWarnings({ + rlang::lang(print) +}) + ### Funs remove_file <- function(path) { if (file.exists(path)) unlink(path, force = TRUE) @@ -56,13 +62,16 @@ fakename <- sprintf( gsub("[ :-]", "", Sys.time()) ) + +## random dir +randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") + tpdir <- normalizePath(tempdir()) unlink(file.path(tpdir, fakename), recursive = TRUE) create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) -## random dir -randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") + fp <- file.path("inst/app", randir) dir.create(file.path(pkg, fp), recursive = TRUE) diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 8aef7553..7057e65a 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -76,60 +76,8 @@ test_that("add_dockerfiles repos variation", { } }) }) -test_that("add_dockerfiles multi repos", { - skip_if_not_installed("dockerfiler", "0.1.4") - - repos <- c( - bioc1 = "https://bioconductor.org/packages/3.10/data/annotation", - bioc2 = "https://bioconductor.org/packages/3.10/data/experiment", - CRAN = "https://cran.rstudio.com" - ) - - - with_dir(pkg, { - for (fun in list( - add_dockerfile, - add_dockerfile_heroku, - add_dockerfile_shinyproxy - )) { - burn_after_reading( - "Dockerfile", - { - output <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = repos, - output = "Dockerfile" - ) - ) - - expect_exists("Dockerfile") - - - test <- stringr::str_detect( - output, - "Dockerfile created at Dockerfile" - ) - expect_true(test) - to_find <- "RUN echo \"options(repos = c(bioc1 = 'https://bioconductor.org/packages/3.10/data/annotation', bioc2 = 'https://bioconductor.org/packages/3.10/data/experiment', CRAN = 'https://cran.rstudio.com'), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - # for R <= 3.4 - to_find_old <- "RUN echo \"options(repos = structure(c('https://bioconductor.org/packages/3.10/data/annotation', 'https://bioconductor.org/packages/3.10/data/experiment', 'https://cran.rstudio.com'), .Names = c('bioc1', 'bioc2', 'CRAN')), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - - expect_true( - sum( - readLines(con = "Dockerfile") %in% c(to_find, to_find_old) - ) == 1 - ) - } - ) - } - }) -}) - test_that("add_rstudio_files", { with_dir(pkg, { for (fun in list( diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R new file mode 100644 index 00000000..43d74c15 --- /dev/null +++ b/tests/testthat/test-renv_stuff.R @@ -0,0 +1,33 @@ +test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all output file are present", { + skip_if_not_installed("dockerfiler", "0.2.0") + + with_dir(pkg, { + for (fun in list( + add_dockerfile_with_renv, + add_dockerfile_with_renv_shinyproxy, + add_dockerfile_with_renv_heroku + )) { + deploy_folder <- file.path( + tempdir(), + make.names( + paste0( + "deploy", + round( + runif(1, min = 0, max = 99999) + ) + ) + ) + ) + + fun(output_dir = deploy_folder, open = FALSE) + + expect_exists(file.path(deploy_folder, "Dockerfile")) + expect_exists(file.path(deploy_folder, "Dockerfile_base")) + expect_exists(file.path(deploy_folder, "README")) + expect_exists(file.path(deploy_folder, "renv.lock.prod")) + + expect_length(list.files(path = deploy_folder, pattern = "tar.gz$"), 1) + unlink(deploy_folder, force = TRUE, recursive = TRUE) + } + }) +}) diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 002671ec..3d93116e 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -122,7 +122,7 @@ golem::fill_desc( ) ``` -About [the DESCRIPTION file](https://r-pkgs.org/description.html). +About [the DESCRIPTION file](https://r-pkgs.org/Metadata.html#sec-description). ### Add `{golem}` options @@ -155,7 +155,7 @@ Create a template for tests: golem::use_recommended_tests() ``` -About [tests in a package](https://r-pkgs.org/tests.html). +About [tests in a package](https://r-pkgs.org/testing-basics.html). ### Use Recommended Packages diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index aaa3dd82..052bbf95 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -45,7 +45,7 @@ Note that the `{attachment}` package should be installed on your machine. attachment::att_amend_desc() ``` -About [package dependencies](https://r-pkgs.org/namespace.html). +About [package dependencies](https://r-pkgs.org/Metadata.html#sec-namespace). ### Add modules @@ -139,7 +139,7 @@ Add more tests to your application: usethis::use_test("app") ``` -About [testing a package](https://r-pkgs.org/tests.html). +About [testing a package](https://r-pkgs.org/testing-basics.html). ## Documentation diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index 4021cd03..cf434e2f 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -69,6 +69,10 @@ golem::add_shinyserver_file() ### Docker +#### without using {renv} + + + ```{r} # If you want to deploy via a generic Dockerfile golem::add_dockerfile() @@ -80,3 +84,75 @@ golem::add_dockerfile_shinyproxy() golem::add_dockerfile_heroku() ``` +#### using {renv} + + +#### CASE 1 : you didn't use renv during developpment process + + +> this functions will create a "deploy" folder containing : + +```{txt} +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file + + +```{r} +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy") + +``` + +If you would like to use {renv} during developpement, you can init a renv.lock file with + +```{r} +attachment::create_renv_for_dev(dev_pkg = c("renv", "devtools", "roxygen2", + "usethis", "pkgload", "testthat", "remotes", "covr", "attachment", + "pak", "dockerfiler","golem")) +``` +an activate {renv} with + +```{r} +renv::activate() +``` + + + + + +#### CASE 2 : you already have a renv.lock file for your project + + +```{r} + +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy",lockfile = "renv.lock") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy",lockfile = "renv.lock") + + +``` + +> this functions will create a "deploy" folder containing : + +```{txt} +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file \ No newline at end of file From fe1d12d7e8807d61d15a5d1ec977614c6378fbf0 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 8 Aug 2022 21:44:01 +0200 Subject: [PATCH 016/190] doc: bump dev version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index eb60aa0e..411b8d2f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3 +Version: 0.3.3.9000 Authors@R: c(person(given = "Colin", family = "Fay", From 8b22ea8a7067c292477334ee3aa99d101cca3006 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 8 Aug 2022 21:44:39 +0200 Subject: [PATCH 017/190] bug: fixed bug with check_installed Hotfix because of the dirty way we released version 0.3.3 --- R/add_dockerfiles.R | 22 +++++++++++++++------- R/add_dockerfiles_renv.R | 18 ++++++++++++++---- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index df3bb111..56a62789 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -144,9 +144,11 @@ add_dockerfile_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - check_is_installed("dockerfiler") - - required_version("dockerfiler", "0.1.4") + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) where <- path(pkg, output) @@ -253,8 +255,11 @@ add_dockerfile_shinyproxy_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - check_is_installed("dockerfiler") - required_version("dockerfiler", "0.1.4") + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) where <- path(pkg, output) usethis::use_build_ignore(output) @@ -354,8 +359,11 @@ add_dockerfile_heroku_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - check_is_installed("dockerfiler") - required_version("dockerfiler", "0.1.4") + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) where <- path(pkg, output) usethis::use_build_ignore(output) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 80fb9d61..f6639bdf 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -12,10 +12,20 @@ add_dockerfile_with_renv_ <- function( update_tar_gz = TRUE # build_golem_from_source = TRUE, ) { - check_is_installed("renv") - check_is_installed("dockerfiler") - required_version("dockerfiler", "0.2.0") - check_is_installed("attachment") + rlang::check_installed( + "renv", + reason = "to build a Dockerfile." + ) + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) + rlang::check_installed( + "attachment", + version = "0.2.5", + reason = "to build a Dockerfile." + ) # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on From cc2ba3dcb7ea95cffcf36627d98f36d7d9d6f02a Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 8 Aug 2022 21:44:51 +0200 Subject: [PATCH 018/190] chore: added an EOF --- R/utils.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils.R b/R/utils.R index 6e368690..ad70b6e3 100644 --- a/R/utils.R +++ b/R/utils.R @@ -452,4 +452,4 @@ is_existing_module <- function(module) { existing_module_files ) module %in% existing_module_names -} \ No newline at end of file +} From 9d51302df00f5fca43fba4fa6e9e275eb70e9223 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 8 Aug 2022 21:54:54 +0200 Subject: [PATCH 019/190] chore: reindent --- R/add_dockerfiles.R | 170 ++++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 8805b8e5..b3857371 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -179,47 +179,47 @@ add_dockerfile_ <- talk_once( reason = "to build a Dockerfile." ) - where <- path(pkg, output) + where <- path(pkg, output) - usethis::use_build_ignore(path_file(where)) + usethis::use_build_ignore(path_file(where)) - dock <- dockerfiler::dock_from_desc( - path = path, - FROM = from, - AS = as, - sysreqs = sysreqs, - repos = repos, - expand = expand, - build_from_source = build_golem_from_source, - update_tar_gz = update_tar_gz, - extra_sysreqs = extra_sysreqs - ) + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) - dock$EXPOSE(port) + dock$EXPOSE(port) - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", - port, - host, - read.dcf(path)[1] - ) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + read.dcf(path)[1] ) + ) - dock$write(output) + dock$write(output) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) - } + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) } - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + } + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source + ) return(invisible(dock)) }, @@ -291,39 +291,39 @@ add_dockerfile_shinyproxy_ <- talk_once( ) where <- path(pkg, output) - usethis::use_build_ignore(output) + usethis::use_build_ignore(output) - dock <- dockerfiler::dock_from_desc( - path = path, - FROM = from, - AS = as, - sysreqs = sysreqs, - repos = repos, - expand = expand, - build_from_source = build_golem_from_source, - update_tar_gz = update_tar_gz, - extra_sysreqs = extra_sysreqs - ) + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) - dock$EXPOSE(3838) - dock$CMD(sprintf( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", - read.dcf(path)[1] - )) - dock$write(output) + dock$EXPOSE(3838) + dock$CMD(sprintf( + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", + read.dcf(path)[1] + )) + dock$write(output) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) - } + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) } - alert_build( - path, - output, - build_golem_from_source = build_golem_from_source - ) + } + alert_build( + path, + output, + build_golem_from_source = build_golem_from_source + ) return(invisible(dock)) }, @@ -395,33 +395,33 @@ add_dockerfile_heroku_ <- talk_once( ) where <- path(pkg, output) - usethis::use_build_ignore(output) + usethis::use_build_ignore(output) - dock <- dockerfiler::dock_from_desc( - path = path, - FROM = from, - AS = as, - sysreqs = sysreqs, - repos = repos, - expand = expand, - build_from_source = build_golem_from_source, - update_tar_gz = update_tar_gz, - extra_sysreqs = extra_sysreqs - ) + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", - read.dcf(path)[1] - ) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + read.dcf(path)[1] ) - dock$write(output) + ) + dock$write(output) - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source + ) apps_h <- gsub( "\\.", From 75ee80706659dbcbca28d4c0eac8e734370441e0 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 8 Aug 2022 22:01:49 +0200 Subject: [PATCH 020/190] chore: changed indentatino --- R/config.R | 204 ++++++++--------- R/create_golem.R | 426 +++++++++++++++++------------------ R/golem-yaml-get.R | 150 ++++++------ R/golem-yaml-set.R | 150 ++++++------ R/golem-yaml-utils.R | 140 ++++++------ R/set_golem_options.R | 96 ++++---- R/utils.R | 2 +- tests/testthat/test-config.R | 150 ++++++------ 8 files changed, 659 insertions(+), 659 deletions(-) diff --git a/R/config.R b/R/config.R index 1740adb4..e2ab53b0 100644 --- a/R/config.R +++ b/R/config.R @@ -10,45 +10,45 @@ #' @importFrom attempt attempt is_try_error #' @importFrom fs path path_abs guess_where_config <- function( - path = ".", - file = "inst/golem-config.yml" + path = ".", + file = "inst/golem-config.yml" ) { - # We'll try to guess where the path - # to the golem-config file is + # We'll try to guess where the path + # to the golem-config file is - # This one should be correct in 99% of the case - # => current directory /inst/golem-config.yml - ret_path <- path( - path, - file - ) - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } + # This one should be correct in 99% of the case + # => current directory /inst/golem-config.yml + ret_path <- path( + path, + file + ) + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } - # Maybe for some reason we are in inst/ - ret_path <- "golem-config.yml" - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } + # Maybe for some reason we are in inst/ + ret_path <- "golem-config.yml" + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } - # Trying with pkg_path - ret_path <- attempt({ - path( - golem::pkg_path(), - "inst/golem-config.yml" - ) - }) + # Trying with pkg_path + ret_path <- attempt({ + path( + golem::pkg_path(), + "inst/golem-config.yml" + ) + }) - if ( - !is_try_error(ret_path) & - file_exists(ret_path) - ) { - return( - path_abs(ret_path) - ) - } - return(NULL) + if ( + !is_try_error(ret_path) & + file_exists(ret_path) + ) { + return( + path_abs(ret_path) + ) + } + return(NULL) } #' Get the path to the current config File @@ -64,85 +64,85 @@ guess_where_config <- function( #' @export get_current_config <- function(path = ".") { - # We check wether we can guess where the config file is - path_conf <- guess_where_config(path) + # We check wether we can guess where the config file is + path_conf <- guess_where_config(path) - # We default to inst/ if this doesn't exist - if (is.null(path_conf)) { - path_conf <- path( - path, - "inst/golem-config.yml" - ) - } + # We default to inst/ if this doesn't exist + if (is.null(path_conf)) { + path_conf <- path( + path, + "inst/golem-config.yml" + ) + } - if (!file_exists(path_conf)) { - if (interactive()) { - ask <- yesno( - sprintf( - "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", - basename(path_conf) - ) - ) - # Return early if the user doesn't allow - if (!ask) { - return(NULL) - } + if (!file_exists(path_conf)) { + if (interactive()) { + ask <- yesno( + sprintf( + "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", + basename(path_conf) + ) + ) + # Return early if the user doesn't allow + if (!ask) { + return(NULL) + } - file_copy( - path = golem_sys("shinyexample/inst/golem-config.yml"), - new_path = path( - path, - "inst/golem-config.yml" - ) - ) - file_copy( - path = golem_sys("shinyexample/R/app_config.R"), - new_path = file.path( - path, - "R/app_config.R" - ) - ) - replace_word( - path( - path, - "R/app_config.R" - ), - "shinyexample", - golem::pkg_name() - ) - # TODO This should also create the dev folder - } else { - stop( - sprintf( - "The %s file doesn't exist.", - basename(path_conf) - ) - ) - } - } + file_copy( + path = golem_sys("shinyexample/inst/golem-config.yml"), + new_path = path( + path, + "inst/golem-config.yml" + ) + ) + file_copy( + path = golem_sys("shinyexample/R/app_config.R"), + new_path = file.path( + path, + "R/app_config.R" + ) + ) + replace_word( + path( + path, + "R/app_config.R" + ), + "shinyexample", + golem::pkg_name() + ) + # TODO This should also create the dev folder + } else { + stop( + sprintf( + "The %s file doesn't exist.", + basename(path_conf) + ) + ) + } + } - return( - invisible(path_conf) - ) + return( + invisible(path_conf) + ) } # This function changes the name of the # package in app_config when you need to # set the {golem} name change_app_config_name <- function( - name, - path = get_golem_wd() + name, + path = get_golem_wd() ) { - pth <- fs::path(path, "R", "app_config.R") - app_config <- readLines(pth) + pth <- fs::path(path, "R", "app_config.R") + app_config <- readLines(pth) - where_system.file <- grep("system.file", app_config) + where_system.file <- grep("system.file", app_config) - app_config[ - where_system.file - ] <- sprintf( - ' system.file(..., package = "%s")', - name - ) - write(app_config, pth) + app_config[ + where_system.file + ] <- sprintf( + ' system.file(..., package = "%s")', + name + ) + write(app_config, pth) } diff --git a/R/create_golem.R b/R/create_golem.R index b531cc77..566ede49 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -1,33 +1,33 @@ replace_package_name <- function( - copied_files, - package_name, - path_to_golem + copied_files, + package_name, + path_to_golem ) { - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path_to_golem, f) - - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path_to_golem, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) - } - - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path_to_golem, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path_to_golem, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } } @@ -65,194 +65,194 @@ replace_package_name <- function( #' #' @return The path, invisibly. create_golem <- function( - path, - check_name = TRUE, - open = TRUE, - overwrite = FALSE, - package_name = basename(path), - without_comments = FALSE, - project_hook = golem::project_hook, - with_git = FALSE, - ... + path, + check_name = TRUE, + open = TRUE, + overwrite = FALSE, + package_name = basename(path), + without_comments = FALSE, + project_hook = golem::project_hook, + with_git = FALSE, + ... ) { - path_to_golem <- normalizePath(path, mustWork = FALSE) - - if (check_name) { - cat_rule("Checking package name") - getFromNamespace("check_package_name", "usethis")(package_name) - cat_green_tick("Valid package name") - } - - - if (dir.exists(path_to_golem)) { - if (!isTRUE(overwrite)) { - stop( - paste( - "Project directory already exists. \n", - "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", - "Be careful this will restore a brand new golem. \n", - "You might be at risk of losing your work !" - ), - call. = FALSE - ) - } else { - cat_red_bullet("Overwriting existing project.") - } - } else { - cat_rule("Creating dir") - usethis::create_project( - path = path_to_golem, - open = FALSE, - ) - cat_green_tick("Created package directory") - } - - - cat_rule("Copying package skeleton") - from <- golem_sys("shinyexample") - - # Copy over whole directory - dir_copy( - path = from, - new_path = path_to_golem, - overwrite = TRUE - ) - - # Listing copied files ***from source directory*** - copied_files <- list.files( - path = from, - full.names = FALSE, - all.files = TRUE, - recursive = TRUE - ) - - replace_package_name( - copied_files, - package_name, - path_to_golem - ) - - cat_green_tick("Copied app skeleton") - - old <- setwd(path_to_golem) - - cat_rule("Running project hook function") - - # TODO fix - # for some weird reason test() fails here when using golem::create_golem - # and I don't have time to search why rn - if (substitute(project_hook) == "golem::project_hook") { - project_hook <- getFromNamespace("project_hook", "golem") - } - project_hook( - path = path_to_golem, - package_name = package_name, - ... - ) - - setwd(old) - - cat_green_tick("All set") - - - if (isTRUE(without_comments)) { - files <- list.files( - path = c( - file.path(path_to_golem, "dev"), - file.path(path_to_golem, "R") - ), - full.names = TRUE - ) - for (file in files) { - remove_comments(file) - } - } - - - if (isTRUE(with_git)) { - cat_rule("Initializing git repository") - git_output <- system( - command = paste("git init", path_to_golem), - ignore.stdout = TRUE, - ignore.stderr = TRUE - ) - if (git_output) { - cat_red_bullet("Error initializing git epository") - } else { - cat_green_tick("Initialized git repository") - } - } - - - old <- setwd(path_to_golem) - use_latest_dependencies() - - # No .Rprofile for now - # cat_rule("Appending .Rprofile") - # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) - # write("home_profile <- file.path(", ".Rprofile", append = TRUE) - # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) - # write(" \".Rprofile\"", ".Rprofile", append = TRUE) - # write(")", ".Rprofile", append = TRUE) - # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) - # write(" source(home_profile)", ".Rprofile", append = TRUE) - # write("}", ".Rprofile", append = TRUE) - # write("rm(home_profile)", ".Rprofile", append = TRUE) - # - # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) - # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) - # cat_green_tick("Appended") - - setwd(old) - - cat_rule("Done") - - cat_line( - paste0( - "A new golem named ", - package_name, - " was created at ", - path_to_golem, - " .\n", - "To continue working on your app, start editing the 01_start.R file." - ) - ) - - - if (isTRUE(open)) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { - rstudioapi::openProject(path = path) - } else { - setwd(path) - } - } - - return( - invisible( - path_to_golem - ) - ) + path_to_golem <- normalizePath(path, mustWork = FALSE) + + if (check_name) { + cat_rule("Checking package name") + getFromNamespace("check_package_name", "usethis")(package_name) + cat_green_tick("Valid package name") + } + + + if (dir.exists(path_to_golem)) { + if (!isTRUE(overwrite)) { + stop( + paste( + "Project directory already exists. \n", + "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", + "Be careful this will restore a brand new golem. \n", + "You might be at risk of losing your work !" + ), + call. = FALSE + ) + } else { + cat_red_bullet("Overwriting existing project.") + } + } else { + cat_rule("Creating dir") + usethis::create_project( + path = path_to_golem, + open = FALSE, + ) + cat_green_tick("Created package directory") + } + + + cat_rule("Copying package skeleton") + from <- golem_sys("shinyexample") + + # Copy over whole directory + dir_copy( + path = from, + new_path = path_to_golem, + overwrite = TRUE + ) + + # Listing copied files ***from source directory*** + copied_files <- list.files( + path = from, + full.names = FALSE, + all.files = TRUE, + recursive = TRUE + ) + + replace_package_name( + copied_files, + package_name, + path_to_golem + ) + + cat_green_tick("Copied app skeleton") + + old <- setwd(path_to_golem) + + cat_rule("Running project hook function") + + # TODO fix + # for some weird reason test() fails here when using golem::create_golem + # and I don't have time to search why rn + if (substitute(project_hook) == "golem::project_hook") { + project_hook <- getFromNamespace("project_hook", "golem") + } + project_hook( + path = path_to_golem, + package_name = package_name, + ... + ) + + setwd(old) + + cat_green_tick("All set") + + + if (isTRUE(without_comments)) { + files <- list.files( + path = c( + file.path(path_to_golem, "dev"), + file.path(path_to_golem, "R") + ), + full.names = TRUE + ) + for (file in files) { + remove_comments(file) + } + } + + + if (isTRUE(with_git)) { + cat_rule("Initializing git repository") + git_output <- system( + command = paste("git init", path_to_golem), + ignore.stdout = TRUE, + ignore.stderr = TRUE + ) + if (git_output) { + cat_red_bullet("Error initializing git epository") + } else { + cat_green_tick("Initialized git repository") + } + } + + + old <- setwd(path_to_golem) + use_latest_dependencies() + + # No .Rprofile for now + # cat_rule("Appending .Rprofile") + # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) + # write("home_profile <- file.path(", ".Rprofile", append = TRUE) + # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) + # write(" \".Rprofile\"", ".Rprofile", append = TRUE) + # write(")", ".Rprofile", append = TRUE) + # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) + # write(" source(home_profile)", ".Rprofile", append = TRUE) + # write("}", ".Rprofile", append = TRUE) + # write("rm(home_profile)", ".Rprofile", append = TRUE) + # + # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) + # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) + # cat_green_tick("Appended") + + setwd(old) + + cat_rule("Done") + + cat_line( + paste0( + "A new golem named ", + package_name, + " was created at ", + path_to_golem, + " .\n", + "To continue working on your app, start editing the 01_start.R file." + ) + ) + + + if (isTRUE(open)) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { + rstudioapi::openProject(path = path) + } else { + setwd(path) + } + } + + return( + invisible( + path_to_golem + ) + ) } # to be used in RStudio "new project" GUI create_golem_gui <- function(path, ...) { - dots <- list(...) - attempt::stop_if_not( - dots$project_hook, - ~ grepl("::", .x), - "{golem} project templates must be explicitely namespaced (pkg::fun)" - ) - splt <- strsplit(dots$project_hook, "::") - project_hook <- getFromNamespace( - splt[[1]][2], - splt[[1]][1] - ) - create_golem( - path = path, - open = FALSE, - without_comments = dots$without_comments, - project_hook = project_hook, - check_name = dots$check_name, - with_git = dots$with_git - ) + dots <- list(...) + attempt::stop_if_not( + dots$project_hook, + ~ grepl("::", .x), + "{golem} project templates must be explicitely namespaced (pkg::fun)" + ) + splt <- strsplit(dots$project_hook, "::") + project_hook <- getFromNamespace( + splt[[1]][2], + splt[[1]][1] + ) + create_golem( + path = path, + open = FALSE, + without_comments = dots$without_comments, + project_hook = project_hook, + check_name = dots$check_name, + with_git = dots$with_git + ) } diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index b6c82eae..d0fda7b1 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -1,99 +1,99 @@ #' @importFrom config get get_golem_things <- function( - value, - config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), - use_parent = TRUE, - path + value, + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path ) { - conf_path <- get_current_config( - path, - set_options = TRUE - ) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) + conf_path <- get_current_config( + path, + set_options = TRUE + ) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) } #' @export #' @rdname golem_opts get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() + use_parent = TRUE, + path = golem::pkg_path() ) { - pth <- get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) - if (is.null(pth)) { - pth <- golem::pkg_path() - } - return(pth) + pth <- get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) + if (is.null(pth)) { + pth <- golem::pkg_path() + } + return(pth) } #' @export #' @rdname golem_opts get_golem_name <- function( - config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path = golem::pkg_path() ) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - return(nm) + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + return(nm) } #' @export #' @rdname golem_opts get_golem_version <- function( - config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path = golem::pkg_path() ) { - vers <- get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(vers)) { - vers <- golem::pkg_version() - } - return(vers) + vers <- get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(vers)) { + vers <- golem::pkg_version() + } + return(vers) } diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 441f74ab..495edb3e 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -30,101 +30,101 @@ #' @rdname golem_opts #' @importFrom fs path_abs set_golem_wd <- function( - golem_wd = golem::pkg_path(), - pkg = golem::pkg_path(), - talkative = TRUE + golem_wd = golem::pkg_path(), + pkg = golem::pkg_path(), + talkative = TRUE ) { - if ( - golem_wd == "golem::pkg_path()" | - golem_wd == golem::pkg_path() - ) { - golem_yaml_path <- "golem::pkg_path()" - attr(golem_yaml_path, "tag") <- "!expr" - } else { - golem_yaml_path <- path_abs(golem_wd) - } + if ( + golem_wd == "golem::pkg_path()" | + golem_wd == golem::pkg_path() + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" + } else { + golem_yaml_path <- path_abs(golem_wd) + } - amend_golem_config( - key = "golem_wd", - value = golem_yaml_path, - config = "dev", - pkg = pkg, - talkative = talkative - ) + amend_golem_config( + key = "golem_wd", + value = golem_yaml_path, + config = "dev", + pkg = pkg, + talkative = talkative + ) - invisible(path) + invisible(path) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_name <- function( - name = golem::pkg_name(), - pkg = golem::pkg_path(), - talkative = TRUE + name = golem::pkg_name(), + pkg = golem::pkg_path(), + talkative = TRUE ) { - # Changing in YAML - amend_golem_config( - key = "golem_name", - value = name, - config = "default", - pkg = pkg, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = pkg - ) + # Changing in YAML + amend_golem_config( + key = "golem_name", + value = name, + config = "default", + pkg = pkg, + talkative = talkative + ) + # Changing in app-config.R + change_app_config_name( + name = name, + path = pkg + ) - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) + # Changing in DESCRIPTION + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(name) + invisible(name) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_version <- function( - version = golem::pkg_version(), - pkg = golem::pkg_path(), - talkative = TRUE + version = golem::pkg_version(), + pkg = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) + path <- path_abs(path) - # Changing in YAML - amend_golem_config( - key = "golem_version", - value = as.character(version), - config = "default", - pkg = pkg, - talkative = talkative - ) + # Changing in YAML + amend_golem_config( + key = "golem_version", + value = as.character(version), + config = "default", + pkg = pkg, + talkative = talkative + ) - desc <- desc::description$new( - file = fs::path( - pkg, - "DESCRIPTION" - ) - ) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) + desc <- desc::description$new( + file = fs::path( + pkg, + "DESCRIPTION" + ) + ) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(version) + invisible(version) } diff --git a/R/golem-yaml-utils.R b/R/golem-yaml-utils.R index 0e025655..a6f61463 100644 --- a/R/golem-yaml-utils.R +++ b/R/golem-yaml-utils.R @@ -9,42 +9,42 @@ # #' @importFrom utils modifyList find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml( - conf_path, - eval.expr = FALSE - ) - conf.eval <- yaml::read_yaml( - conf_path, - eval.expr = TRUE - ) + conf <- yaml::read_yaml( + conf_path, + eval.expr = FALSE + ) + conf.eval <- yaml::read_yaml( + conf_path, + eval.expr = TRUE + ) - expr_list <- lapply( - names(conf), - function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - } - ) + expr_list <- lapply( + names(conf), + function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + } + ) - names(expr_list) <- names(conf) + names(expr_list) <- names(conf) - expr_list <- Filter( - function(x) length(x) > 0, - expr_list - ) + expr_list <- Filter( + function(x) length(x) > 0, + expr_list + ) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply( - expr_list, - add_expr_tag - ) + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply( + expr_list, + add_expr_tag + ) - modifyList( - conf, - tagged_exprs - ) + modifyList( + conf, + tagged_exprs + ) } #' Amend golem config file @@ -60,51 +60,51 @@ find_and_tag_exprs <- function(conf_path) { #' #' @return Used for side effects. amend_golem_config <- function( - key, - value, - config = "default", - pkg = golem::pkg_path(), - talkative = TRUE + key, + value, + config = "default", + pkg = golem::pkg_path(), + talkative = TRUE ) { - conf_path <- get_current_config(pkg) + conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) - cat_if_talk <- function( - ..., - fun = cat_green_tick - ) { - if (talkative) { - fun(...) - } - } + cat_if_talk <- function( + ..., + fun = cat_green_tick + ) { + if (talkative) { + fun(...) + } + } - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) - if (key == "golem_wd") { - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) - } + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) + write_yaml( + conf, + conf_path + ) - invisible(TRUE) + invisible(TRUE) } diff --git a/R/set_golem_options.R b/R/set_golem_options.R index c35ac8cc..6eb4ed3f 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -36,62 +36,62 @@ #' @return Used for side-effects for the setters, and values from the #' config in the getters. set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE, - config_file = golem::get_current_config(golem_wd) + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) ) { - # TODO here we'll run the - # golem_install_dev_pkg() function + # TODO here we'll run the + # golem_install_dev_pkg() function - if (talkative) { - cli::cat_rule( - "Setting {golem} options in `golem-config.yml`" - ) - } + if (talkative) { + cli::cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } - # Let's do this in the order of the - # parameters - # Setting name of the golem - set_golem_name( - name = golem_name, - talkative = talkative - ) + # Let's do this in the order of the + # parameters + # Setting name of the golem + set_golem_name( + name = golem_name, + talkative = talkative + ) - # Let's start with wd - # Basically here the idea is to be able - # to keep the wd as an expr if it is the - # same as golem::pkg_path(), otherwise - # we use the explicit path + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path - set_golem_wd( - path = golem_wd, - talkative = talkative - ) + set_golem_wd( + path = golem_wd, + talkative = talkative + ) - # Setting golem_version - set_golem_version( - version = golem_version, - talkative = talkative - ) + # Setting golem_version + set_golem_version( + version = golem_version, + talkative = talkative + ) - # Setting app_prod - set_golem_things( - "app_prod", - app_prod, - path = golem_wd, - talkative = talkative - ) + # Setting app_prod + set_golem_things( + "app_prod", + app_prod, + path = golem_wd, + talkative = talkative + ) - # This part is for {usethis} and {here} - if (talkative) { - cli::cat_rule( - "Setting {usethis} project as `golem_wd`" - ) - } + # This part is for {usethis} and {here} + if (talkative) { + cli::cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } - proj_set(golem_wd) + proj_set(golem_wd) } diff --git a/R/utils.R b/R/utils.R index 92f2fdff..fe5d5116 100644 --- a/R/utils.R +++ b/R/utils.R @@ -452,4 +452,4 @@ is_existing_module <- function(module) { existing_module_files ) module %in% existing_module_names -} \ No newline at end of file +} diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index 69400c0f..fd553276 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -1,78 +1,78 @@ test_that("config works", { - with_dir(pkg, { - # We'll try to be sure that - # golem_wd: !expr golem::pkg_path() - # is kept along the way - expect_equal( - tail(readLines("inst/golem-config.yml"), 1), - " golem_wd: !expr golem::pkg_path()" - ) - expect_equal( - get_golem_name(), - fakename - ) - expect_equal( - get_golem_version(), - "0.0.0.9000" - ) - expect_equal( - normalizePath(get_golem_wd(), mustWork = FALSE), - normalizePath(pkg, mustWork = FALSE) - ) - amend_golem_config( - key = "where", - value = "indev" - ) - amend_golem_config( - key = "where", - value = "inprod", - config = "production" - ) - expect_equal( - tail(readLines("inst/golem-config.yml"), 1), - " golem_wd: !expr golem::pkg_path()" - ) - expect_equal( - config::get("where", file = "inst/golem-config.yml"), - "indev" - ) - expect_equal( - config::get("where", config = "production", file = "inst/golem-config.yml"), - "inprod" - ) - where_conf <- withr::with_envvar( - c("R_CONFIG_ACTIVE" = "production"), - { - config::get("where", file = "inst/golem-config.yml") - } - ) - expect_equal( - where_conf, - "inprod" - ) - set_golem_name("plop") - expect_equal( - get_golem_name(), - "plop" - ) - set_golem_name(fakename) - set_golem_version("0.0.0.9001") - expect_equal( - get_golem_version(), - "0.0.0.9001" - ) - set_golem_version("0.0.0.9000") + with_dir(pkg, { + # We'll try to be sure that + # golem_wd: !expr golem::pkg_path() + # is kept along the way + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + get_golem_name(), + fakename + ) + expect_equal( + get_golem_version(), + "0.0.0.9000" + ) + expect_equal( + normalizePath(get_golem_wd(), mustWork = FALSE), + normalizePath(pkg, mustWork = FALSE) + ) + amend_golem_config( + key = "where", + value = "indev" + ) + amend_golem_config( + key = "where", + value = "inprod", + config = "production" + ) + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + config::get("where", file = "inst/golem-config.yml"), + "indev" + ) + expect_equal( + config::get("where", config = "production", file = "inst/golem-config.yml"), + "inprod" + ) + where_conf <- withr::with_envvar( + c("R_CONFIG_ACTIVE" = "production"), + { + config::get("where", file = "inst/golem-config.yml") + } + ) + expect_equal( + where_conf, + "inprod" + ) + set_golem_name("plop") + expect_equal( + get_golem_name(), + "plop" + ) + set_golem_name(fakename) + set_golem_version("0.0.0.9001") + expect_equal( + get_golem_version(), + "0.0.0.9001" + ) + set_golem_version("0.0.0.9000") - set_golem_wd(normalizePath("inst")) - expect_equal( - normalizePath(get_golem_wd()), - normalizePath("inst") - ) - set_golem_wd(pkg) - # Be sure that after setting the stuff the wd is still here::here() - expect_equal( - tail(readLines("inst/golem-config.yml"), 1), - " golem_wd: !expr golem::pkg_path()" - ) - }) + set_golem_wd(normalizePath("inst")) + expect_equal( + normalizePath(get_golem_wd()), + normalizePath("inst") + ) + set_golem_wd(pkg) + # Be sure that after setting the stuff the wd is still here::here() + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + }) }) From 828f3def223dcea4b9640b3a3154a0ef21a520a1 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 08:18:15 +0200 Subject: [PATCH 021/190] fix(DESCRIPTION): typo in version number --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index f985dcdd..411b8d2f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -82,7 +82,7 @@ Suggests: testthat, tools, withr, - attachment (>= 0.2.5) (>= 0.2.5) + attachment (>= 0.2.5) VignetteBuilder: knitr Config/testthat/edition: 3 From a6ac8581dc019413a18994dc1eba2486b36ddc95 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 08:21:41 +0200 Subject: [PATCH 022/190] chore: added some comments --- R/config.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/config.R b/R/config.R index e2ab53b0..4f2ac5cc 100644 --- a/R/config.R +++ b/R/config.R @@ -17,6 +17,7 @@ guess_where_config <- function( # to the golem-config file is # This one should be correct in 99% of the case + # If we don't change the default values of the params. # => current directory /inst/golem-config.yml ret_path <- path( path, From 3ba39ee2d7f2fd0823b57ec4f9f2dba120484583 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 08:56:43 +0200 Subject: [PATCH 023/190] doc: re-built the doc --- DESCRIPTION | 2 +- NAMESPACE | 1 + man/amend_golem_config.Rd | 4 +- man/document_and_reload.Rd | 2 +- man/get_current_config.Rd | 16 ++++++++ man/golem_opts.Rd | 79 +++++++++++++++++++------------------- 6 files changed, 60 insertions(+), 44 deletions(-) create mode 100644 man/get_current_config.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 5117b403..65c5be9b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -89,4 +89,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.1 diff --git a/NAMESPACE b/NAMESPACE index 468dedd2..30bc57fd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -37,6 +37,7 @@ export(expect_shinytag) export(expect_shinytaglist) export(favicon) export(fill_desc) +export(get_current_config) export(get_golem_name) export(get_golem_options) export(get_golem_version) diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd index 208f95a3..1c803ebb 100644 --- a/man/amend_golem_config.Rd +++ b/man/amend_golem_config.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/config.R +% Please edit documentation in R/golem-yaml-utils.R \name{amend_golem_config} \alias{amend_golem_config} \title{Amend golem config file} @@ -8,7 +8,7 @@ amend_golem_config( key, value, config = "default", - pkg = get_golem_wd(), + pkg = golem::pkg_path(), talkative = TRUE ) } diff --git a/man/document_and_reload.Rd b/man/document_and_reload.Rd index 8d0c84ec..95b60561 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -24,7 +24,7 @@ which defaults to \code{c("collate", "namespace", "rd")}.} \item{load_code}{A function used to load all the R code in the package directory. The default, \code{NULL}, uses the strategy defined by -the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load]{load_pkgload()}}. +the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load_pkgload]{load_pkgload()}}. See \link[roxygen2]{load} for more details.} \item{clean}{If \code{TRUE}, roxygen will delete all files previously diff --git a/man/get_current_config.Rd b/man/get_current_config.Rd new file mode 100644 index 00000000..e2ed0da8 --- /dev/null +++ b/man/get_current_config.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{get_current_config} +\alias{get_current_config} +\title{Get the path to the current config File} +\usage{ +get_current_config(path = ".") +} +\arguments{ +\item{path}{Path to start looking for the config} +} +\description{ +This function tries to guess where the golem-config file is located. +If it can't find it, this function asks the +user if they want to set the golem skeleton. +} diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index fa7de33a..4601e99d 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -1,75 +1,74 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\alias{set_golem_wd} -\alias{set_golem_name} -\alias{set_golem_version} +% Please edit documentation in R/golem-yaml-get.R, R/golem-yaml-set.R, +% R/set_golem_options.R +\name{get_golem_wd} \alias{get_golem_wd} \alias{get_golem_name} \alias{get_golem_version} +\alias{set_golem_name} +\alias{set_golem_version} +\alias{set_golem_options} \title{\code{{golem}} options} \usage{ -set_golem_options( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE +get_golem_wd(use_parent = TRUE, path = golem::pkg_path()) + +get_golem_name( + config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), + use_parent = TRUE, + path = golem::pkg_path() ) -set_golem_wd(path = golem::pkg_path(), talkative = TRUE) +get_golem_version( + config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), + use_parent = TRUE, + path = golem::pkg_path() +) set_golem_name( name = golem::pkg_name(), - path = golem::pkg_path(), + pkg = golem::pkg_path(), talkative = TRUE ) set_golem_version( version = golem::pkg_version(), - path = golem::pkg_path(), + pkg = golem::pkg_path(), talkative = TRUE ) -get_golem_wd(use_parent = TRUE, path = golem::pkg_path()) - -get_golem_name( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) - -get_golem_version( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() +set_golem_options( + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) ) } \arguments{ -\item{golem_name}{Name of the current golem.} +\item{use_parent}{\code{TRUE} to scan parent directories for +configuration files if the specified config file isn't found.} -\item{golem_version}{Version of the current golem.} +\item{path}{The path to set the golem working directory. +Note that it will be passed to \code{normalizePath}.} -\item{golem_wd}{Working directory of the current golem package.} +\item{config}{Name of configuration to read from. Defaults to +the value of the \code{R_CONFIG_ACTIVE} environment variable +("default" if the variable does not exist).} -\item{app_prod}{Is the \code{{golem}} in prod mode?} +\item{name}{The name of the app} \item{talkative}{Should the messages be printed to the console?} -\item{path}{The path to set the golem working directory. -Note that it will be passed to \code{normalizePath}.} +\item{version}{The version of the app} -\item{name}{The name of the app} +\item{golem_name}{Name of the current golem.} -\item{version}{The version of the app} +\item{golem_version}{Version of the current golem.} -\item{use_parent}{\code{TRUE} to scan parent directories for -configuration files if the specified config file isn't found.} +\item{golem_wd}{Working directory of the current golem package.} -\item{config}{Name of configuration to read from. Defaults to -the value of the \code{R_CONFIG_ACTIVE} environment variable -("default" if the variable does not exist).} +\item{app_prod}{Is the \code{{golem}} in prod mode?} } \value{ Used for side-effects for the setters, and values from the From b556255b2bafabb3e901203e62dcb626b3ba38ca Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 08:58:33 +0200 Subject: [PATCH 024/190] fix: issue with param names path is now renamed pkg --- R/desc.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/desc.R b/R/desc.R index dd120fa9..87c254c7 100644 --- a/R/desc.R +++ b/R/desc.R @@ -69,7 +69,7 @@ fill_desc <- function( ) set_golem_version( version = "0.0.0.9000", - path = path + pkg = path ) desc$set( Package = pkg_name From 0598caec2bee093d5460444d7bf9c86e90910278 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:02:51 +0200 Subject: [PATCH 025/190] refactor: explicitly set arguments --- R/set_golem_options.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R/set_golem_options.R b/R/set_golem_options.R index 6eb4ed3f..1a00c1e4 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -58,6 +58,7 @@ set_golem_options <- function( # Setting name of the golem set_golem_name( name = golem_name, + pkg = golem_wd, talkative = talkative ) @@ -68,21 +69,22 @@ set_golem_options <- function( # we use the explicit path set_golem_wd( - path = golem_wd, + pkg = golem_wd, talkative = talkative ) # Setting golem_version set_golem_version( version = golem_version, + pkg = golem_wd, talkative = talkative ) # Setting app_prod - set_golem_things( + amend_golem_config( "app_prod", app_prod, - path = golem_wd, + pkg = golem_wd, talkative = talkative ) From 682757268f10b67c4efdcbf2c281c2c5d69ffec7 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:03:05 +0200 Subject: [PATCH 026/190] fix: removed unused argument --- R/golem-yaml-get.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index d0fda7b1..96fe198d 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -13,8 +13,7 @@ get_golem_things <- function( path ) { conf_path <- get_current_config( - path, - set_options = TRUE + path ) stop_if( conf_path, From ffb491c27f438977a78ddf5ed522b02c1d00536d Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:03:34 +0200 Subject: [PATCH 027/190] doc(vignette): renames the title according to convention --- vignettes/e_config.Rmd | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 384de1ca..5b583ea4 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -1,5 +1,5 @@ --- -title: "config" +title: "e_config" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{config} @@ -27,6 +27,10 @@ knitr::opts_knit$set(root.dir = x) library(golem) ``` +```{r echo = FALSE} +old <- setwd(x) +``` + ## About `inst/golem-config.yml` When you start a new `{golem}` application, you'll find a file called `golem-config.yml` in the `inst/` folder. @@ -39,7 +43,7 @@ This config file is based on the [`{config}`](https://github.com/rstudio/config) Here is what the default config file looks like: -```{r echo = FALSE, comment= ""} +```{r echo = FALSE, comment= "", } cat( sep = "\n", readLines( @@ -60,6 +64,15 @@ These options are globally set with: set_golem_options() ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + The functions reading the options in this config file are: ```{r} @@ -76,6 +89,15 @@ set_golem_wd(".") set_golem_version("0.0.1") ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + ## Using `golem-config` @@ -95,6 +117,7 @@ amend_golem_config( ) ``` + Will result in a `golem-config.yml` file as such: ```{r echo = FALSE, comment= ""} @@ -143,4 +166,8 @@ The idea is also that the `golem-config.yml` file is shareable across `{golem}` ## Note for `{golem}` < 0.2.0 users -If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. \ No newline at end of file +If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. + +```{r echo = FALSE} +setwd(old) +``` \ No newline at end of file From 48f78feead3ac22b8ff695f032c1335995397731 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:03:52 +0200 Subject: [PATCH 028/190] fix: correct handling of paths in yaml-set --- R/golem-yaml-set.R | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 495edb3e..c4350fdc 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -63,6 +63,9 @@ set_golem_name <- function( pkg = golem::pkg_path(), talkative = TRUE ) { + + path <- path_abs(pkg) + # Changing in YAML amend_golem_config( key = "golem_name", @@ -74,7 +77,7 @@ set_golem_name <- function( # Changing in app-config.R change_app_config_name( name = name, - path = pkg + path = path ) # Changing in DESCRIPTION @@ -102,7 +105,7 @@ set_golem_version <- function( pkg = golem::pkg_path(), talkative = TRUE ) { - path <- path_abs(path) + path <- path_abs(pkg) # Changing in YAML amend_golem_config( @@ -115,7 +118,7 @@ set_golem_version <- function( desc <- desc::description$new( file = fs::path( - pkg, + path, "DESCRIPTION" ) ) From d6b5670a2a268b7bf0501678aefc16983bfed2e3 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 10:28:48 +0200 Subject: [PATCH 029/190] fix(config): unifying config manipulation All config manipulation now go through amend_golem_config. This allows to be sure that the !expr are not lost Close #709 --- R/config.R | 300 +++++++++-------- R/create_golem.R | 422 ++++++++++++------------ R/options.R | 422 +++++++++++------------- inst/shinyexample/inst/golem-config.yml | 2 +- tests/testthat/test-config.R | 108 ++++-- 5 files changed, 643 insertions(+), 611 deletions(-) diff --git a/R/config.R b/R/config.R index 64438199..cfd76718 100644 --- a/R/config.R +++ b/R/config.R @@ -1,125 +1,145 @@ +# This file contains everything related to the +# manipulation of the golem-config file + +# We first need something to guess where the file +# is. 99.99% of the time it will be +# ./inst/golem-config.yml but if for some reason +# you're somewhere else, functions still need to +# work + #' @importFrom attempt attempt is_try_error #' @importFrom fs path path_abs guess_where_config <- function( - path = ".", - file = "inst/golem-config.yml" + path = ".", + file = "inst/golem-config.yml" ) { - # Trying the path - ret_path <- path( - path, - file - ) - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } - # Trying maybe in the wd - ret_path <- "golem-config.yml" - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } - # Trying with pkgpath - ret_path <- attempt({ - path( - golem::pkg_path(), - "inst/golem-config.yml" - ) - }) - if ( - !is_try_error(ret_path) & - file_exists(ret_path) - ) { - return( - path_abs(ret_path) - ) - } - return(NULL) + # We'll try to guess where the path + # to the golem-config file is + + # This one should be correct in 99% of the case + # => current directory /inst/golem-config.yml + ret_path <- path( + path, + file + ) + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } + + # Maybe for some reason we are in inst/ + ret_path <- "golem-config.yml" + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } + + # Trying with pkg_path + ret_path <- attempt({ + path( + golem::pkg_path(), + "inst/golem-config.yml" + ) + }) + + if ( + !is_try_error(ret_path) & + file_exists(ret_path) + ) { + return( + path_abs(ret_path) + ) + } + return(NULL) } #' @importFrom fs file_copy path get_current_config <- function( - path = ".", - set_options = TRUE + path = ".", + set_options = TRUE ) { - # We check wether we can guess where the config file is - path_conf <- guess_where_config(path) - # We default to inst/ if this doesn't exist - if (is.null(path_conf)) { - path_conf <- path( - path, - "inst/golem-config.yml" - ) - } - - if (!file_exists(path_conf)) { - if (interactive()) { - ask <- yesno( - sprintf( - "The %s file doesn't exist, create?", - basename(path_conf) - ) - ) - # Return early if the user doesn't allow - if (!ask) { - return(NULL) - } - - file_copy( - path = golem_sys("shinyexample/inst/golem-config.yml"), - new_path = path( - path, - "inst/golem-config.yml" - ) - ) - file_copy( - path = golem_sys("shinyexample/R/app_config.R"), - new_path = file.path( - path, - "R/app_config.R" - ) - ) - replace_word( - path( - path, - "R/app_config.R" - ), - "shinyexample", - golem::pkg_name() - ) - if (set_options) { - set_golem_options() - } - } else { - stop( - sprintf( - "The %s file doesn't exist.", - basename(path_conf) - ) - ) - } - } - - return( - invisible(path_conf) - ) + # We check wether we can guess where the config file is + path_conf <- guess_where_config(path) + + # We default to inst/ if this doesn't exist + if (is.null(path_conf)) { + path_conf <- path( + path, + "inst/golem-config.yml" + ) + } + + if (!file_exists(path_conf)) { + if (interactive()) { + ask <- yesno( + sprintf( + "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", + basename(path_conf) + ) + ) + # Return early if the user doesn't allow + if (!ask) { + return(NULL) + } + + file_copy( + path = golem_sys("shinyexample/inst/golem-config.yml"), + new_path = path( + path, + "inst/golem-config.yml" + ) + ) + file_copy( + path = golem_sys("shinyexample/R/app_config.R"), + new_path = file.path( + path, + "R/app_config.R" + ) + ) + replace_word( + path( + path, + "R/app_config.R" + ), + "shinyexample", + golem::pkg_name() + ) + if (set_options) { + set_golem_options() + } + } else { + stop( + sprintf( + "The %s file doesn't exist.", + basename(path_conf) + ) + ) + } + } + + return( + invisible(path_conf) + ) } +# This function changes the name of the +# package in app_config when you need to +# set the {golem} name change_app_config_name <- function( - name, - path = get_golem_wd() + name, + path = get_golem_wd() ) { - pth <- fs::path(path, "R", "app_config.R") - app_config <- readLines(pth) - - where_system.file <- grep("system.file", app_config) - - app_config[ - where_system.file - ] <- sprintf( - ' system.file(..., package = "%s")', - name - ) - write(app_config, pth) + pth <- fs::path(path, "R", "app_config.R") + app_config <- readLines(pth) + + where_system.file <- grep("system.file", app_config) + + app_config[ + where_system.file + ] <- sprintf( + ' system.file(..., package = "%s")', + name + ) + write(app_config, pth) } @@ -127,20 +147,20 @@ change_app_config_name <- function( # used internally in `amend_golem_config` #' @importFrom utils modifyList find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) - conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) - - expr_list <- lapply(names(conf), function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - }) - names(expr_list) <- names(conf) - expr_list <- Filter(function(x) length(x) > 0, expr_list) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply(expr_list, add_expr_tag) - modifyList(conf, tagged_exprs) + conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) + conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) + + expr_list <- lapply(names(conf), function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + }) + names(expr_list) <- names(conf) + expr_list <- Filter(function(x) length(x) > 0, expr_list) + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply(expr_list, add_expr_tag) + modifyList(conf, tagged_exprs) } #' Amend golem config file @@ -156,23 +176,23 @@ find_and_tag_exprs <- function(conf_path) { #' #' @return Used for side effects. amend_golem_config <- function( - key, - value, - config = "default", - pkg = get_golem_wd(), - talkative = TRUE + key, + value, + config = "default", + pkg = get_golem_wd(), + talkative = TRUE ) { - conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) - invisible(TRUE) + conf_path <- get_current_config(pkg) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value + write_yaml( + conf, + conf_path + ) + invisible(TRUE) } diff --git a/R/create_golem.R b/R/create_golem.R index 35e9e8f2..874f40cb 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -32,220 +32,220 @@ #' #' @return The path, invisibly. create_golem <- function( - path, - check_name = TRUE, - open = TRUE, - overwrite = FALSE, - package_name = basename(path), - without_comments = FALSE, - project_hook = golem::project_hook, - with_git = FALSE, - ... + path, + check_name = TRUE, + open = TRUE, + overwrite = FALSE, + package_name = basename(path), + without_comments = FALSE, + project_hook = golem::project_hook, + with_git = FALSE, + ... ) { - path <- normalizePath(path, mustWork = FALSE) - - if (check_name) { - cat_rule("Checking package name") - getFromNamespace("check_package_name", "usethis")(package_name) - cat_green_tick("Valid package name") - } - - - if (dir.exists(path)) { - if (!isTRUE(overwrite)) { - stop( - paste( - "Project directory already exists. \n", - "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", - "Be careful this will restore a brand new golem. \n", - "You might be at risk of losing your work !" - ), - call. = FALSE - ) - } else { - cat_red_bullet("Overwriting existing project.") - } - } else { - cat_rule("Creating dir") - usethis::create_project( - path = path, - open = FALSE, - ) - cat_green_tick("Created package directory") - } - - - cat_rule("Copying package skeleton") - from <- golem_sys("shinyexample") - - # Copy over whole directory - dir_copy(path = from, new_path = path, overwrite = TRUE) - - # Listing copied files ***from source directory*** - copied_files <- list.files( - path = from, - full.names = FALSE, - all.files = TRUE, - recursive = TRUE - ) - - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path, f) - - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path, gsub("REMOVEME", "", f)) - } - - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } - - cat_green_tick("Copied app skeleton") - - - cat_rule("Setting the default config") - - yml_path <- file.path(path, "inst/golem-config.yml") - conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - yaml_golem_wd <- "here::here()" - attr(yaml_golem_wd, "tag") <- "!expr" - conf$dev$golem_wd <- yaml_golem_wd - conf$default$golem_name <- package_name - conf$default$golem_version <- "0.0.0.9000" - write_yaml(conf, yml_path) - - cat_green_tick("Configured app") - - - cat_rule("Running project hook function") - - old <- setwd(path) - # TODO fix - # for some weird reason test() fails here when using golem::create_golem - # and I don't have time to search why rn - if (substitute(project_hook) == "golem::project_hook") { - project_hook <- getFromNamespace("project_hook", "golem") - } - project_hook(path = path, package_name = package_name, ...) - setwd(old) - - cat_green_tick("All set") - - - if (isTRUE(without_comments)) { - files <- list.files( - path = c( - file.path(path, "dev"), - file.path(path, "R") - ), - full.names = TRUE - ) - for (file in files) { - remove_comments(file) - } - } - - - if (isTRUE(with_git)) { - cat_rule("Initializing git repository") - git_output <- system( - command = paste("git init", path), - ignore.stdout = TRUE, - ignore.stderr = TRUE - ) - if (git_output) { - cat_red_bullet("Error initializing git epository") - } else { - cat_green_tick("Initialized git repository") - } - } - - - old <- setwd(path) - use_latest_dependencies() - - # No .Rprofile for now - # cat_rule("Appending .Rprofile") - # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) - # write("home_profile <- file.path(", ".Rprofile", append = TRUE) - # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) - # write(" \".Rprofile\"", ".Rprofile", append = TRUE) - # write(")", ".Rprofile", append = TRUE) - # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) - # write(" source(home_profile)", ".Rprofile", append = TRUE) - # write("}", ".Rprofile", append = TRUE) - # write("rm(home_profile)", ".Rprofile", append = TRUE) - # - # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) - # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) - # cat_green_tick("Appended") - - setwd(old) - - - cat_rule("Done") - cat_line( - paste0( - "A new golem named ", - package_name, - " was created at ", - normalizePath(path), - " .\n", - "To continue working on your app, start editing the 01_start.R file." - ) - ) - - - if (isTRUE(open)) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { - rstudioapi::openProject(path = path) - } else { - setwd(path) - } - } - - - return( - invisible( - normalizePath(path) - ) - ) + path <- normalizePath(path, mustWork = FALSE) + + if (check_name) { + cat_rule("Checking package name") + getFromNamespace("check_package_name", "usethis")(package_name) + cat_green_tick("Valid package name") + } + + + if (dir.exists(path)) { + if (!isTRUE(overwrite)) { + stop( + paste( + "Project directory already exists. \n", + "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", + "Be careful this will restore a brand new golem. \n", + "You might be at risk of losing your work !" + ), + call. = FALSE + ) + } else { + cat_red_bullet("Overwriting existing project.") + } + } else { + cat_rule("Creating dir") + usethis::create_project( + path = path, + open = FALSE, + ) + cat_green_tick("Created package directory") + } + + + cat_rule("Copying package skeleton") + from <- golem_sys("shinyexample") + + # Copy over whole directory + dir_copy(path = from, new_path = path, overwrite = TRUE) + + # Listing copied files ***from source directory*** + copied_files <- list.files( + path = from, + full.names = FALSE, + all.files = TRUE, + recursive = TRUE + ) + + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } + + cat_green_tick("Copied app skeleton") + + + cat_rule("Setting the default config") + + yml_path <- file.path(path, "inst/golem-config.yml") + conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) + yaml_golem_wd <- "golem::pkg_path()" + attr(yaml_golem_wd, "tag") <- "!expr" + conf$dev$golem_wd <- yaml_golem_wd + conf$default$golem_name <- package_name + conf$default$golem_version <- "0.0.0.9000" + write_yaml(conf, yml_path) + + cat_green_tick("Configured app") + + + cat_rule("Running project hook function") + + old <- setwd(path) + # TODO fix + # for some weird reason test() fails here when using golem::create_golem + # and I don't have time to search why rn + if (substitute(project_hook) == "golem::project_hook") { + project_hook <- getFromNamespace("project_hook", "golem") + } + project_hook(path = path, package_name = package_name, ...) + setwd(old) + + cat_green_tick("All set") + + + if (isTRUE(without_comments)) { + files <- list.files( + path = c( + file.path(path, "dev"), + file.path(path, "R") + ), + full.names = TRUE + ) + for (file in files) { + remove_comments(file) + } + } + + + if (isTRUE(with_git)) { + cat_rule("Initializing git repository") + git_output <- system( + command = paste("git init", path), + ignore.stdout = TRUE, + ignore.stderr = TRUE + ) + if (git_output) { + cat_red_bullet("Error initializing git epository") + } else { + cat_green_tick("Initialized git repository") + } + } + + + old <- setwd(path) + use_latest_dependencies() + + # No .Rprofile for now + # cat_rule("Appending .Rprofile") + # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) + # write("home_profile <- file.path(", ".Rprofile", append = TRUE) + # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) + # write(" \".Rprofile\"", ".Rprofile", append = TRUE) + # write(")", ".Rprofile", append = TRUE) + # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) + # write(" source(home_profile)", ".Rprofile", append = TRUE) + # write("}", ".Rprofile", append = TRUE) + # write("rm(home_profile)", ".Rprofile", append = TRUE) + # + # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) + # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) + # cat_green_tick("Appended") + + setwd(old) + + + cat_rule("Done") + cat_line( + paste0( + "A new golem named ", + package_name, + " was created at ", + normalizePath(path), + " .\n", + "To continue working on your app, start editing the 01_start.R file." + ) + ) + + + if (isTRUE(open)) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { + rstudioapi::openProject(path = path) + } else { + setwd(path) + } + } + + + return( + invisible( + normalizePath(path) + ) + ) } # to be used in RStudio "new project" GUI create_golem_gui <- function(path, ...) { - dots <- list(...) - attempt::stop_if_not( - dots$project_hook, - ~ grepl("::", .x), - "{golem} project templates must be explicitely namespaced (pkg::fun)" - ) - splt <- strsplit(dots$project_hook, "::") - project_hook <- getFromNamespace( - splt[[1]][2], - splt[[1]][1] - ) - create_golem( - path = path, - open = FALSE, - without_comments = dots$without_comments, - project_hook = project_hook, - check_name = dots$check_name, - with_git = dots$with_git - ) + dots <- list(...) + attempt::stop_if_not( + dots$project_hook, + ~ grepl("::", .x), + "{golem} project templates must be explicitely namespaced (pkg::fun)" + ) + splt <- strsplit(dots$project_hook, "::") + project_hook <- getFromNamespace( + splt[[1]][2], + splt[[1]][1] + ) + create_golem( + path = path, + open = FALSE, + without_comments = dots$without_comments, + project_hook = project_hook, + check_name = dots$check_name, + with_git = dots$with_git + ) } diff --git a/R/options.R b/R/options.R index 134eb2a9..c639967f 100644 --- a/R/options.R +++ b/R/options.R @@ -31,303 +31,273 @@ #' #' @export #' @importFrom attempt stop_if_not -#' @importFrom yaml read_yaml write_yaml #' @importFrom usethis proj_set #' #' @return Used for side-effects for the setters, and values from the #' config in the getters. set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE ) { - change_app_config_name( - name = golem_name, - path = golem_wd - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } + # TODO here we'll run the + # golem_install_dev_pkg() function - conf_path <- get_current_config( - golem_wd, - set_options = FALSE - ) + if (talkative) { + cli::cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path + if (golem_wd == golem::pkg_path()) { + path <- "golem::pkg_path()" + attr(path, "tag") <- "!expr" + } else { + path <- golem_wd + } - cat_if_talk( - "Setting {golem} options in `golem-config.yml`", - fun = cli::cat_rule - ) + set_golem_wd( + path = path, + talkative = talkative + ) - conf <- read_yaml(conf_path, eval.expr = TRUE) + # Setting name of the golem + set_golem_name( + name = golem_name, + talkative = talkative + ) - # Setting wd - if (golem_wd == here::here()) { - path <- "here::here()" - attr(path, "tag") <- "!expr" - } else { - path <- golem_wd - } + # Setting golem_version + set_golem_version( + version = golem_version, + talkative = talkative + ) - cat_if_talk( - sprintf( - "Setting `golem_wd` to %s", - path - ) - ) - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) + # Setting app_prod + set_golem_things( + "app_prod", + app_prod, + path = golem_wd, + talkative = talkative + ) - conf$dev$golem_wd <- path + # This part is for {usethis} and {here} + if (talkative) { + cli::cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } - # Setting name of the golem - cat_if_talk( - sprintf( - "Setting `golem_name` to %s", - golem_name - ) - ) - conf$default$golem_name <- golem_name - - # Setting golem_version - cat_if_talk( - sprintf( - "Setting `golem_version` to %s", - golem_version - ) - ) - conf$default$golem_version <- as.character(golem_version) - - # Setting app_prod - cat_if_talk( - sprintf( - "Setting `app_prod` to %s", - app_prod - ) - ) - conf$default$app_prod <- app_prod - - # Export - write_yaml( - conf, - conf_path - ) - - cat_if_talk( - "Setting {usethis} project as `golem_wd`", - fun = cli::cat_rule - ) - proj_set(golem_wd) + proj_set(golem_wd) } -#' @importFrom yaml read_yaml write_yaml set_golem_things <- function( - key, - value, - path, - talkative, - config = "default" + key, + value, + path, + talkative, + config = "default" ) { - conf_path <- get_current_config(path, set_options = FALSE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } + conf_path <- get_current_config(path, set_options = FALSE) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + cat_if_talk <- function(..., fun = cat_green_tick) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } - conf <- read_yaml(conf_path, eval.expr = TRUE) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) + amend_golem_config( + key = key, + value = value, + config = config + ) - invisible(path) + invisible(path) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_wd <- function( - path = golem::pkg_path(), - talkative = TRUE + path = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) - # Setting wd + path <- path_abs(path) - if (path == here::here()) { - path <- "here::here()" - attr(path, "tag") <- "!expr" - } + if (path == golem::pkg_path()) { + path <- "golem::pkg_path()" + attr(path, "tag") <- "!expr" + } - set_golem_things( - "golem_wd", - path, - path, - talkative = talkative, - config = "dev" - ) + set_golem_things( + "golem_wd", + path, + path, + talkative = talkative, + config = "dev" + ) - invisible(path) + invisible(path) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_name <- function( - name = golem::pkg_name(), - path = golem::pkg_path(), - talkative = TRUE + name = golem::pkg_name(), + path = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) - # Changing in YAML - set_golem_things( - "golem_name", - name, - path, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = path - ) + path <- path_abs(path) + # Changing in YAML + set_golem_things( + "golem_name", + name, + path, + talkative = talkative + ) + # Changing in app-config.R + change_app_config_name( + name = name, + path = path + ) - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) + # Changing in DESCRIPTION + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(name) + invisible(name) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_version <- function( - version = golem::pkg_version(), - path = golem::pkg_path(), - talkative = TRUE + version = golem::pkg_version(), + path = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) - set_golem_things( - "golem_version", - as.character(version), - path, - talkative = talkative - ) - desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) + path <- path_abs(path) + set_golem_things( + "golem_version", + as.character(version), + path, + talkative = talkative + ) + desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(version) + invisible(version) } #' @importFrom config get get_golem_things <- function( - value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path + value, + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path ) { - conf_path <- get_current_config(path, set_options = TRUE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) + conf_path <- get_current_config(path, set_options = TRUE) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) } #' @export #' @rdname golem_opts get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() + use_parent = TRUE, + path = golem::pkg_path() ) { - get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) + get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) } #' @export #' @rdname golem_opts get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() ) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - nm + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + nm } #' @export #' @rdname golem_opts get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() ) { - get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) + get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) } diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml index 6ce2c787..879263c9 100644 --- a/inst/shinyexample/inst/golem-config.yml +++ b/inst/shinyexample/inst/golem-config.yml @@ -7,5 +7,5 @@ production: app_prod: yes dev: - golem_wd: !exp here::here() + golem_wd: !exp golem::golem_path() diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index c5d52490..69400c0f 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -1,36 +1,78 @@ test_that("config works", { - with_dir(pkg, { - expect_equal(get_golem_name(), fakename) - expect_equal(get_golem_version(), "0.0.0.9000") - expect_equal(normalizePath(get_golem_wd(), mustWork = FALSE), normalizePath(pkg, mustWork = FALSE)) - amend_golem_config( - key = "where", - value = "indev" - ) - amend_golem_config( - key = "where", - value = "inprod", - config = "production" - ) + with_dir(pkg, { + # We'll try to be sure that + # golem_wd: !expr golem::pkg_path() + # is kept along the way + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + get_golem_name(), + fakename + ) + expect_equal( + get_golem_version(), + "0.0.0.9000" + ) + expect_equal( + normalizePath(get_golem_wd(), mustWork = FALSE), + normalizePath(pkg, mustWork = FALSE) + ) + amend_golem_config( + key = "where", + value = "indev" + ) + amend_golem_config( + key = "where", + value = "inprod", + config = "production" + ) + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + config::get("where", file = "inst/golem-config.yml"), + "indev" + ) + expect_equal( + config::get("where", config = "production", file = "inst/golem-config.yml"), + "inprod" + ) + where_conf <- withr::with_envvar( + c("R_CONFIG_ACTIVE" = "production"), + { + config::get("where", file = "inst/golem-config.yml") + } + ) + expect_equal( + where_conf, + "inprod" + ) + set_golem_name("plop") + expect_equal( + get_golem_name(), + "plop" + ) + set_golem_name(fakename) + set_golem_version("0.0.0.9001") + expect_equal( + get_golem_version(), + "0.0.0.9001" + ) + set_golem_version("0.0.0.9000") - expect_equal(config::get("where", file = "inst/golem-config.yml"), "indev") - expect_equal(config::get("where", config = "production", file = "inst/golem-config.yml"), "inprod") - where_conf <- withr::with_envvar( - c("R_CONFIG_ACTIVE" = "production"), - { - config::get("where", file = "inst/golem-config.yml") - } - ) - expect_equal(where_conf, "inprod") - set_golem_name("plop") - expect_equal(get_golem_name(), "plop") - set_golem_name(fakename) - set_golem_version("0.0.0.9001") - expect_equal(get_golem_version(), "0.0.0.9001") - set_golem_version("0.0.0.9000") - - set_golem_wd(normalizePath("inst")) - expect_equal(normalizePath(get_golem_wd()), normalizePath("inst")) - set_golem_wd(pkg) - }) + set_golem_wd(normalizePath("inst")) + expect_equal( + normalizePath(get_golem_wd()), + normalizePath("inst") + ) + set_golem_wd(pkg) + # Be sure that after setting the stuff the wd is still here::here() + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + }) }) From 4d3a5bb360af41924fa9ea35007bb8130dbf4682 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 12:42:08 +0200 Subject: [PATCH 030/190] fix(CI): correct path check --- inst/mantests/build.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 8257554c..f297869a 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -248,7 +248,7 @@ withr::with_tempdir({ golem::set_golem_options() expect_equal( golem::get_golem_wd(), - here::here() + golem::golem_path() ) expect_equal( golem::get_golem_name(), From 6aa325f8d2730dd96cdb90a69b9f336fd4e9cac2 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 12:52:40 +0200 Subject: [PATCH 031/190] fix(typo): pkg_path instead of golem_path --- inst/mantests/build.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index f297869a..e78800cf 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -248,7 +248,7 @@ withr::with_tempdir({ golem::set_golem_options() expect_equal( golem::get_golem_wd(), - golem::golem_path() + golem::pkg_path() ) expect_equal( golem::get_golem_name(), From 70d2471e9fe763e02ba0d4797be5ff8c51b2604e Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 19:05:53 +0200 Subject: [PATCH 032/190] feature(creation): create golem only set the app name There is no need to set things that are already set in the yaml --- R/config.R | 21 +++++++++ R/create_golem.R | 112 +++++++++++++++++++++++++++-------------------- R/options.R | 4 +- 3 files changed, 88 insertions(+), 49 deletions(-) diff --git a/R/config.R b/R/config.R index cfd76718..bedce9dd 100644 --- a/R/config.R +++ b/R/config.R @@ -188,6 +188,27 @@ amend_golem_config <- function( is.null, "Unable to retrieve golem config file." ) + cat_if_talk <- function(..., fun = cat_green_tick) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } + conf <- find_and_tag_exprs(conf_path) conf[[config]][[key]] <- value write_yaml( diff --git a/R/create_golem.R b/R/create_golem.R index 874f40cb..7af15cb9 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -1,3 +1,36 @@ +replace_package_name <- function( + copied_files, + package_name, + path_to_golem +) { + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path_to_golem, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path_to_golem, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } +} + + #' Create a package for a Shiny App using `{golem}` #' #' @param path Name of the folder to create the package in. @@ -42,7 +75,7 @@ create_golem <- function( with_git = FALSE, ... ) { - path <- normalizePath(path, mustWork = FALSE) + path_to_golem <- normalizePath(path, mustWork = FALSE) if (check_name) { cat_rule("Checking package name") @@ -51,7 +84,7 @@ create_golem <- function( } - if (dir.exists(path)) { + if (dir.exists(path_to_golem)) { if (!isTRUE(overwrite)) { stop( paste( @@ -68,7 +101,7 @@ create_golem <- function( } else { cat_rule("Creating dir") usethis::create_project( - path = path, + path = path_to_golem, open = FALSE, ) cat_green_tick("Created package directory") @@ -79,7 +112,11 @@ create_golem <- function( from <- golem_sys("shinyexample") # Copy over whole directory - dir_copy(path = from, new_path = path, overwrite = TRUE) + dir_copy( + path = from, + new_path = path_to_golem, + overwrite = TRUE + ) # Listing copied files ***from source directory*** copied_files <- list.files( @@ -89,59 +126,41 @@ create_golem <- function( recursive = TRUE ) - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path, f) + replace_package_name( + copied_files, + package_name, + path_to_golem + ) - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path, gsub("REMOVEME", "", f)) - } - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } cat_green_tick("Copied app skeleton") + old <- setwd(path_to_golem) - cat_rule("Setting the default config") + cat_rule("Changing the app name") - yml_path <- file.path(path, "inst/golem-config.yml") - conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - yaml_golem_wd <- "golem::pkg_path()" - attr(yaml_golem_wd, "tag") <- "!expr" - conf$dev$golem_wd <- yaml_golem_wd - conf$default$golem_name <- package_name - conf$default$golem_version <- "0.0.0.9000" - write_yaml(conf, yml_path) + set_golem_name( + package_name, + path_to_golem + ) cat_green_tick("Configured app") - cat_rule("Running project hook function") - old <- setwd(path) # TODO fix # for some weird reason test() fails here when using golem::create_golem # and I don't have time to search why rn if (substitute(project_hook) == "golem::project_hook") { project_hook <- getFromNamespace("project_hook", "golem") } - project_hook(path = path, package_name = package_name, ...) + project_hook( + path = path_to_golem, + package_name = package_name, + ... + ) + setwd(old) cat_green_tick("All set") @@ -150,8 +169,8 @@ create_golem <- function( if (isTRUE(without_comments)) { files <- list.files( path = c( - file.path(path, "dev"), - file.path(path, "R") + file.path(path_to_golem, "dev"), + file.path(path_to_golem, "R") ), full.names = TRUE ) @@ -164,7 +183,7 @@ create_golem <- function( if (isTRUE(with_git)) { cat_rule("Initializing git repository") git_output <- system( - command = paste("git init", path), + command = paste("git init", path_to_golem), ignore.stdout = TRUE, ignore.stderr = TRUE ) @@ -176,7 +195,7 @@ create_golem <- function( } - old <- setwd(path) + old <- setwd(path_to_golem) use_latest_dependencies() # No .Rprofile for now @@ -197,14 +216,14 @@ create_golem <- function( setwd(old) - cat_rule("Done") + cat_line( paste0( "A new golem named ", package_name, " was created at ", - normalizePath(path), + path_to_golem, " .\n", "To continue working on your app, start editing the 01_start.R file." ) @@ -219,10 +238,9 @@ create_golem <- function( } } - return( invisible( - normalizePath(path) + path_to_golem ) ) } diff --git a/R/options.R b/R/options.R index c639967f..9685ae5e 100644 --- a/R/options.R +++ b/R/options.R @@ -149,11 +149,11 @@ set_golem_wd <- function( path = golem::pkg_path(), talkative = TRUE ) { - path <- path_abs(path) - if (path == golem::pkg_path()) { path <- "golem::pkg_path()" attr(path, "tag") <- "!expr" + } else { + path <- path_abs(path) } set_golem_things( From 9a988e29a21fd01ec09fd465bc47428d1a0ce78e Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 19:11:47 +0200 Subject: [PATCH 033/190] feat(create_golem): No name change in the YAML The name is already set by the function that gsub in the files so there is no need to do it one extra time --- R/create_golem.R | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/R/create_golem.R b/R/create_golem.R index 7af15cb9..b531cc77 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -132,21 +132,10 @@ create_golem <- function( path_to_golem ) - - cat_green_tick("Copied app skeleton") old <- setwd(path_to_golem) - cat_rule("Changing the app name") - - set_golem_name( - package_name, - path_to_golem - ) - - cat_green_tick("Configured app") - cat_rule("Running project hook function") # TODO fix From ca5a007e51ba27a5cb239b4055fc40d8e2635aee Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Sat, 2 Jul 2022 19:23:16 +0200 Subject: [PATCH 034/190] fix(option-setting): don't change to expr before passing to set_golem_wd() The attr is not set before being passed to set_golem_wd() in set_golem_options() --- R/options.R | 55 ++++++++++++++--------------------------------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/R/options.R b/R/options.R index 9685ae5e..97d9895f 100644 --- a/R/options.R +++ b/R/options.R @@ -40,7 +40,8 @@ set_golem_options <- function( golem_version = golem::pkg_version(), golem_wd = golem::pkg_path(), app_prod = FALSE, - talkative = TRUE + talkative = TRUE, + config_file = path(golem_wd, "inst/golem-config.yml") ) { # TODO here we'll run the @@ -57,12 +58,6 @@ set_golem_options <- function( # to keep the wd as an expr if it is the # same as golem::pkg_path(), otherwise # we use the explicit path - if (golem_wd == golem::pkg_path()) { - path <- "golem::pkg_path()" - attr(path, "tag") <- "!expr" - } else { - path <- golem_wd - } set_golem_wd( path = path, @@ -103,40 +98,15 @@ set_golem_things <- function( key, value, path, - talkative, + talkative = TRUE, config = "default" ) { - conf_path <- get_current_config(path, set_options = FALSE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } - - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) - - if (key == "golem_wd") { - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) - } - amend_golem_config( key = key, value = value, - config = config + config = config, + pkg = path, + talkative = talkative ) invisible(path) @@ -149,16 +119,19 @@ set_golem_wd <- function( path = golem::pkg_path(), talkative = TRUE ) { - if (path == golem::pkg_path()) { - path <- "golem::pkg_path()" - attr(path, "tag") <- "!expr" + if ( + path == "golem::pkg_path()" | + path == golem::pkg_path() + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" } else { - path <- path_abs(path) + golem_yaml_path <- path_abs(path) } set_golem_things( "golem_wd", - path, + golem_yaml_path, path, talkative = talkative, config = "dev" From ddf241ca3d135115c71543b587b1104cbe6ffa48 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:35:23 +0200 Subject: [PATCH 035/190] chore(file): file splitting & renaming The yaml manimpulation functions are now separated in various files --- R/golem-yaml-get.R | 70 +++++++++++ R/golem-yaml-set.R | 130 ++++++++++++++++++++ R/golem-yaml-utils.R | 110 +++++++++++++++++ R/options.R | 276 ------------------------------------------ R/set_golem_options.R | 97 +++++++++++++++ 5 files changed, 407 insertions(+), 276 deletions(-) create mode 100644 R/golem-yaml-get.R create mode 100644 R/golem-yaml-set.R create mode 100644 R/golem-yaml-utils.R delete mode 100644 R/options.R create mode 100644 R/set_golem_options.R diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R new file mode 100644 index 00000000..3c93ae29 --- /dev/null +++ b/R/golem-yaml-get.R @@ -0,0 +1,70 @@ + +#' @importFrom config get +get_golem_things <- function( + value, + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path +) { + conf_path <- get_current_config(path, set_options = TRUE) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) +} + + +#' @export +#' @rdname golem_opts +get_golem_wd <- function( + use_parent = TRUE, + path = golem::pkg_path() +) { + get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) +} + +#' @export +#' @rdname golem_opts +get_golem_name <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() +) { + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + nm +} + +#' @export +#' @rdname golem_opts +get_golem_version <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = golem::pkg_path() +) { + get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) +} diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R new file mode 100644 index 00000000..441f74ab --- /dev/null +++ b/R/golem-yaml-set.R @@ -0,0 +1,130 @@ + +#' Set things in golem config +#' +#' @param key,value entries in the yaml +#' @param path path to the config file +#' @param talkative Should things be printed? +#' @param config config context to write to +#' +#' @noRd +#' +# set_golem_things <- function( +# key, +# value, +# config_file, +# talkative = TRUE, +# config = "default" +# ) { +# amend_golem_config( +# key = key, +# value = value, +# config = config, +# pkg = config_file, +# talkative = talkative +# ) + +# invisible(path) +# } + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_wd <- function( + golem_wd = golem::pkg_path(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + if ( + golem_wd == "golem::pkg_path()" | + golem_wd == golem::pkg_path() + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" + } else { + golem_yaml_path <- path_abs(golem_wd) + } + + amend_golem_config( + key = "golem_wd", + value = golem_yaml_path, + config = "dev", + pkg = pkg, + talkative = talkative + ) + + invisible(path) +} + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_name <- function( + name = golem::pkg_name(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + # Changing in YAML + amend_golem_config( + key = "golem_name", + value = name, + config = "default", + pkg = pkg, + talkative = talkative + ) + # Changing in app-config.R + change_app_config_name( + name = name, + path = pkg + ) + + # Changing in DESCRIPTION + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) + + invisible(name) +} + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_version <- function( + version = golem::pkg_version(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + path <- path_abs(path) + + # Changing in YAML + amend_golem_config( + key = "golem_version", + value = as.character(version), + config = "default", + pkg = pkg, + talkative = talkative + ) + + desc <- desc::description$new( + file = fs::path( + pkg, + "DESCRIPTION" + ) + ) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) + + invisible(version) +} diff --git a/R/golem-yaml-utils.R b/R/golem-yaml-utils.R new file mode 100644 index 00000000..0e025655 --- /dev/null +++ b/R/golem-yaml-utils.R @@ -0,0 +1,110 @@ +# This file contains all the necessary functions +# required to manipualte the golem-config file. + +# find and tag expressions in a yaml +# used internally in `amend_golem_config` +# This is an utilitary function to prevent +# the !expr from being lost in translation +# when manipulating the yaml +# +#' @importFrom utils modifyList +find_and_tag_exprs <- function(conf_path) { + conf <- yaml::read_yaml( + conf_path, + eval.expr = FALSE + ) + conf.eval <- yaml::read_yaml( + conf_path, + eval.expr = TRUE + ) + + expr_list <- lapply( + names(conf), + function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + } + ) + + names(expr_list) <- names(conf) + + expr_list <- Filter( + function(x) length(x) > 0, + expr_list + ) + + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply( + expr_list, + add_expr_tag + ) + + modifyList( + conf, + tagged_exprs + ) +} + +#' Amend golem config file +#' +#' @param key key of the value to add in `config` +#' @inheritParams config::get +#' @inheritParams add_module +#' @inheritParams set_golem_options +#' +#' @export +#' @importFrom yaml read_yaml write_yaml +#' @importFrom attempt stop_if +#' +#' @return Used for side effects. +amend_golem_config <- function( + key, + value, + config = "default", + pkg = golem::pkg_path(), + talkative = TRUE +) { + conf_path <- get_current_config(pkg) + + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + + cat_if_talk <- function( + ..., + fun = cat_green_tick + ) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } + + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value + + write_yaml( + conf, + conf_path + ) + + invisible(TRUE) +} diff --git a/R/options.R b/R/options.R deleted file mode 100644 index 97d9895f..00000000 --- a/R/options.R +++ /dev/null @@ -1,276 +0,0 @@ -#' `{golem}` options -#' -#' Set and get a series of options to be used with `{golem}`. -#' These options are found inside the `golem-config.yml` file, found in most cases -#' inside the `inst` folder. -#' -#' @section Set Functions: -#' + `set_golem_options()` sets all the options, with the defaults from the functions below. -#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. -#' + `set_golem_name()` defaults `golem::pkg_name()` -#' + `set_golem_version()` defaults `golem::pkg_version()` -#' -#' @section Get Functions: -#' Reads the information from `golem-config.yml` -#' + `get_golem_wd()` -#' + `get_golem_name()` -#' + `get_golem_version()` -#' -#' @param golem_name Name of the current golem. -#' @param golem_version Version of the current golem. -#' @param golem_wd Working directory of the current golem package. -#' @param app_prod Is the `{golem}` in prod mode? -#' @param path The path to set the golem working directory. -#' Note that it will be passed to `normalizePath`. -#' @param talkative Should the messages be printed to the console? -#' @param name The name of the app -#' @param version The version of the app -#' @inheritParams config::get -#' -#' @rdname golem_opts -#' -#' @export -#' @importFrom attempt stop_if_not -#' @importFrom usethis proj_set -#' -#' @return Used for side-effects for the setters, and values from the -#' config in the getters. -set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE, - config_file = path(golem_wd, "inst/golem-config.yml") -) { - - # TODO here we'll run the - # golem_install_dev_pkg() function - - if (talkative) { - cli::cat_rule( - "Setting {golem} options in `golem-config.yml`" - ) - } - - # Let's start with wd - # Basically here the idea is to be able - # to keep the wd as an expr if it is the - # same as golem::pkg_path(), otherwise - # we use the explicit path - - set_golem_wd( - path = path, - talkative = talkative - ) - - # Setting name of the golem - set_golem_name( - name = golem_name, - talkative = talkative - ) - - # Setting golem_version - set_golem_version( - version = golem_version, - talkative = talkative - ) - - # Setting app_prod - set_golem_things( - "app_prod", - app_prod, - path = golem_wd, - talkative = talkative - ) - - # This part is for {usethis} and {here} - if (talkative) { - cli::cat_rule( - "Setting {usethis} project as `golem_wd`" - ) - } - - proj_set(golem_wd) -} - -set_golem_things <- function( - key, - value, - path, - talkative = TRUE, - config = "default" -) { - amend_golem_config( - key = key, - value = value, - config = config, - pkg = path, - talkative = talkative - ) - - invisible(path) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_wd <- function( - path = golem::pkg_path(), - talkative = TRUE -) { - if ( - path == "golem::pkg_path()" | - path == golem::pkg_path() - ) { - golem_yaml_path <- "golem::pkg_path()" - attr(golem_yaml_path, "tag") <- "!expr" - } else { - golem_yaml_path <- path_abs(path) - } - - set_golem_things( - "golem_wd", - golem_yaml_path, - path, - talkative = talkative, - config = "dev" - ) - - invisible(path) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_name <- function( - name = golem::pkg_name(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - # Changing in YAML - set_golem_things( - "golem_name", - name, - path, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = path - ) - - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(name) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_version <- function( - version = golem::pkg_version(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - set_golem_things( - "golem_version", - as.character(version), - path, - talkative = talkative - ) - desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(version) -} - -#' @importFrom config get -get_golem_things <- function( - value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path -) { - conf_path <- get_current_config(path, set_options = TRUE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) -} - - -#' @export -#' @rdname golem_opts -get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) -} - -#' @export -#' @rdname golem_opts -get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - nm -} - -#' @export -#' @rdname golem_opts -get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) -} diff --git a/R/set_golem_options.R b/R/set_golem_options.R new file mode 100644 index 00000000..c35ac8cc --- /dev/null +++ b/R/set_golem_options.R @@ -0,0 +1,97 @@ +#' `{golem}` options +#' +#' Set and get a series of options to be used with `{golem}`. +#' These options are found inside the `golem-config.yml` file, found in most cases +#' inside the `inst` folder. +#' +#' @section Set Functions: +#' + `set_golem_options()` sets all the options, with the defaults from the functions below. +#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. +#' + `set_golem_name()` defaults `golem::pkg_name()` +#' + `set_golem_version()` defaults `golem::pkg_version()` +#' +#' @section Get Functions: +#' Reads the information from `golem-config.yml` +#' + `get_golem_wd()` +#' + `get_golem_name()` +#' + `get_golem_version()` +#' +#' @param golem_name Name of the current golem. +#' @param golem_version Version of the current golem. +#' @param golem_wd Working directory of the current golem package. +#' @param app_prod Is the `{golem}` in prod mode? +#' @param path The path to set the golem working directory. +#' Note that it will be passed to `normalizePath`. +#' @param talkative Should the messages be printed to the console? +#' @param name The name of the app +#' @param version The version of the app +#' @inheritParams config::get +#' +#' @rdname golem_opts +#' +#' @export +#' @importFrom attempt stop_if_not +#' @importFrom usethis proj_set +#' +#' @return Used for side-effects for the setters, and values from the +#' config in the getters. +set_golem_options <- function( + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) +) { + + # TODO here we'll run the + # golem_install_dev_pkg() function + + if (talkative) { + cli::cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } + + # Let's do this in the order of the + # parameters + # Setting name of the golem + set_golem_name( + name = golem_name, + talkative = talkative + ) + + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path + + set_golem_wd( + path = golem_wd, + talkative = talkative + ) + + # Setting golem_version + set_golem_version( + version = golem_version, + talkative = talkative + ) + + # Setting app_prod + set_golem_things( + "app_prod", + app_prod, + path = golem_wd, + talkative = talkative + ) + + # This part is for {usethis} and {here} + if (talkative) { + cli::cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } + + proj_set(golem_wd) +} From 6615842c12751ebe3fe4d4332a22f7d33f12c998 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:37:12 +0200 Subject: [PATCH 036/190] fix(config): Use the correct env var in get_golem_* The get_golem_* didn't rely on the GOLEM_CONFIG_ACTIVE env_var --- R/golem-yaml-get.R | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index 3c93ae29..976b1a7c 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -2,7 +2,13 @@ #' @importFrom config get get_golem_things <- function( value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + config = config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path ) { @@ -38,7 +44,13 @@ get_golem_wd <- function( #' @export #' @rdname golem_opts get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { @@ -57,7 +69,13 @@ get_golem_name <- function( #' @export #' @rdname golem_opts get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { From 4e221a66b630e4ee48c121a8056e13ea51e70d38 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:37:46 +0200 Subject: [PATCH 037/190] fix(config): golem_wd is now golem::pkg_path() --- inst/shinyexample/inst/golem-config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml index 879263c9..3e66f3fb 100644 --- a/inst/shinyexample/inst/golem-config.yml +++ b/inst/shinyexample/inst/golem-config.yml @@ -1,11 +1,11 @@ -default: +default: golem_name: shinyexample golem_version: 0.0.0.9000 app_prod: no -production: +production: app_prod: yes - -dev: - golem_wd: !exp golem::golem_path() + +dev: + golem_wd: !expr golem::pkg_path() From 15df8751bfe386864134e57b3fe51ac1af1e53aa Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:38:50 +0200 Subject: [PATCH 038/190] chore(style): Change the indentation of param --- R/golem-yaml-get.R | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index 976b1a7c..d4f23bd3 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -2,13 +2,13 @@ #' @importFrom config get get_golem_things <- function( value, - config = config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path ) { @@ -45,12 +45,12 @@ get_golem_wd <- function( #' @rdname golem_opts get_golem_name <- function( config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { @@ -70,12 +70,12 @@ get_golem_name <- function( #' @rdname golem_opts get_golem_version <- function( config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), use_parent = TRUE, path = golem::pkg_path() ) { From 1b929655f8946bc350f4a7302b99cc6711aa0d3b Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Mon, 4 Jul 2022 07:44:23 +0200 Subject: [PATCH 039/190] feat(config): get_golem_* now tries to guess if not found in yaml + get_golem_wd tries golem::pkg_path + get_golem_name tries pkg_name + get_golem_version tries pkg_version --- R/golem-yaml-get.R | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index d4f23bd3..b6c82eae 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -12,7 +12,10 @@ get_golem_things <- function( use_parent = TRUE, path ) { - conf_path <- get_current_config(path, set_options = TRUE) + conf_path <- get_current_config( + path, + set_options = TRUE + ) stop_if( conf_path, is.null, @@ -33,12 +36,16 @@ get_golem_wd <- function( use_parent = TRUE, path = golem::pkg_path() ) { - get_golem_things( + pth <- get_golem_things( value = "golem_wd", config = "dev", use_parent = use_parent, path = path ) + if (is.null(pth)) { + pth <- golem::pkg_path() + } + return(pth) } #' @export @@ -63,7 +70,7 @@ get_golem_name <- function( if (is.null(nm)) { nm <- golem::pkg_name() } - nm + return(nm) } #' @export @@ -79,10 +86,14 @@ get_golem_version <- function( use_parent = TRUE, path = golem::pkg_path() ) { - get_golem_things( + vers <- get_golem_things( value = "golem_version", config = config, use_parent = use_parent, path = path ) + if (is.null(vers)) { + vers <- golem::pkg_version() + } + return(vers) } From feda561a3647ba4e11d71080e52e1caa2e392a90 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Tue, 5 Jul 2022 17:30:52 +0200 Subject: [PATCH 040/190] fix(test): no recommended tests --- inst/mantests/build.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index e78800cf..d17fec96 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -299,7 +299,7 @@ withr::with_tempdir({ dir.exists("tests") ) - golem::use_recommended_deps() + # golem::use_recommended_deps() golem::use_utils_ui(with_test = TRUE) expect_true( @@ -432,4 +432,4 @@ withr::with_tempdir({ # unlink(temp_lib, TRUE, TRUE) cli::cat_rule("Completed") -}) \ No newline at end of file +}) From e5df61252983e20b66564ee6acbf906e50575e57 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Tue, 5 Jul 2022 17:36:40 +0200 Subject: [PATCH 041/190] feat(config): get_current_config no longer set options --- R/config.R | 95 ++++---------------------------- vignettes/a_start.Rmd | 10 +--- vignettes/b_dev.Rmd | 2 +- vignettes/c_deploy.Rmd | 2 +- vignettes/d_js.Rmd | 2 +- vignettes/e_config.Rmd | 2 +- vignettes/f_extending_golem.Rmd | 46 ++++++++-------- vignettes/z_golem_cheatsheet.Rmd | 2 +- 8 files changed, 40 insertions(+), 121 deletions(-) diff --git a/R/config.R b/R/config.R index bedce9dd..1740adb4 100644 --- a/R/config.R +++ b/R/config.R @@ -51,11 +51,18 @@ guess_where_config <- function( return(NULL) } +#' Get the path to the current config File +#' +#' This function tries to guess where the golem-config file is located. +#' If it can't find it, this function asks the +#' user if they want to set the golem skeleton. +#' #' @importFrom fs file_copy path -get_current_config <- function( - path = ".", - set_options = TRUE -) { +#' +#' @param path Path to start looking for the config +#' +#' @export +get_current_config <- function(path = ".") { # We check wether we can guess where the config file is path_conf <- guess_where_config(path) @@ -103,9 +110,7 @@ get_current_config <- function( "shinyexample", golem::pkg_name() ) - if (set_options) { - set_golem_options() - } + # TODO This should also create the dev folder } else { stop( sprintf( @@ -141,79 +146,3 @@ change_app_config_name <- function( ) write(app_config, pth) } - - -# find and tag expressions in a yaml -# used internally in `amend_golem_config` -#' @importFrom utils modifyList -find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) - conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) - - expr_list <- lapply(names(conf), function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - }) - names(expr_list) <- names(conf) - expr_list <- Filter(function(x) length(x) > 0, expr_list) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply(expr_list, add_expr_tag) - modifyList(conf, tagged_exprs) -} - -#' Amend golem config file -#' -#' @param key key of the value to add in `config` -#' @inheritParams config::get -#' @inheritParams add_module -#' @inheritParams set_golem_options -#' -#' @export -#' @importFrom yaml read_yaml write_yaml -#' @importFrom attempt stop_if -#' -#' @return Used for side effects. -amend_golem_config <- function( - key, - value, - config = "default", - pkg = get_golem_wd(), - talkative = TRUE -) { - conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } - - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) - - if (key == "golem_wd") { - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) - } - - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) - invisible(TRUE) -} diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 3d93116e..b6bf8aa0 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -1,5 +1,5 @@ --- -title: "Getting Started with {golem}" +title: "a_start" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -157,14 +157,6 @@ golem::use_recommended_tests() About [tests in a package](https://r-pkgs.org/testing-basics.html). -### Use Recommended Packages - -This will add "shiny", "DT", "attempt", "glue", "htmltools", and "golem" as a dependency to your package. - -```{r} -golem::use_recommended_deps() -``` - ### Add various tools + If you want to change the default favicon diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index 052bbf95..c0b41100 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -1,5 +1,5 @@ --- -title: "Day to Day Dev with {golem}" +title: "b_dev" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index cf434e2f..5ff5b4c7 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -1,5 +1,5 @@ --- -title: "Deploying Apps with {golem}" +title: "c_deploy" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/d_js.Rmd b/vignettes/d_js.Rmd index 72629009..757b3a42 100644 --- a/vignettes/d_js.Rmd +++ b/vignettes/d_js.Rmd @@ -1,5 +1,5 @@ --- -title: "Using {golem} js functions" +title: "d_js" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 5339ec26..384de1ca 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -1,5 +1,5 @@ --- -title: "Using golem config" +title: "config" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{config} diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index d9f6b087..5f14c655 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -1,5 +1,5 @@ --- -title: "Extending {golem}" +title: "f_extending_golem" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{f_extending_golem} @@ -10,7 +10,7 @@ vignette: > ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>", + comment = "#>", eval = FALSE ) ``` @@ -70,8 +70,8 @@ These parameters might not be used inside your own hook, but __they need to be s Here is an example of a function that can be used to remove the `dev/` folder: ```{r} -no_dev <- function(path, package_name, ...){ - fs::dir_delete("dev") +no_dev <- function(path, package_name, ...) { + fs::dir_delete("dev") } create_golem("ici", project_hook = no_dev) ``` @@ -79,22 +79,20 @@ create_golem("ici", project_hook = no_dev) This one will create a CSS: ```{r} -new_css <- function(path, package_name, ...){ - +new_css <- function(path, package_name, ...) { css_path <- fs::path_abs("inst/app/www/custom.css") - + fs::file_create(css_path) - - write_there <- function(...){ + + write_there <- function(...) { write(..., file = css_path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") - + cli::cat_bullet("CSS generated") - } create_golem("ici", project_hook = new_css) @@ -137,16 +135,16 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(name, path, export, ...){ - # Define a template that only write the name of the - # module in the file - write(name, path) +my_tmpl <- function(name, path, export, ...) { + # Define a template that only write the name of the + # module in the file + write(name, path) } golem::add_module(name = "custom", module_template = my_tmpl) -my_other_tmpl <- function(name, path, ...){ - # Copy and paste a file from somewhere else - file.copy(..., path) +my_other_tmpl <- function(name, path, ...) { + # Copy and paste a file from somewhere else + file.copy(..., path) } golem::add_module(name = "custom", module_template = my_other_tmpl) ``` @@ -186,13 +184,13 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(path, ...){ - # Define a template that only write the name of the - # module in the file - write_there <- function(...){ +my_tmpl <- function(path, ...) { + # Define a template that only write the name of the + # module in the file + write_there <- function(...) { write(..., file = path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") diff --git a/vignettes/z_golem_cheatsheet.Rmd b/vignettes/z_golem_cheatsheet.Rmd index 4c1c9199..10c3967b 100644 --- a/vignettes/z_golem_cheatsheet.Rmd +++ b/vignettes/z_golem_cheatsheet.Rmd @@ -1,5 +1,5 @@ --- -title: "{golem} cheatsheet" +title: "e_golem_cheatsheet" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{e_golem_cheatsheet} From a386650efa67a9fc47acb874eee35fd6b7148b58 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 8 Aug 2022 22:01:49 +0200 Subject: [PATCH 042/190] chore: changed indentatino --- R/config.R | 204 ++++++++--------- R/create_golem.R | 426 +++++++++++++++++------------------ R/golem-yaml-get.R | 150 ++++++------ R/golem-yaml-set.R | 150 ++++++------ R/golem-yaml-utils.R | 140 ++++++------ R/set_golem_options.R | 96 ++++---- tests/testthat/test-config.R | 150 ++++++------ 7 files changed, 658 insertions(+), 658 deletions(-) diff --git a/R/config.R b/R/config.R index 1740adb4..e2ab53b0 100644 --- a/R/config.R +++ b/R/config.R @@ -10,45 +10,45 @@ #' @importFrom attempt attempt is_try_error #' @importFrom fs path path_abs guess_where_config <- function( - path = ".", - file = "inst/golem-config.yml" + path = ".", + file = "inst/golem-config.yml" ) { - # We'll try to guess where the path - # to the golem-config file is + # We'll try to guess where the path + # to the golem-config file is - # This one should be correct in 99% of the case - # => current directory /inst/golem-config.yml - ret_path <- path( - path, - file - ) - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } + # This one should be correct in 99% of the case + # => current directory /inst/golem-config.yml + ret_path <- path( + path, + file + ) + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } - # Maybe for some reason we are in inst/ - ret_path <- "golem-config.yml" - if (file_exists(ret_path)) { - return(path_abs(ret_path)) - } + # Maybe for some reason we are in inst/ + ret_path <- "golem-config.yml" + if (file_exists(ret_path)) { + return(path_abs(ret_path)) + } - # Trying with pkg_path - ret_path <- attempt({ - path( - golem::pkg_path(), - "inst/golem-config.yml" - ) - }) + # Trying with pkg_path + ret_path <- attempt({ + path( + golem::pkg_path(), + "inst/golem-config.yml" + ) + }) - if ( - !is_try_error(ret_path) & - file_exists(ret_path) - ) { - return( - path_abs(ret_path) - ) - } - return(NULL) + if ( + !is_try_error(ret_path) & + file_exists(ret_path) + ) { + return( + path_abs(ret_path) + ) + } + return(NULL) } #' Get the path to the current config File @@ -64,85 +64,85 @@ guess_where_config <- function( #' @export get_current_config <- function(path = ".") { - # We check wether we can guess where the config file is - path_conf <- guess_where_config(path) + # We check wether we can guess where the config file is + path_conf <- guess_where_config(path) - # We default to inst/ if this doesn't exist - if (is.null(path_conf)) { - path_conf <- path( - path, - "inst/golem-config.yml" - ) - } + # We default to inst/ if this doesn't exist + if (is.null(path_conf)) { + path_conf <- path( + path, + "inst/golem-config.yml" + ) + } - if (!file_exists(path_conf)) { - if (interactive()) { - ask <- yesno( - sprintf( - "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", - basename(path_conf) - ) - ) - # Return early if the user doesn't allow - if (!ask) { - return(NULL) - } + if (!file_exists(path_conf)) { + if (interactive()) { + ask <- yesno( + sprintf( + "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", + basename(path_conf) + ) + ) + # Return early if the user doesn't allow + if (!ask) { + return(NULL) + } - file_copy( - path = golem_sys("shinyexample/inst/golem-config.yml"), - new_path = path( - path, - "inst/golem-config.yml" - ) - ) - file_copy( - path = golem_sys("shinyexample/R/app_config.R"), - new_path = file.path( - path, - "R/app_config.R" - ) - ) - replace_word( - path( - path, - "R/app_config.R" - ), - "shinyexample", - golem::pkg_name() - ) - # TODO This should also create the dev folder - } else { - stop( - sprintf( - "The %s file doesn't exist.", - basename(path_conf) - ) - ) - } - } + file_copy( + path = golem_sys("shinyexample/inst/golem-config.yml"), + new_path = path( + path, + "inst/golem-config.yml" + ) + ) + file_copy( + path = golem_sys("shinyexample/R/app_config.R"), + new_path = file.path( + path, + "R/app_config.R" + ) + ) + replace_word( + path( + path, + "R/app_config.R" + ), + "shinyexample", + golem::pkg_name() + ) + # TODO This should also create the dev folder + } else { + stop( + sprintf( + "The %s file doesn't exist.", + basename(path_conf) + ) + ) + } + } - return( - invisible(path_conf) - ) + return( + invisible(path_conf) + ) } # This function changes the name of the # package in app_config when you need to # set the {golem} name change_app_config_name <- function( - name, - path = get_golem_wd() + name, + path = get_golem_wd() ) { - pth <- fs::path(path, "R", "app_config.R") - app_config <- readLines(pth) + pth <- fs::path(path, "R", "app_config.R") + app_config <- readLines(pth) - where_system.file <- grep("system.file", app_config) + where_system.file <- grep("system.file", app_config) - app_config[ - where_system.file - ] <- sprintf( - ' system.file(..., package = "%s")', - name - ) - write(app_config, pth) + app_config[ + where_system.file + ] <- sprintf( + ' system.file(..., package = "%s")', + name + ) + write(app_config, pth) } diff --git a/R/create_golem.R b/R/create_golem.R index b531cc77..566ede49 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -1,33 +1,33 @@ replace_package_name <- function( - copied_files, - package_name, - path_to_golem + copied_files, + package_name, + path_to_golem ) { - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path_to_golem, f) - - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path_to_golem, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) - } - - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path_to_golem, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path_to_golem, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } } @@ -65,194 +65,194 @@ replace_package_name <- function( #' #' @return The path, invisibly. create_golem <- function( - path, - check_name = TRUE, - open = TRUE, - overwrite = FALSE, - package_name = basename(path), - without_comments = FALSE, - project_hook = golem::project_hook, - with_git = FALSE, - ... + path, + check_name = TRUE, + open = TRUE, + overwrite = FALSE, + package_name = basename(path), + without_comments = FALSE, + project_hook = golem::project_hook, + with_git = FALSE, + ... ) { - path_to_golem <- normalizePath(path, mustWork = FALSE) - - if (check_name) { - cat_rule("Checking package name") - getFromNamespace("check_package_name", "usethis")(package_name) - cat_green_tick("Valid package name") - } - - - if (dir.exists(path_to_golem)) { - if (!isTRUE(overwrite)) { - stop( - paste( - "Project directory already exists. \n", - "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", - "Be careful this will restore a brand new golem. \n", - "You might be at risk of losing your work !" - ), - call. = FALSE - ) - } else { - cat_red_bullet("Overwriting existing project.") - } - } else { - cat_rule("Creating dir") - usethis::create_project( - path = path_to_golem, - open = FALSE, - ) - cat_green_tick("Created package directory") - } - - - cat_rule("Copying package skeleton") - from <- golem_sys("shinyexample") - - # Copy over whole directory - dir_copy( - path = from, - new_path = path_to_golem, - overwrite = TRUE - ) - - # Listing copied files ***from source directory*** - copied_files <- list.files( - path = from, - full.names = FALSE, - all.files = TRUE, - recursive = TRUE - ) - - replace_package_name( - copied_files, - package_name, - path_to_golem - ) - - cat_green_tick("Copied app skeleton") - - old <- setwd(path_to_golem) - - cat_rule("Running project hook function") - - # TODO fix - # for some weird reason test() fails here when using golem::create_golem - # and I don't have time to search why rn - if (substitute(project_hook) == "golem::project_hook") { - project_hook <- getFromNamespace("project_hook", "golem") - } - project_hook( - path = path_to_golem, - package_name = package_name, - ... - ) - - setwd(old) - - cat_green_tick("All set") - - - if (isTRUE(without_comments)) { - files <- list.files( - path = c( - file.path(path_to_golem, "dev"), - file.path(path_to_golem, "R") - ), - full.names = TRUE - ) - for (file in files) { - remove_comments(file) - } - } - - - if (isTRUE(with_git)) { - cat_rule("Initializing git repository") - git_output <- system( - command = paste("git init", path_to_golem), - ignore.stdout = TRUE, - ignore.stderr = TRUE - ) - if (git_output) { - cat_red_bullet("Error initializing git epository") - } else { - cat_green_tick("Initialized git repository") - } - } - - - old <- setwd(path_to_golem) - use_latest_dependencies() - - # No .Rprofile for now - # cat_rule("Appending .Rprofile") - # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) - # write("home_profile <- file.path(", ".Rprofile", append = TRUE) - # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) - # write(" \".Rprofile\"", ".Rprofile", append = TRUE) - # write(")", ".Rprofile", append = TRUE) - # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) - # write(" source(home_profile)", ".Rprofile", append = TRUE) - # write("}", ".Rprofile", append = TRUE) - # write("rm(home_profile)", ".Rprofile", append = TRUE) - # - # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) - # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) - # cat_green_tick("Appended") - - setwd(old) - - cat_rule("Done") - - cat_line( - paste0( - "A new golem named ", - package_name, - " was created at ", - path_to_golem, - " .\n", - "To continue working on your app, start editing the 01_start.R file." - ) - ) - - - if (isTRUE(open)) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { - rstudioapi::openProject(path = path) - } else { - setwd(path) - } - } - - return( - invisible( - path_to_golem - ) - ) + path_to_golem <- normalizePath(path, mustWork = FALSE) + + if (check_name) { + cat_rule("Checking package name") + getFromNamespace("check_package_name", "usethis")(package_name) + cat_green_tick("Valid package name") + } + + + if (dir.exists(path_to_golem)) { + if (!isTRUE(overwrite)) { + stop( + paste( + "Project directory already exists. \n", + "Set `create_golem(overwrite = TRUE)` to overwrite anyway.\n", + "Be careful this will restore a brand new golem. \n", + "You might be at risk of losing your work !" + ), + call. = FALSE + ) + } else { + cat_red_bullet("Overwriting existing project.") + } + } else { + cat_rule("Creating dir") + usethis::create_project( + path = path_to_golem, + open = FALSE, + ) + cat_green_tick("Created package directory") + } + + + cat_rule("Copying package skeleton") + from <- golem_sys("shinyexample") + + # Copy over whole directory + dir_copy( + path = from, + new_path = path_to_golem, + overwrite = TRUE + ) + + # Listing copied files ***from source directory*** + copied_files <- list.files( + path = from, + full.names = FALSE, + all.files = TRUE, + recursive = TRUE + ) + + replace_package_name( + copied_files, + package_name, + path_to_golem + ) + + cat_green_tick("Copied app skeleton") + + old <- setwd(path_to_golem) + + cat_rule("Running project hook function") + + # TODO fix + # for some weird reason test() fails here when using golem::create_golem + # and I don't have time to search why rn + if (substitute(project_hook) == "golem::project_hook") { + project_hook <- getFromNamespace("project_hook", "golem") + } + project_hook( + path = path_to_golem, + package_name = package_name, + ... + ) + + setwd(old) + + cat_green_tick("All set") + + + if (isTRUE(without_comments)) { + files <- list.files( + path = c( + file.path(path_to_golem, "dev"), + file.path(path_to_golem, "R") + ), + full.names = TRUE + ) + for (file in files) { + remove_comments(file) + } + } + + + if (isTRUE(with_git)) { + cat_rule("Initializing git repository") + git_output <- system( + command = paste("git init", path_to_golem), + ignore.stdout = TRUE, + ignore.stderr = TRUE + ) + if (git_output) { + cat_red_bullet("Error initializing git epository") + } else { + cat_green_tick("Initialized git repository") + } + } + + + old <- setwd(path_to_golem) + use_latest_dependencies() + + # No .Rprofile for now + # cat_rule("Appending .Rprofile") + # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) + # write("home_profile <- file.path(", ".Rprofile", append = TRUE) + # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) + # write(" \".Rprofile\"", ".Rprofile", append = TRUE) + # write(")", ".Rprofile", append = TRUE) + # write("if (file.exists(home_profile)){", ".Rprofile", append = TRUE) + # write(" source(home_profile)", ".Rprofile", append = TRUE) + # write("}", ".Rprofile", append = TRUE) + # write("rm(home_profile)", ".Rprofile", append = TRUE) + # + # write("# Setting shiny.autoload.r to FALSE ", ".Rprofile", append = TRUE) + # write("options(shiny.autoload.r = FALSE)", ".Rprofile", append = TRUE) + # cat_green_tick("Appended") + + setwd(old) + + cat_rule("Done") + + cat_line( + paste0( + "A new golem named ", + package_name, + " was created at ", + path_to_golem, + " .\n", + "To continue working on your app, start editing the 01_start.R file." + ) + ) + + + if (isTRUE(open)) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { + rstudioapi::openProject(path = path) + } else { + setwd(path) + } + } + + return( + invisible( + path_to_golem + ) + ) } # to be used in RStudio "new project" GUI create_golem_gui <- function(path, ...) { - dots <- list(...) - attempt::stop_if_not( - dots$project_hook, - ~ grepl("::", .x), - "{golem} project templates must be explicitely namespaced (pkg::fun)" - ) - splt <- strsplit(dots$project_hook, "::") - project_hook <- getFromNamespace( - splt[[1]][2], - splt[[1]][1] - ) - create_golem( - path = path, - open = FALSE, - without_comments = dots$without_comments, - project_hook = project_hook, - check_name = dots$check_name, - with_git = dots$with_git - ) + dots <- list(...) + attempt::stop_if_not( + dots$project_hook, + ~ grepl("::", .x), + "{golem} project templates must be explicitely namespaced (pkg::fun)" + ) + splt <- strsplit(dots$project_hook, "::") + project_hook <- getFromNamespace( + splt[[1]][2], + splt[[1]][1] + ) + create_golem( + path = path, + open = FALSE, + without_comments = dots$without_comments, + project_hook = project_hook, + check_name = dots$check_name, + with_git = dots$with_git + ) } diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index b6c82eae..d0fda7b1 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -1,99 +1,99 @@ #' @importFrom config get get_golem_things <- function( - value, - config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), - use_parent = TRUE, - path + value, + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path ) { - conf_path <- get_current_config( - path, - set_options = TRUE - ) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) + conf_path <- get_current_config( + path, + set_options = TRUE + ) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) } #' @export #' @rdname golem_opts get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() + use_parent = TRUE, + path = golem::pkg_path() ) { - pth <- get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) - if (is.null(pth)) { - pth <- golem::pkg_path() - } - return(pth) + pth <- get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) + if (is.null(pth)) { + pth <- golem::pkg_path() + } + return(pth) } #' @export #' @rdname golem_opts get_golem_name <- function( - config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path = golem::pkg_path() ) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - return(nm) + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + return(nm) } #' @export #' @rdname golem_opts get_golem_version <- function( - config = Sys.getenv( - "GOLEM_CONFIG_ACTIVE", - Sys.getenv( - "R_CONFIG_ACTIVE", - "default" - ) - ), - use_parent = TRUE, - path = golem::pkg_path() + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path = golem::pkg_path() ) { - vers <- get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(vers)) { - vers <- golem::pkg_version() - } - return(vers) + vers <- get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(vers)) { + vers <- golem::pkg_version() + } + return(vers) } diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 441f74ab..495edb3e 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -30,101 +30,101 @@ #' @rdname golem_opts #' @importFrom fs path_abs set_golem_wd <- function( - golem_wd = golem::pkg_path(), - pkg = golem::pkg_path(), - talkative = TRUE + golem_wd = golem::pkg_path(), + pkg = golem::pkg_path(), + talkative = TRUE ) { - if ( - golem_wd == "golem::pkg_path()" | - golem_wd == golem::pkg_path() - ) { - golem_yaml_path <- "golem::pkg_path()" - attr(golem_yaml_path, "tag") <- "!expr" - } else { - golem_yaml_path <- path_abs(golem_wd) - } + if ( + golem_wd == "golem::pkg_path()" | + golem_wd == golem::pkg_path() + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" + } else { + golem_yaml_path <- path_abs(golem_wd) + } - amend_golem_config( - key = "golem_wd", - value = golem_yaml_path, - config = "dev", - pkg = pkg, - talkative = talkative - ) + amend_golem_config( + key = "golem_wd", + value = golem_yaml_path, + config = "dev", + pkg = pkg, + talkative = talkative + ) - invisible(path) + invisible(path) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_name <- function( - name = golem::pkg_name(), - pkg = golem::pkg_path(), - talkative = TRUE + name = golem::pkg_name(), + pkg = golem::pkg_path(), + talkative = TRUE ) { - # Changing in YAML - amend_golem_config( - key = "golem_name", - value = name, - config = "default", - pkg = pkg, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = pkg - ) + # Changing in YAML + amend_golem_config( + key = "golem_name", + value = name, + config = "default", + pkg = pkg, + talkative = talkative + ) + # Changing in app-config.R + change_app_config_name( + name = name, + path = pkg + ) - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) + # Changing in DESCRIPTION + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(name) + invisible(name) } #' @export #' @rdname golem_opts #' @importFrom fs path_abs set_golem_version <- function( - version = golem::pkg_version(), - pkg = golem::pkg_path(), - talkative = TRUE + version = golem::pkg_version(), + pkg = golem::pkg_path(), + talkative = TRUE ) { - path <- path_abs(path) + path <- path_abs(path) - # Changing in YAML - amend_golem_config( - key = "golem_version", - value = as.character(version), - config = "default", - pkg = pkg, - talkative = talkative - ) + # Changing in YAML + amend_golem_config( + key = "golem_version", + value = as.character(version), + config = "default", + pkg = pkg, + talkative = talkative + ) - desc <- desc::description$new( - file = fs::path( - pkg, - "DESCRIPTION" - ) - ) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) + desc <- desc::description$new( + file = fs::path( + pkg, + "DESCRIPTION" + ) + ) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) - invisible(version) + invisible(version) } diff --git a/R/golem-yaml-utils.R b/R/golem-yaml-utils.R index 0e025655..a6f61463 100644 --- a/R/golem-yaml-utils.R +++ b/R/golem-yaml-utils.R @@ -9,42 +9,42 @@ # #' @importFrom utils modifyList find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml( - conf_path, - eval.expr = FALSE - ) - conf.eval <- yaml::read_yaml( - conf_path, - eval.expr = TRUE - ) + conf <- yaml::read_yaml( + conf_path, + eval.expr = FALSE + ) + conf.eval <- yaml::read_yaml( + conf_path, + eval.expr = TRUE + ) - expr_list <- lapply( - names(conf), - function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - } - ) + expr_list <- lapply( + names(conf), + function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + } + ) - names(expr_list) <- names(conf) + names(expr_list) <- names(conf) - expr_list <- Filter( - function(x) length(x) > 0, - expr_list - ) + expr_list <- Filter( + function(x) length(x) > 0, + expr_list + ) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply( - expr_list, - add_expr_tag - ) + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply( + expr_list, + add_expr_tag + ) - modifyList( - conf, - tagged_exprs - ) + modifyList( + conf, + tagged_exprs + ) } #' Amend golem config file @@ -60,51 +60,51 @@ find_and_tag_exprs <- function(conf_path) { #' #' @return Used for side effects. amend_golem_config <- function( - key, - value, - config = "default", - pkg = golem::pkg_path(), - talkative = TRUE + key, + value, + config = "default", + pkg = golem::pkg_path(), + talkative = TRUE ) { - conf_path <- get_current_config(pkg) + conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) - cat_if_talk <- function( - ..., - fun = cat_green_tick - ) { - if (talkative) { - fun(...) - } - } + cat_if_talk <- function( + ..., + fun = cat_green_tick + ) { + if (talkative) { + fun(...) + } + } - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) - if (key == "golem_wd") { - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) - } + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) + write_yaml( + conf, + conf_path + ) - invisible(TRUE) + invisible(TRUE) } diff --git a/R/set_golem_options.R b/R/set_golem_options.R index c35ac8cc..6eb4ed3f 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -36,62 +36,62 @@ #' @return Used for side-effects for the setters, and values from the #' config in the getters. set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE, - config_file = golem::get_current_config(golem_wd) + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) ) { - # TODO here we'll run the - # golem_install_dev_pkg() function + # TODO here we'll run the + # golem_install_dev_pkg() function - if (talkative) { - cli::cat_rule( - "Setting {golem} options in `golem-config.yml`" - ) - } + if (talkative) { + cli::cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } - # Let's do this in the order of the - # parameters - # Setting name of the golem - set_golem_name( - name = golem_name, - talkative = talkative - ) + # Let's do this in the order of the + # parameters + # Setting name of the golem + set_golem_name( + name = golem_name, + talkative = talkative + ) - # Let's start with wd - # Basically here the idea is to be able - # to keep the wd as an expr if it is the - # same as golem::pkg_path(), otherwise - # we use the explicit path + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path - set_golem_wd( - path = golem_wd, - talkative = talkative - ) + set_golem_wd( + path = golem_wd, + talkative = talkative + ) - # Setting golem_version - set_golem_version( - version = golem_version, - talkative = talkative - ) + # Setting golem_version + set_golem_version( + version = golem_version, + talkative = talkative + ) - # Setting app_prod - set_golem_things( - "app_prod", - app_prod, - path = golem_wd, - talkative = talkative - ) + # Setting app_prod + set_golem_things( + "app_prod", + app_prod, + path = golem_wd, + talkative = talkative + ) - # This part is for {usethis} and {here} - if (talkative) { - cli::cat_rule( - "Setting {usethis} project as `golem_wd`" - ) - } + # This part is for {usethis} and {here} + if (talkative) { + cli::cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } - proj_set(golem_wd) + proj_set(golem_wd) } diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index 69400c0f..fd553276 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -1,78 +1,78 @@ test_that("config works", { - with_dir(pkg, { - # We'll try to be sure that - # golem_wd: !expr golem::pkg_path() - # is kept along the way - expect_equal( - tail(readLines("inst/golem-config.yml"), 1), - " golem_wd: !expr golem::pkg_path()" - ) - expect_equal( - get_golem_name(), - fakename - ) - expect_equal( - get_golem_version(), - "0.0.0.9000" - ) - expect_equal( - normalizePath(get_golem_wd(), mustWork = FALSE), - normalizePath(pkg, mustWork = FALSE) - ) - amend_golem_config( - key = "where", - value = "indev" - ) - amend_golem_config( - key = "where", - value = "inprod", - config = "production" - ) - expect_equal( - tail(readLines("inst/golem-config.yml"), 1), - " golem_wd: !expr golem::pkg_path()" - ) - expect_equal( - config::get("where", file = "inst/golem-config.yml"), - "indev" - ) - expect_equal( - config::get("where", config = "production", file = "inst/golem-config.yml"), - "inprod" - ) - where_conf <- withr::with_envvar( - c("R_CONFIG_ACTIVE" = "production"), - { - config::get("where", file = "inst/golem-config.yml") - } - ) - expect_equal( - where_conf, - "inprod" - ) - set_golem_name("plop") - expect_equal( - get_golem_name(), - "plop" - ) - set_golem_name(fakename) - set_golem_version("0.0.0.9001") - expect_equal( - get_golem_version(), - "0.0.0.9001" - ) - set_golem_version("0.0.0.9000") + with_dir(pkg, { + # We'll try to be sure that + # golem_wd: !expr golem::pkg_path() + # is kept along the way + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + get_golem_name(), + fakename + ) + expect_equal( + get_golem_version(), + "0.0.0.9000" + ) + expect_equal( + normalizePath(get_golem_wd(), mustWork = FALSE), + normalizePath(pkg, mustWork = FALSE) + ) + amend_golem_config( + key = "where", + value = "indev" + ) + amend_golem_config( + key = "where", + value = "inprod", + config = "production" + ) + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + config::get("where", file = "inst/golem-config.yml"), + "indev" + ) + expect_equal( + config::get("where", config = "production", file = "inst/golem-config.yml"), + "inprod" + ) + where_conf <- withr::with_envvar( + c("R_CONFIG_ACTIVE" = "production"), + { + config::get("where", file = "inst/golem-config.yml") + } + ) + expect_equal( + where_conf, + "inprod" + ) + set_golem_name("plop") + expect_equal( + get_golem_name(), + "plop" + ) + set_golem_name(fakename) + set_golem_version("0.0.0.9001") + expect_equal( + get_golem_version(), + "0.0.0.9001" + ) + set_golem_version("0.0.0.9000") - set_golem_wd(normalizePath("inst")) - expect_equal( - normalizePath(get_golem_wd()), - normalizePath("inst") - ) - set_golem_wd(pkg) - # Be sure that after setting the stuff the wd is still here::here() - expect_equal( - tail(readLines("inst/golem-config.yml"), 1), - " golem_wd: !expr golem::pkg_path()" - ) - }) + set_golem_wd(normalizePath("inst")) + expect_equal( + normalizePath(get_golem_wd()), + normalizePath("inst") + ) + set_golem_wd(pkg) + # Be sure that after setting the stuff the wd is still here::here() + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + }) }) From 4472ae0fc354f4ea3888c3c15d7e867e5b9717f5 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 08:21:41 +0200 Subject: [PATCH 043/190] chore: added some comments --- R/config.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/config.R b/R/config.R index e2ab53b0..4f2ac5cc 100644 --- a/R/config.R +++ b/R/config.R @@ -17,6 +17,7 @@ guess_where_config <- function( # to the golem-config file is # This one should be correct in 99% of the case + # If we don't change the default values of the params. # => current directory /inst/golem-config.yml ret_path <- path( path, From 57869e47c3dcabe33e4d8dc23741524766e54af3 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 08:58:33 +0200 Subject: [PATCH 044/190] fix: issue with param names path is now renamed pkg --- R/desc.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/desc.R b/R/desc.R index dd120fa9..87c254c7 100644 --- a/R/desc.R +++ b/R/desc.R @@ -69,7 +69,7 @@ fill_desc <- function( ) set_golem_version( version = "0.0.0.9000", - path = path + pkg = path ) desc$set( Package = pkg_name From 78ae7a75139113ffbdf7e6a0d54af82c9da6a745 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:02:51 +0200 Subject: [PATCH 045/190] refactor: explicitly set arguments --- R/set_golem_options.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R/set_golem_options.R b/R/set_golem_options.R index 6eb4ed3f..1a00c1e4 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -58,6 +58,7 @@ set_golem_options <- function( # Setting name of the golem set_golem_name( name = golem_name, + pkg = golem_wd, talkative = talkative ) @@ -68,21 +69,22 @@ set_golem_options <- function( # we use the explicit path set_golem_wd( - path = golem_wd, + pkg = golem_wd, talkative = talkative ) # Setting golem_version set_golem_version( version = golem_version, + pkg = golem_wd, talkative = talkative ) # Setting app_prod - set_golem_things( + amend_golem_config( "app_prod", app_prod, - path = golem_wd, + pkg = golem_wd, talkative = talkative ) From bdc2b4f129815ff43d7da57b779f06866061d190 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:03:05 +0200 Subject: [PATCH 046/190] fix: removed unused argument --- R/golem-yaml-get.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index d0fda7b1..96fe198d 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -13,8 +13,7 @@ get_golem_things <- function( path ) { conf_path <- get_current_config( - path, - set_options = TRUE + path ) stop_if( conf_path, From e21105a7ddbf3b1b5d7c1cbda01a0a5ea5c6c30f Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:03:34 +0200 Subject: [PATCH 047/190] doc(vignette): renames the title according to convention --- vignettes/e_config.Rmd | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 384de1ca..5b583ea4 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -1,5 +1,5 @@ --- -title: "config" +title: "e_config" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{config} @@ -27,6 +27,10 @@ knitr::opts_knit$set(root.dir = x) library(golem) ``` +```{r echo = FALSE} +old <- setwd(x) +``` + ## About `inst/golem-config.yml` When you start a new `{golem}` application, you'll find a file called `golem-config.yml` in the `inst/` folder. @@ -39,7 +43,7 @@ This config file is based on the [`{config}`](https://github.com/rstudio/config) Here is what the default config file looks like: -```{r echo = FALSE, comment= ""} +```{r echo = FALSE, comment= "", } cat( sep = "\n", readLines( @@ -60,6 +64,15 @@ These options are globally set with: set_golem_options() ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + The functions reading the options in this config file are: ```{r} @@ -76,6 +89,15 @@ set_golem_wd(".") set_golem_version("0.0.1") ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + ## Using `golem-config` @@ -95,6 +117,7 @@ amend_golem_config( ) ``` + Will result in a `golem-config.yml` file as such: ```{r echo = FALSE, comment= ""} @@ -143,4 +166,8 @@ The idea is also that the `golem-config.yml` file is shareable across `{golem}` ## Note for `{golem}` < 0.2.0 users -If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. \ No newline at end of file +If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. + +```{r echo = FALSE} +setwd(old) +``` \ No newline at end of file From 0d7f6aeda65a259aa92eac27c02e5c6157bf5403 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 09:03:52 +0200 Subject: [PATCH 048/190] fix: correct handling of paths in yaml-set --- R/golem-yaml-set.R | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 495edb3e..c4350fdc 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -63,6 +63,9 @@ set_golem_name <- function( pkg = golem::pkg_path(), talkative = TRUE ) { + + path <- path_abs(pkg) + # Changing in YAML amend_golem_config( key = "golem_name", @@ -74,7 +77,7 @@ set_golem_name <- function( # Changing in app-config.R change_app_config_name( name = name, - path = pkg + path = path ) # Changing in DESCRIPTION @@ -102,7 +105,7 @@ set_golem_version <- function( pkg = golem::pkg_path(), talkative = TRUE ) { - path <- path_abs(path) + path <- path_abs(pkg) # Changing in YAML amend_golem_config( @@ -115,7 +118,7 @@ set_golem_version <- function( desc <- desc::description$new( file = fs::path( - pkg, + path, "DESCRIPTION" ) ) From 01713785c63e5b3d943a934a8ec249abadc53fcd Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 21:11:45 +0200 Subject: [PATCH 049/190] doc: documentation update for new params in funs --- DESCRIPTION | 2 +- R/golem-yaml-set.R | 3 +-- R/set_golem_options.R | 4 ++-- man/dockerfiles.Rd | 24 +++++++++++++++++++++--- man/golem_opts.Rd | 4 ++-- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 411b8d2f..72ea4c2f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -89,4 +89,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.0 +RoxygenNote: 7.2.1 diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index c4350fdc..58d0dc04 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -27,8 +27,8 @@ # } #' @export -#' @rdname golem_opts #' @importFrom fs path_abs +#' @rdname golem_opts set_golem_wd <- function( golem_wd = golem::pkg_path(), pkg = golem::pkg_path(), @@ -63,7 +63,6 @@ set_golem_name <- function( pkg = golem::pkg_path(), talkative = TRUE ) { - path <- path_abs(pkg) # Changing in YAML diff --git a/R/set_golem_options.R b/R/set_golem_options.R index 1a00c1e4..77338862 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -6,7 +6,7 @@ #' #' @section Set Functions: #' + `set_golem_options()` sets all the options, with the defaults from the functions below. -#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. +#' + `set_golem_wd()` defaults to `golem::golem_wd()`, which is the package root when starting a golem. #' + `set_golem_name()` defaults `golem::pkg_name()` #' + `set_golem_version()` defaults `golem::pkg_version()` #' @@ -20,7 +20,7 @@ #' @param golem_version Version of the current golem. #' @param golem_wd Working directory of the current golem package. #' @param app_prod Is the `{golem}` in prod mode? -#' @param path The path to set the golem working directory. +#' @param path,pkg The path to set the golem working directory. #' Note that it will be passed to `normalizePath`. #' @param talkative Should the messages be printed to the console? #' @param name The name of the app diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 53129521..dfd5715b 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -133,7 +133,7 @@ Default is 0.0.0.0.} \item{expand}{boolean. If \code{TRUE} each system requirement will have its own \code{RUN} line.} -\item{open}{boolean. Should the Dockerfile/README be open after creation? Default is \code{TRUE}.} +\item{open}{boolean. Should the Dockerfile/README/README be open after creation? Default is \code{TRUE}.} \item{update_tar_gz}{boolean. If \code{TRUE} and \code{build_golem_from_source} is also \code{TRUE}, an updated tar.gz is created.} @@ -160,8 +160,8 @@ the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}') The \code{{dockerfiler}} object, invisibly. } \description{ -Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} creates -a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} and +Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} and \code{add_dockerfile_with_renv()} creates +a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} , \code{add_dockerfile_with_renv_shinyproxy()} and \code{add_dockerfile_heroku()} creates platform specific Dockerfile. } \examples{ @@ -178,11 +178,29 @@ if (interactive()) { output_dir = "deploy" ) } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock", # uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} # Add a Dockerfile for ShinyProxy if (interactive()) { add_dockerfile_shinyproxy() } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem with ShinyProxy using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock",# uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} + + # Crete a 'deploy' folder containing everything needed to deploy # the golem with ShinyProxy using docker based on {renv} if (interactive()) { diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index 4601e99d..0d78ccee 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -49,7 +49,7 @@ set_golem_options( \item{use_parent}{\code{TRUE} to scan parent directories for configuration files if the specified config file isn't found.} -\item{path}{The path to set the golem working directory. +\item{path, pkg}{The path to set the golem working directory. Note that it will be passed to \code{normalizePath}.} \item{config}{Name of configuration to read from. Defaults to @@ -83,7 +83,7 @@ inside the \code{inst} folder. \itemize{ \item \code{set_golem_options()} sets all the options, with the defaults from the functions below. -\item \code{set_golem_wd()} defaults to \code{here::here()}, which is the package root when starting a golem. +\item \code{set_golem_wd()} defaults to \code{golem::golem_wd()}, which is the package root when starting a golem. \item \code{set_golem_name()} defaults \code{golem::pkg_name()} \item \code{set_golem_version()} defaults \code{golem::pkg_version()} } From 61ad01c09c54e9504885474429ed7481e11e5d04 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 21:46:18 +0200 Subject: [PATCH 050/190] doc: moved {here} to Suggests --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 72ea4c2f..8b53f5c0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -55,7 +55,6 @@ Imports: crayon, desc, fs, - here, htmltools, rlang, rstudioapi, @@ -67,6 +66,7 @@ Suggests: covr, devtools, dockerfiler (>= 0.2.0), + here, knitr, pkgload, pkgbuild, From c2907e3ef64f15f82dbe928eba958a26e5992c8b Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 9 Aug 2022 21:47:14 +0200 Subject: [PATCH 051/190] doc: funs documented & param names - Unified param name for pkg/path - set_golem_wd() is now correctly documented --- R/golem-yaml-get.R | 14 +++++++---- R/golem-yaml-set.R | 56 +++++++++++++++++++++---------------------- R/set_golem_options.R | 3 ++- man/golem_opts.Rd | 23 ++++++++++++------ 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index 96fe198d..5eaa7781 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -28,13 +28,15 @@ get_golem_things <- function( ) } - #' @export +#' @importFrom fs path_abs #' @rdname golem_opts get_golem_wd <- function( use_parent = TRUE, - path = golem::pkg_path() + pkg = golem::pkg_path() ) { + path <- path_abs(pkg) + pth <- get_golem_things( value = "golem_wd", config = "dev", @@ -48,6 +50,7 @@ get_golem_wd <- function( } #' @export +#' @importFrom fs path_abs #' @rdname golem_opts get_golem_name <- function( config = Sys.getenv( @@ -58,8 +61,9 @@ get_golem_name <- function( ) ), use_parent = TRUE, - path = golem::pkg_path() + pkg = golem::pkg_path() ) { + path <- path_abs(pkg) nm <- get_golem_things( value = "golem_name", config = config, @@ -73,6 +77,7 @@ get_golem_name <- function( } #' @export +#' @importFrom fs path_abs #' @rdname golem_opts get_golem_version <- function( config = Sys.getenv( @@ -83,8 +88,9 @@ get_golem_version <- function( ) ), use_parent = TRUE, - path = golem::pkg_path() + pkg = golem::pkg_path() ) { + path <- path_abs(pkg) vers <- get_golem_things( value = "golem_version", config = config, diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 58d0dc04..55f7e24d 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -1,31 +1,3 @@ - -#' Set things in golem config -#' -#' @param key,value entries in the yaml -#' @param path path to the config file -#' @param talkative Should things be printed? -#' @param config config context to write to -#' -#' @noRd -#' -# set_golem_things <- function( -# key, -# value, -# config_file, -# talkative = TRUE, -# config = "default" -# ) { -# amend_golem_config( -# key = key, -# value = value, -# config = config, -# pkg = config_file, -# talkative = talkative -# ) - -# invisible(path) -# } - #' @export #' @importFrom fs path_abs #' @rdname golem_opts @@ -130,3 +102,31 @@ set_golem_version <- function( invisible(version) } + + +#' Set things in golem config +#' +#' @param key,value entries in the yaml +#' @param path path to the config file +#' @param talkative Should things be printed? +#' @param config config context to write to +#' +#' @noRd +#' +# set_golem_things <- function( +# key, +# value, +# config_file, +# talkative = TRUE, +# config = "default" +# ) { +# amend_golem_config( +# key = key, +# value = value, +# config = config, +# pkg = config_file, +# talkative = talkative +# ) + +# invisible(path) +# } diff --git a/R/set_golem_options.R b/R/set_golem_options.R index 77338862..bf9d59e8 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -20,11 +20,12 @@ #' @param golem_version Version of the current golem. #' @param golem_wd Working directory of the current golem package. #' @param app_prod Is the `{golem}` in prod mode? -#' @param path,pkg The path to set the golem working directory. +#' @param pkg The path to set the golem working directory. #' Note that it will be passed to `normalizePath`. #' @param talkative Should the messages be printed to the console? #' @param name The name of the app #' @param version The version of the app +#' @param config_file path to the {golem} config file #' @inheritParams config::get #' #' @rdname golem_opts diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index 0d78ccee..e6c115bf 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -5,23 +5,30 @@ \alias{get_golem_wd} \alias{get_golem_name} \alias{get_golem_version} +\alias{set_golem_wd} \alias{set_golem_name} \alias{set_golem_version} \alias{set_golem_options} \title{\code{{golem}} options} \usage{ -get_golem_wd(use_parent = TRUE, path = golem::pkg_path()) +get_golem_wd(use_parent = TRUE, pkg = golem::pkg_path()) get_golem_name( config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), use_parent = TRUE, - path = golem::pkg_path() + pkg = golem::pkg_path() ) get_golem_version( config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), use_parent = TRUE, - path = golem::pkg_path() + pkg = golem::pkg_path() +) + +set_golem_wd( + golem_wd = golem::pkg_path(), + pkg = golem::pkg_path(), + talkative = TRUE ) set_golem_name( @@ -49,26 +56,28 @@ set_golem_options( \item{use_parent}{\code{TRUE} to scan parent directories for configuration files if the specified config file isn't found.} -\item{path, pkg}{The path to set the golem working directory. +\item{pkg}{The path to set the golem working directory. Note that it will be passed to \code{normalizePath}.} \item{config}{Name of configuration to read from. Defaults to the value of the \code{R_CONFIG_ACTIVE} environment variable ("default" if the variable does not exist).} -\item{name}{The name of the app} +\item{golem_wd}{Working directory of the current golem package.} \item{talkative}{Should the messages be printed to the console?} +\item{name}{The name of the app} + \item{version}{The version of the app} \item{golem_name}{Name of the current golem.} \item{golem_version}{Version of the current golem.} -\item{golem_wd}{Working directory of the current golem package.} - \item{app_prod}{Is the \code{{golem}} in prod mode?} + +\item{config_file}{path to the {golem} config file} } \value{ Used for side-effects for the setters, and values from the From d4fc8f853d3388a93a4a10d094a16ea3dfbb4804 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 11 Aug 2022 16:32:57 +0200 Subject: [PATCH 052/190] fix: issue with pkg_path when not in project Move to here::here() for now --- DESCRIPTION | 2 +- R/pkg_tools.R | 29 +++-------------------------- man/pkg_tools.Rd | 5 +---- 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8b53f5c0..a7df8147 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -54,6 +54,7 @@ Imports: config, crayon, desc, + here, fs, htmltools, rlang, @@ -66,7 +67,6 @@ Suggests: covr, devtools, dockerfiler (>= 0.2.0), - here, knitr, pkgload, pkgbuild, diff --git a/R/pkg_tools.R b/R/pkg_tools.R index f746c646..9169df9a 100644 --- a/R/pkg_tools.R +++ b/R/pkg_tools.R @@ -22,8 +22,6 @@ daf_desc <- function( #' inside your project while developing #' #' @param path Path to use to read the DESCRIPTION -#' @param depth Number of parent directory to visit before -#' deciding we are not in a package. #' #' @export #' @rdname pkg_tools @@ -37,28 +35,7 @@ pkg_version <- function(path = ".") { } #' @export #' @rdname pkg_tools -pkg_path <- function( - path = ".", - depth = 3 -) { - # We use depth 3 bcs it's the deepest you can be - # i.e. inst/app/www/ - path <- normalizePath(path) - if (file.exists(file.path(path, "DESCRIPTION"))) { - return(path) - } - for (i in 1:depth) { - path <- dirname(path) - if ( - file.exists( - file.path( - path, - "DESCRIPTION" - ) - ) - ) { - return(path) - } - } - stop("Unable to locate the package path.") +pkg_path <- function(path = ".") { + rlang::check_installed("here") + here::here() } diff --git a/man/pkg_tools.Rd b/man/pkg_tools.Rd index 14f802ab..6103d946 100644 --- a/man/pkg_tools.Rd +++ b/man/pkg_tools.Rd @@ -10,13 +10,10 @@ pkg_name(path = ".") pkg_version(path = ".") -pkg_path(path = ".", depth = 3) +pkg_path(path = ".") } \arguments{ \item{path}{Path to use to read the DESCRIPTION} - -\item{depth}{Number of parent directory to visit before -deciding we are not in a package.} } \description{ These are functions to help you navigate From 8cbad801bd819eb7258a9e35b38ead50ee83e3c7 Mon Sep 17 00:00:00 2001 From: nathansquan <61978506+nathansquan@users.noreply.github.com> Date: Thu, 11 Aug 2022 15:18:30 -0400 Subject: [PATCH 053/190] feat: create add_partial_html_template() (#894) Co-authored-by: Colin Fay Co-authored-by: vincent guyader --- R/add_files.R | 57 +++++++++++++++++++++++++++++++++ tests/testthat/test-add_files.R | 12 +++++++ 2 files changed, 69 insertions(+) diff --git a/R/add_files.R b/R/add_files.R index bfe88086..41bc9855 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -584,6 +584,63 @@ add_html_template <- function( } } +#' @export +#' @rdname add_files +#' @importFrom fs path_abs path file_create file_exists +add_partial_html_template <- function( + name = "partial_template.html", + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +) { + name <- file_path_sans_ext(name) + + old <- setwd(path_abs(pkg)) + on.exit(setwd(old)) + + dir_created <- create_if_needed( + dir, + type = "directory" + ) + + if (!dir_created) { + cat_dir_necessary() + return(invisible(FALSE)) + } + + dir <- path_abs(dir) + + where <- path( + dir, + sprintf( + "%s.html", + name + ) + ) + + if (!file_exists(where)) { + file_create(where) + write_there <- function(...) write(..., file = where, append = TRUE) + write_there("

") + write_there(" {{ content }}") + write_there("
") + write_there("") + file_created_dance( + where, + after_creation_message_html_template, + pkg, + dir, + name, + open + ) + } else { + file_already_there_dance( + where = where, + open_file = open + ) + } +} #' @export #' @rdname add_files diff --git a/tests/testthat/test-add_files.R b/tests/testthat/test-add_files.R index 456d3bee..50a48362 100644 --- a/tests/testthat/test-add_files.R +++ b/tests/testthat/test-add_files.R @@ -108,6 +108,18 @@ test_that("add_files", { pak = pkg, fp = fp ) + expect_add_file( + add_html_template, + ext = "html", + pak = pkg, + fp = fp + ) + expect_add_file( + add_partial_html_template, + ext = "html", + pak = pkg, + fp = fp + ) }) }) From b14678f3fe1c397b1a7cce4986c3411174c19e56 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 11 Aug 2022 21:25:52 +0200 Subject: [PATCH 054/190] chore: run devtools::document() --- NAMESPACE | 1 + man/add_files.Rd | 9 +++++++++ man/dockerfiles.Rd | 24 +++++++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index d16aa169..c1d29e95 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,6 +15,7 @@ export(add_js_handler) export(add_js_input_binding) export(add_js_output_binding) export(add_module) +export(add_partial_html_template) export(add_resource_path) export(add_rstudioconnect_file) export(add_sass_file) diff --git a/man/add_files.Rd b/man/add_files.Rd index 48901a8c..ca473887 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -8,6 +8,7 @@ \alias{add_css_file} \alias{add_sass_file} \alias{add_html_template} +\alias{add_partial_html_template} \alias{add_ui_server_files} \title{Create Files} \usage{ @@ -79,6 +80,14 @@ add_html_template( dir_create = TRUE ) +add_partial_html_template( + name = "partial_template.html", + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +) + add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", dir_create = TRUE) } \arguments{ diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 53129521..dfd5715b 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -133,7 +133,7 @@ Default is 0.0.0.0.} \item{expand}{boolean. If \code{TRUE} each system requirement will have its own \code{RUN} line.} -\item{open}{boolean. Should the Dockerfile/README be open after creation? Default is \code{TRUE}.} +\item{open}{boolean. Should the Dockerfile/README/README be open after creation? Default is \code{TRUE}.} \item{update_tar_gz}{boolean. If \code{TRUE} and \code{build_golem_from_source} is also \code{TRUE}, an updated tar.gz is created.} @@ -160,8 +160,8 @@ the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}') The \code{{dockerfiler}} object, invisibly. } \description{ -Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} creates -a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} and +Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} and \code{add_dockerfile_with_renv()} creates +a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} , \code{add_dockerfile_with_renv_shinyproxy()} and \code{add_dockerfile_heroku()} creates platform specific Dockerfile. } \examples{ @@ -178,11 +178,29 @@ if (interactive()) { output_dir = "deploy" ) } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock", # uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} # Add a Dockerfile for ShinyProxy if (interactive()) { add_dockerfile_shinyproxy() } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem with ShinyProxy using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock",# uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} + + # Crete a 'deploy' folder containing everything needed to deploy # the golem with ShinyProxy using docker based on {renv} if (interactive()) { From 3fe84775a5e6a7a4823036caa35ddb8e85b4ae53 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 11 Aug 2022 21:26:05 +0200 Subject: [PATCH 055/190] doc: version bump & news update --- DESCRIPTION | 4 ++-- NEWS.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 411b8d2f..8dfcd2ec 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9000 +Version: 0.3.3.9001 Authors@R: c(person(given = "Colin", family = "Fay", @@ -89,4 +89,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.0 +RoxygenNote: 7.2.1 diff --git a/NEWS.md b/NEWS.md index 13a672bb..fd42f748 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,8 @@ ## New functions ++ Add `add_partial_html_template()` to create a partial html template, with "just" a div and a `{{ }}` (@nathansquan #858). + ## New features From 8dad7283f4c4978fad79fcf3c1743af18db314ed Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Tue, 16 Aug 2022 09:58:28 +0200 Subject: [PATCH 056/190] fix: deprecation message in use_recommended_deps (#901) --- R/use_recommended.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/use_recommended.R b/R/use_recommended.R index e8d62181..3321721e 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -23,8 +23,7 @@ use_recommended_deps <- function( recommended = c("shiny", "DT", "attempt", "glue", "htmltools", "golem") ) { .Deprecated( - "golem::use_recommended_deps", - msg <- "use_recommended_deps() is soft deprecated and will be removed in future versions of {golem}." + msg = "use_recommended_deps() is soft deprecated and will be removed in future versions of {golem}." ) old <- setwd(path_abs(pkg)) From 18f762f3dc49e1231747761e946f9a511269ebcf Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 Aug 2022 10:24:16 +0200 Subject: [PATCH 057/190] doc: NEWS & Version bump --- DESCRIPTION | 2 +- NEWS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8dfcd2ec..4bf5bb1d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9001 +Version: 0.3.3.9002 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index fd42f748..95dfeaba 100644 --- a/NEWS.md +++ b/NEWS.md @@ -25,6 +25,8 @@ + The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) ++ The Deprecation message for `use_recommended_deps` no longer suggest to user `use_recommended_deps` (#900) + ## Internal changes # golem 0.3.3 From 45b3e20c633cb2823b68d7ac98163174d26ef7e4 Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Wed, 17 Aug 2022 09:14:24 +0200 Subject: [PATCH 058/190] 709 win (#904) --- R/desc.R | 2 +- R/golem-yaml-set.R | 2 +- README.Rmd | 2 +- README.md | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/R/desc.R b/R/desc.R index 87c254c7..b26da92d 100644 --- a/R/desc.R +++ b/R/desc.R @@ -72,7 +72,7 @@ fill_desc <- function( pkg = path ) desc$set( - Package = pkg_name + Package = as.character(pkg_name) ) change_app_config_name( name = pkg_name, diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 55f7e24d..4f46045e 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -59,7 +59,7 @@ set_golem_name <- function( ) ) desc$set( - Package = name + Package = as.character(name) ) desc$write( file = "DESCRIPTION" diff --git a/README.Rmd b/README.Rmd index ad948985..61060376 100644 --- a/README.Rmd +++ b/README.Rmd @@ -75,7 +75,7 @@ This package is part of a series of tools for Shiny, which includes: - `r emo::flag("France")` [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) - rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) - `r emo::flag("France")` Rencontres R 2021 : [ Conception d'applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) - +- `r emo::flag("France")` [ Déploiement d'une application {shiny} dans docker avec {renv} et {golem}](https://www.youtube.com/watch?v=diCG4t76k78) diff --git a/README.md b/README.md index 19bddce1..d241396b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ You’re reading the doc about version : ``` r desc::desc_get_version() -#> [1] '0.3.3' +#> [1] '0.3.3.9000' ``` ## Tool series @@ -81,6 +81,8 @@ scratch*](https://towardsdatascience.com/production-grade-r-shiny-with-golem-pro golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) - 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) +- 🇫🇷 [Déploiement d’une application {shiny} dans docker avec {renv} et + {golem}](https://www.youtube.com/watch?v=diCG4t76k78) ### Cheatsheet From f7fdf74c8d75de6940c3c84ae14b68b5308d9bec Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Thu, 18 Aug 2022 08:59:14 +0200 Subject: [PATCH 059/190] fix: add normalizePath() inside set_golem_wd() check() (#908) --- R/golem-yaml-set.R | 5 +++-- R/pkg_tools.R | 4 +++- tests/testthat/helper-config.R | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 4f46045e..e74e4d9f 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -6,9 +6,10 @@ set_golem_wd <- function( pkg = golem::pkg_path(), talkative = TRUE ) { + if ( golem_wd == "golem::pkg_path()" | - golem_wd == golem::pkg_path() + normalizePath(golem_wd) == normalizePath(golem::pkg_path()) ) { golem_yaml_path <- "golem::pkg_path()" attr(golem_yaml_path, "tag") <- "!expr" @@ -59,7 +60,7 @@ set_golem_name <- function( ) ) desc$set( - Package = as.character(name) + Package = name ) desc$write( file = "DESCRIPTION" diff --git a/R/pkg_tools.R b/R/pkg_tools.R index 9169df9a..8cc9d09a 100644 --- a/R/pkg_tools.R +++ b/R/pkg_tools.R @@ -3,7 +3,8 @@ daf_desc <- function( path = ".", entry ) { - unlist( + as.character( + unlist( unname( as.data.frame( read.dcf( @@ -14,6 +15,7 @@ daf_desc <- function( )[entry] ) ) + ) } #' Package tools diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 9273c45d..564d5e41 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -71,7 +71,6 @@ unlink(file.path(tpdir, fakename), recursive = TRUE) create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) - fp <- file.path("inst/app", randir) dir.create(file.path(pkg, fp), recursive = TRUE) @@ -82,6 +81,8 @@ rand_name <- function() { withr::with_dir(pkg, { set_golem_options() usethis::proj_set(pkg) - orig_test <- set_golem_wd(pkg) + orig_test <- set_golem_wd( + pkg = pkg, + ) usethis::use_mit_license("Golem") }) From 3bace0d1a35739528126409bab45274909a03305 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 18 Aug 2022 09:15:06 +0200 Subject: [PATCH 060/190] chore: linter --- R/golem-yaml-set.R | 3 +-- R/pkg_tools.R | 16 ++++++++-------- tests/testthat/helper-config.R | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index e74e4d9f..b1807c32 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -6,10 +6,9 @@ set_golem_wd <- function( pkg = golem::pkg_path(), talkative = TRUE ) { - if ( golem_wd == "golem::pkg_path()" | - normalizePath(golem_wd) == normalizePath(golem::pkg_path()) + normalizePath(golem_wd) == normalizePath(golem::pkg_path()) ) { golem_yaml_path <- "golem::pkg_path()" attr(golem_yaml_path, "tag") <- "!expr" diff --git a/R/pkg_tools.R b/R/pkg_tools.R index 8cc9d09a..6e743294 100644 --- a/R/pkg_tools.R +++ b/R/pkg_tools.R @@ -5,17 +5,17 @@ daf_desc <- function( ) { as.character( unlist( - unname( - as.data.frame( - read.dcf( - normalizePath( - file.path(path, "DESCRIPTION") + unname( + as.data.frame( + read.dcf( + normalizePath( + file.path(path, "DESCRIPTION") + ) ) - ) - )[entry] + )[entry] + ) ) ) - ) } #' Package tools diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 564d5e41..fce360c3 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -82,7 +82,7 @@ withr::with_dir(pkg, { set_golem_options() usethis::proj_set(pkg) orig_test <- set_golem_wd( - pkg = pkg, - ) + pkg = pkg + ) usethis::use_mit_license("Golem") }) From 0f951d9958d675809bb449c94f60d8b309ffa82f Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 18 Aug 2022 09:15:31 +0200 Subject: [PATCH 061/190] doc: version bump & NEWS update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 61b83512..4d5ae647 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9002 +Version: 0.3.3.9003 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index 95dfeaba..906a301b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,6 +27,8 @@ + The Deprecation message for `use_recommended_deps` no longer suggest to user `use_recommended_deps` (#900) ++ The setting of the config file has been unified so that we are sure to keep the !expr in `golem_set_wd()`, and the codebase has been simplified for this (#709). + ## Internal changes # golem 0.3.3 From e73b02d4e10fedf51ff4202e74564796125ed2fd Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 18 Aug 2022 16:08:17 +0200 Subject: [PATCH 062/190] doc: Added min version for rlang To use rlang::check_install(version = ), we need at least {rlang) 1.0.0 Close #905 --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4d5ae647..8f85042c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -57,7 +57,7 @@ Imports: here, fs, htmltools, - rlang, + rlang (>= 1.0.0), rstudioapi, shiny (>= 1.5.0), usethis (>= 1.6.0), From 50b54d8279b0e9cb773832603def646f6c0fb49e Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 18 Aug 2022 16:15:09 +0200 Subject: [PATCH 063/190] doc: version bump & NEWS update --- DESCRIPTION | 2 +- NEWS.md | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8f85042c..dbfc40c1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9003 +Version: 0.3.3.9004 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index 906a301b..fba0e226 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,10 +2,10 @@ # golem 0.3.3.9000+ -### Soft deprecated +## Soft deprecated -### Hard deprecated +## Hard deprecated ## New functions @@ -13,7 +13,7 @@ + Add `add_partial_html_template()` to create a partial html template, with "just" a div and a `{{ }}` (@nathansquan #858). -## New features +## New features / user visible changes + The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}` @@ -21,6 +21,8 @@ + `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. ++ `{golem}` now depends on `{rlang}` version >= 1.0.0 + ## Bug fix + The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) From d4a25b0c8c10ea39209132b24be2b24a46757a6a Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 19 Aug 2022 07:51:21 +0200 Subject: [PATCH 064/190] fix: add_ fns can no longer take length(name) > 1 (#910) Functions that add or use files can no longer take an argument name of length() > 1 close #781 --- R/add_files.R | 45 +++++++++++++++++++++++++------ R/add_r_files.R | 2 ++ R/modules_fn.R | 1 + R/use_files.R | 16 +++++++++++ tests/testthat/helper-config.R | 3 +++ tests/testthat/test-add_files.R | 6 +++++ tests/testthat/test-add_r_files.R | 11 ++++++++ 7 files changed, 76 insertions(+), 8 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 41bc9855..df6a9e60 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -1,3 +1,14 @@ +check_name_length <- function(name) { + stop_if( + name, + ~ length(.x) > 1, + sprintf( + "`name` should be of length 1. Got %d.", + length(name) + ) + ) +} + #' Create Files #' #' These functions create files inside the `inst/app` folder. @@ -43,9 +54,11 @@ add_js_file <- function( ) { stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) old <- setwd(path_abs(pkg)) @@ -112,9 +125,11 @@ add_js_handler <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) old <- setwd(path_abs(pkg)) @@ -176,9 +191,11 @@ add_js_input_binding <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + attempt::stop_if( length(events$name) == 0, msg = "At least one event is required" @@ -317,9 +334,11 @@ add_js_output_binding <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + raw_name <- name name <- file_path_sans_ext( @@ -400,9 +419,11 @@ add_css_file <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) old <- setwd(path_abs(pkg)) @@ -462,9 +483,11 @@ add_sass_file <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) old <- setwd(path_abs(pkg)) @@ -502,7 +525,11 @@ add_sass_file <- function( open ) - add_sass_code(where = where, dir = dir, name = name) + add_sass_code( + where = where, + dir = dir, + name = name + ) cli_alert_info( "After running the compilation, your CSS file will be automatically link in `golem_add_external_resources()`." @@ -527,6 +554,8 @@ add_html_template <- function( ) { name <- file_path_sans_ext(name) + check_name_length(name) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) @@ -595,6 +624,7 @@ add_partial_html_template <- function( dir_create = TRUE ) { name <- file_path_sans_ext(name) + check_name_length(name) old <- setwd(path_abs(pkg)) on.exit(setwd(old)) @@ -692,7 +722,6 @@ add_ui_server_files <- function( "server.R" ) - if (!file_exists(where)) { file_create(where) diff --git a/R/add_r_files.R b/R/add_r_files.R index ef05a1b0..4013f6c7 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -10,6 +10,8 @@ add_r_files <- function( ) { name <- file_path_sans_ext(name) + check_name_length(name) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) diff --git a/R/modules_fn.R b/R/modules_fn.R index feee94ad..6ae04aa7 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -40,6 +40,7 @@ add_module <- function( with_test = FALSE, ... ) { + check_name_length(name) name <- file_path_sans_ext(name) old <- setwd(path_abs(pkg)) diff --git a/R/use_files.R b/R/use_files.R index e41fe94f..f23f4b8c 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -33,6 +33,8 @@ use_external_js_file <- function( name <- basename(url) } + check_name_length(name) + name <- file_path_sans_ext(name) new_file <- sprintf("%s.js", name) @@ -99,6 +101,8 @@ use_external_css_file <- function( name <- basename(url) } + check_name_length(name) + name <- file_path_sans_ext(name) new_file <- sprintf("%s.css", name) @@ -166,6 +170,8 @@ use_external_html_template <- function( file_path_sans_ext(name) ) + check_name_length(name) + dir_created <- create_if_needed( dir, type = "directory" @@ -216,10 +222,13 @@ use_external_file <- function( open = FALSE, dir_create = TRUE ) { + check_name_length(name) + if (missing(name)) { name <- basename(url) } + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) @@ -263,6 +272,7 @@ use_internal_js_file <- function( open = FALSE, dir_create = TRUE ) { + check_name_length(name) old <- setwd(path_abs(pkg)) on.exit(setwd(old)) @@ -328,6 +338,8 @@ use_internal_css_file <- function( open = FALSE, dir_create = TRUE ) { + check_name_length(name) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) @@ -396,6 +408,8 @@ use_internal_html_template <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) + check_name_length(name) + new_file <- sprintf( "%s.html", file_path_sans_ext(name) @@ -454,6 +468,8 @@ use_internal_file <- function( name <- basename(path) } + check_name_length(name) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index fce360c3..52f85e55 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -79,6 +79,9 @@ rand_name <- function() { } withr::with_dir(pkg, { + # Some weird things with {here} + unloadNamespace("here") + here::set_here(".") set_golem_options() usethis::proj_set(pkg) orig_test <- set_golem_wd( diff --git a/tests/testthat/test-add_files.R b/tests/testthat/test-add_files.R index 50a48362..ba8762e2 100644 --- a/tests/testthat/test-add_files.R +++ b/tests/testthat/test-add_files.R @@ -9,6 +9,12 @@ expect_add_file <- function( name <- rand_name() # Be sure to remove all files in case there are remove_files("inst/app/www", ext) + + # Checking that check_name_length is throwing an error + expect_error( + fun(c("a", "b")), + ) + # Launch the function fun(name, pkg = pak, open = FALSE) if (fun_nms == "add_js_input_binding") { diff --git a/tests/testthat/test-add_r_files.R b/tests/testthat/test-add_r_files.R index 7cc95f25..8232bc54 100644 --- a/tests/testthat/test-add_r_files.R +++ b/tests/testthat/test-add_r_files.R @@ -9,6 +9,13 @@ test_that("add_fct and add_utils", { add_fct("ui", pkg = pkg, open = FALSE, with_test = TRUE) add_utils("ui", pkg = pkg, open = FALSE, with_test = TRUE) + expect_error( + add_fct(c("a", "b")), + ) + expect_error( + add_utils(c("a", "b")), + ) + expect_true(file.exists(util_file)) expect_true(file.exists(fct_file)) expect_true(file.exists("tests/testthat/test-utils_ui.R")) @@ -21,6 +28,10 @@ test_that("add_fct and add_utils", { expect_true(file.exists(sprintf("R/mod_%s_fct_ui.R", rand))) expect_true(file.exists(sprintf("R/mod_%s_utils_ui.R", rand))) + expect_error( + add_module(c("a", "b")), + ) + # If module not yet created an error is thrown expect_error( add_fct("ui", module = "notyetcreated", pkg = pkg, open = FALSE), From cb391e81f334e94cd4f9883f8ff5f3a3e7e96289 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 19 Aug 2022 07:55:57 +0200 Subject: [PATCH 065/190] doc: version bump & NEWS update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index dbfc40c1..8906fa57 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9004 +Version: 0.3.3.9005 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index fba0e226..9c5222d2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -31,6 +31,8 @@ + The setting of the config file has been unified so that we are sure to keep the !expr in `golem_set_wd()`, and the codebase has been simplified for this (#709). ++ The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) + ## Internal changes # golem 0.3.3 From e54e2f2f7b57767c0b01c20969eaef05e4bea086 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 19 Aug 2022 09:35:18 +0200 Subject: [PATCH 066/190] feat: install_dev_deps() (#912) --- NAMESPACE | 1 + R/install_dev_deps.R | 87 ++++++++++++++++++++++++++ inst/mantests/build.R | 6 +- inst/shinyexample/dev/01_start.R | 3 + man/install_dev_deps.Rd | 40 ++++++++++++ tests/testthat/test-install_dev_deps.R | 31 +++++++++ 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 R/install_dev_deps.R create mode 100644 man/install_dev_deps.Rd create mode 100644 tests/testthat/test-install_dev_deps.R diff --git a/NAMESPACE b/NAMESPACE index b71dc266..c0da22ca 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -47,6 +47,7 @@ export(get_golem_options) export(get_golem_version) export(get_golem_wd) export(get_sysreqs) +export(install_dev_deps) export(invoke_js) export(is_running) export(js_handler_template) diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R new file mode 100644 index 00000000..9d5760d8 --- /dev/null +++ b/R/install_dev_deps.R @@ -0,0 +1,87 @@ +#' Install {golem} dev dependencies +#' +#' This function will run rlang::check_installed() on: +#' + {usethis} +#' + {pkgload} +#' + {dockerfiler} +#' + {devtools} +#' + {roxygen2} +#' + {attachment} +#' + {rstudioapi} +#' + {here} +#' + {fs} +#' + {desc} +#' + {pkgbuild} +#' + {processx} +#' + {rsconnect} +#' + {testthat} +#' + {rstudioapi} +#' +#' @param force_install If force_install is installed, +#' then the user is not interactively asked +#' to install them. +#' @param .... further arguments passed to the install function. +#' +#' @export +#' +#' @examples +#' if (interactive()) { +#' install_dev_deps() +#' } +install_dev_deps <- function( + force_install = FALSE, + ... +) { + if (!force_install) { + if (!interactive()) { + # In non interactive mode with force_install turned to FALSE, + # The default will be rlang::check_installed. This function + # will stop in non-interactive mode so we throw a warning message. + warning("`install_dev_deps()` will not install dev dependencies in non-interactive mode if `force_install` is not set to `TRUE`.") + } + # Case 1, which will be the standard case, the user + # runs this function in interactive mode, with force_install + # turned to FALSE. We then use rlang::check_installed() as + # an installation function, as it will ask the user if they + # want to install, which is what they want + f <- rlang::check_installed + } else { + # Case 2, the user runs this function with force_install to FALSE + # At that point, the user probably has pak installed + # If yes, the installation function + # will be pak::pkg_install, otherwise + # it is install.packages + if (rlang::is_installed("pak")) { + # We manage the install with {pak} + f <- getFromNamespace("pkg_install", "pak") + } else { + f <- utils::install.packages + } + } + + for ( + pak in unique( + c( + "usethis", + "pkgload", + "dockerfiler", + "devtools", + "roxygen2", + "attachment", + "rstudioapi", + "here", + "fs", + "desc", + "pkgbuild", + "processx", + "rsconnect", + "testthat", + "rstudioapi" + ) + ) + ) { + if (!rlang::is_installed(pak)) { + f(pak, ...) + } + } +} diff --git a/inst/mantests/build.R b/inst/mantests/build.R index d17fec96..e90cca0e 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -41,7 +41,7 @@ if (dir.exists(temp_app)) { dir.create(temp_lib, recursive = TRUE) install.packages( - c("remotes", "desc", "testthat", "cli", "fs", "cranlogs"), + c("remotes", "desc", "testthat", "cli", "fs", "cranlogs", "pak"), lib = temp_lib, repo = "https://cran.rstudio.com/" ) @@ -68,6 +68,10 @@ install_local( lib.loc = temp_lib ) +golem::install_dev_deps( + force_install = TRUE, + lib = temp_lib +) withr::with_tempdir({ cli::cat_rule("Install crystalmountains") diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 1348b281..5ea36dc4 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -31,6 +31,9 @@ golem::fill_desc( ## Set {golem} options ---- golem::set_golem_options() +## Install the required dev dependencies ---- +golem::install_dev_deps() + ## Create Common Files ---- ## See ?usethis for more information usethis::use_mit_license("Golem User") # You can set another license here diff --git a/man/install_dev_deps.Rd b/man/install_dev_deps.Rd new file mode 100644 index 00000000..625dee28 --- /dev/null +++ b/man/install_dev_deps.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/install_dev_deps.R +\name{install_dev_deps} +\alias{install_dev_deps} +\title{Install {golem} dev dependencies} +\usage{ +install_dev_deps(force_install = FALSE, ...) +} +\arguments{ +\item{force_install}{If force_install is installed, +then the user is not interactively asked +to install them.} + +\item{....}{further arguments passed to the install function.} +} +\description{ +This function will run rlang::check_installed() on: +\itemize{ +\item {usethis} +\item {pkgload} +\item {dockerfiler} +\item {devtools} +\item {roxygen2} +\item {attachment} +\item {rstudioapi} +\item {here} +\item {fs} +\item {desc} +\item {pkgbuild} +\item {processx} +\item {rsconnect} +\item {testthat} +\item {rstudioapi} +} +} +\examples{ +if (interactive()) { + install_dev_deps() +} +} diff --git a/tests/testthat/test-install_dev_deps.R b/tests/testthat/test-install_dev_deps.R new file mode 100644 index 00000000..8baa1047 --- /dev/null +++ b/tests/testthat/test-install_dev_deps.R @@ -0,0 +1,31 @@ +test_that("install_dev_deps works", { + install_dev_deps( + force_install = TRUE + ) + paks <- unique( + c( + "usethis", + "pkgload", + "dockerfiler", + "devtools", + "roxygen2", + "attachment", + "rstudioapi", + "here", + "fs", + "desc", + "pkgbuild", + "processx", + "rsconnect", + "testthat", + "rstudioapi" + ) + ) + for ( + pak in paks + ) { + expect_true( + rlang::is_installed(pak) + ) + } +}) From d20e6a7aa592c647605ece74c753f10f5cc6ed4f Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 19 Aug 2022 10:14:02 +0200 Subject: [PATCH 067/190] doc: undocumented object Used .... instead of ... --- R/install_dev_deps.R | 2 +- man/install_dev_deps.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R index 9d5760d8..80c05476 100644 --- a/R/install_dev_deps.R +++ b/R/install_dev_deps.R @@ -20,7 +20,7 @@ #' @param force_install If force_install is installed, #' then the user is not interactively asked #' to install them. -#' @param .... further arguments passed to the install function. +#' @param ... further arguments passed to the install function. #' #' @export #' diff --git a/man/install_dev_deps.Rd b/man/install_dev_deps.Rd index 625dee28..34835239 100644 --- a/man/install_dev_deps.Rd +++ b/man/install_dev_deps.Rd @@ -11,7 +11,7 @@ install_dev_deps(force_install = FALSE, ...) then the user is not interactively asked to install them.} -\item{....}{further arguments passed to the install function.} +\item{...}{further arguments passed to the install function.} } \description{ This function will run rlang::check_installed() on: From 36bd42389e807a73a7be061921bc1c28ad1b0367 Mon Sep 17 00:00:00 2001 From: amarin <17020181+asiripanich@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:15:45 +0700 Subject: [PATCH 068/190] fix: wrong function name (#913) --- inst/shinyexample/dev/02_dev.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index 67699ea7..78f968ca 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -15,7 +15,7 @@ ## Dependencies ---- ## Amend DESCRIPTION with dependencies read from package code parsing -## install.package('attachment') # if needed. +## install.packages('attachment') # if needed. attachment::att_amend_desc() ## Add modules ---- From a7ed156ec5b6b80f49b3f86816b7d2e97a0c4e4a Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 23 Aug 2022 08:41:45 +0200 Subject: [PATCH 069/190] doc: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8906fa57..040fc83d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9005 +Version: 0.3.3.9006 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index 9c5222d2..db65a713 100644 --- a/NEWS.md +++ b/NEWS.md @@ -33,6 +33,8 @@ + The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) ++ The typo in `install.packages()` in 02_dev.R has been corrected (@asiripanich) + ## Internal changes # golem 0.3.3 From 131f03de02f683b850c69bb7d96f40d1dc7da501 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 2 Sep 2022 17:06:10 +0200 Subject: [PATCH 070/190] feat: remove fs as hard dep (#916) close #911 --- NAMESPACE | 10 ---- R/add_dockerfiles.R | 11 ++-- R/add_dockerfiles_renv.R | 16 +++++- R/add_files.R | 116 +++++++++++++++----------------------- R/add_r_files.R | 11 ++-- R/add_rstudio_files.R | 9 ++- R/addins.R | 5 +- R/config.R | 39 ++++++------- R/create_golem.R | 11 ++-- R/desc.R | 5 +- R/disable_autoload.R | 4 +- R/fs_boostrap.R | 82 +++++++++++++++++++++++++++ R/golem-yaml-get.R | 9 +-- R/golem-yaml-set.R | 15 ++--- R/modules_fn.R | 23 ++++---- R/pkg_tools.R | 9 +-- R/reload.R | 2 +- R/use_favicon.R | 20 +++---- R/use_files.R | 80 ++++++++++++-------------- R/use_recommended.R | 23 ++++---- R/use_utils.R | 29 ++++++---- R/utils.R | 62 +++++++++----------- inst/mantests/build.R | 11 +--- man/get_current_config.Rd | 2 +- man/pkg_tools.Rd | 2 +- vignettes/c_deploy.Rmd | 41 ++++++++------ vignettes/e_config.Rmd | 21 ++++--- 27 files changed, 356 insertions(+), 312 deletions(-) create mode 100644 R/fs_boostrap.R diff --git a/NAMESPACE b/NAMESPACE index c0da22ca..1f59daf5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -98,16 +98,6 @@ importFrom(cli,cli_alert_info) importFrom(config,get) importFrom(desc,desc_get_deps) importFrom(desc,description) -importFrom(fs,dir_copy) -importFrom(fs,dir_create) -importFrom(fs,dir_exists) -importFrom(fs,file_copy) -importFrom(fs,file_create) -importFrom(fs,file_delete) -importFrom(fs,file_exists) -importFrom(fs,path) -importFrom(fs,path_abs) -importFrom(fs,path_file) importFrom(htmltools,htmlDependency) importFrom(rstudioapi,documentSaveAll) importFrom(rstudioapi,getSourceEditorContext) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index b3857371..563d6059 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -58,7 +58,6 @@ talk_once <- function(.f, msg = "") { #' @importFrom usethis use_build_ignore #' @importFrom desc desc_get_deps #' @importFrom rstudioapi navigateToFile isAvailable hasFun -#' @importFrom fs path path_file #' #' @examples #' \donttest{ @@ -179,9 +178,9 @@ add_dockerfile_ <- talk_once( reason = "to build a Dockerfile." ) - where <- path(pkg, output) + where <- fs_path(pkg, output) - usethis::use_build_ignore(path_file(where)) + usethis::use_build_ignore(basename(where)) dock <- dockerfiler::dock_from_desc( path = path, @@ -228,7 +227,6 @@ add_dockerfile_ <- talk_once( #' @export #' @rdname dockerfiles -#' @importFrom fs path path_file add_dockerfile_shinyproxy <- function( path = "DESCRIPTION", output = "Dockerfile", @@ -289,7 +287,7 @@ add_dockerfile_shinyproxy_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) - where <- path(pkg, output) + where <- fs_path(pkg, output) usethis::use_build_ignore(output) @@ -332,7 +330,6 @@ add_dockerfile_shinyproxy_ <- talk_once( #' @export #' @rdname dockerfiles -#' @importFrom fs path path_file add_dockerfile_heroku <- function( path = "DESCRIPTION", output = "Dockerfile", @@ -393,7 +390,7 @@ add_dockerfile_heroku_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) - where <- path(pkg, output) + where <- fs_path(pkg, output) usethis::use_build_ignore(output) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index f6639bdf..b9c59503 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -41,10 +41,17 @@ add_dockerfile_with_renv_ <- function( } if (is.null(lockfile)) { - lockfile <- attachment::create_renv_for_prod(path = source_folder, output = file.path(output_dir, "renv.lock.prod")) + lockfile <- attachment::create_renv_for_prod( + path = source_folder, + output = file.path(output_dir, "renv.lock.prod") + ) } - file.copy(from = lockfile, to = output_dir) + fs_file_copy( + path = lockfile, + new_path = output_dir, + overwrite = TRUE + ) socle <- dockerfiler::dock_from_renv( lockfile = lockfile, @@ -289,7 +296,10 @@ add_dockerfile_with_renv_heroku <- function( ) ) - readme_output <- file.path(output_dir, "README") + readme_output <- fs_path( + output_dir, + "README" + ) write_there <- function(...) { write(..., file = readme_output, append = TRUE) diff --git a/R/add_files.R b/R/add_files.R index df6a9e60..08a420ea 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -1,14 +1,3 @@ -check_name_length <- function(name) { - stop_if( - name, - ~ length(.x) > 1, - sprintf( - "`name` should be of length 1. Got %d.", - length(name) - ) - ) -} - #' Create Files #' #' These functions create files inside the `inst/app` folder. @@ -35,7 +24,6 @@ check_name_length <- function(name) { #' @importFrom attempt stop_if #' @importFrom cli cat_bullet #' @importFrom utils file.edit -#' @importFrom fs path_abs path file_create file_exists #' #' @note `add_ui_server_files` will be deprecated in future version of `{golem}` #' @@ -61,7 +49,7 @@ add_js_file <- function( name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -74,16 +62,15 @@ add_js_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) - + if (!fs_file_exists(where)) { + fs_file_create(where) if (with_doc_ready) { write_there <- function(...) { write(..., file = where, append = TRUE) @@ -112,8 +99,6 @@ add_js_file <- function( #' @export #' @rdname add_files -#' -#' @importFrom fs path_abs path file_create file_exists add_js_handler <- function( name, pkg = get_golem_wd(), @@ -132,7 +117,7 @@ add_js_handler <- function( name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -145,15 +130,15 @@ add_js_handler <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- file.path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) template(path = where, ...) @@ -175,7 +160,6 @@ add_js_handler <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_js_input_binding <- function( name, pkg = get_golem_wd(), @@ -212,7 +196,7 @@ add_js_input_binding <- function( sprintf("input-%s", name) ) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -225,15 +209,15 @@ add_js_input_binding <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- file.path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) { write(..., file = where, append = TRUE) @@ -324,7 +308,6 @@ add_js_input_binding <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_js_output_binding <- function( name, pkg = get_golem_wd(), @@ -345,7 +328,7 @@ add_js_output_binding <- function( sprintf("output-%s", name) ) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -358,15 +341,15 @@ add_js_output_binding <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- file.path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) { write(..., file = where, append = TRUE) @@ -407,7 +390,6 @@ add_js_output_binding <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_css_file <- function( name, pkg = get_golem_wd(), @@ -426,7 +408,7 @@ add_css_file <- function( name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -439,9 +421,9 @@ add_css_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, sprintf( "%s.css", @@ -449,8 +431,8 @@ add_css_file <- function( ) ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) template(path = where, ...) file_created_dance( where, @@ -470,7 +452,6 @@ add_css_file <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists #' @importFrom cli cli_alert_info add_sass_file <- function( name, @@ -490,7 +471,7 @@ add_sass_file <- function( name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -503,9 +484,9 @@ add_sass_file <- function( return(invisible(FALSE)) } - dir_abs <- path_abs(dir) + dir_abs <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir_abs, sprintf( "%s.sass", @@ -513,8 +494,8 @@ add_sass_file <- function( ) ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) template(path = where, ...) file_created_dance( where, @@ -544,7 +525,6 @@ add_sass_file <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_html_template <- function( name = "template.html", pkg = get_golem_wd(), @@ -556,7 +536,7 @@ add_html_template <- function( check_name_length(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -569,9 +549,9 @@ add_html_template <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, sprintf( "%s.html", @@ -579,8 +559,8 @@ add_html_template <- function( ) ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) write(..., file = where, append = TRUE) write_there("") write_there("") @@ -615,7 +595,6 @@ add_html_template <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_partial_html_template <- function( name = "partial_template.html", pkg = get_golem_wd(), @@ -626,7 +605,7 @@ add_partial_html_template <- function( name <- file_path_sans_ext(name) check_name_length(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -639,9 +618,9 @@ add_partial_html_template <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, sprintf( "%s.html", @@ -649,8 +628,8 @@ add_partial_html_template <- function( ) ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) write(..., file = where, append = TRUE) write_there("
") write_there(" {{ content }}") @@ -674,7 +653,6 @@ add_partial_html_template <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs file_create add_ui_server_files <- function( pkg = get_golem_wd(), dir = "inst/app", @@ -682,7 +660,7 @@ add_ui_server_files <- function( ) { .Deprecated(msg = "This function will be deprecated in a future version of {golem}.\nPlease comment on https://github.com/ThinkR-open/golem/issues/445 if you want it to stay.") - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -695,13 +673,13 @@ add_ui_server_files <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) # UI - where <- path(dir, "ui.R") + where <- fs_path(dir, "ui.R") - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) write(..., file = where, append = TRUE) @@ -722,8 +700,8 @@ add_ui_server_files <- function( "server.R" ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) write(..., file = where, append = TRUE) diff --git a/R/add_r_files.R b/R/add_r_files.R index 4013f6c7..7624ba10 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -1,4 +1,3 @@ -#' @importFrom fs path_abs path file_create add_r_files <- function( name, ext = c("fct", "utils"), @@ -12,7 +11,7 @@ add_r_files <- function( check_name_length(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -43,15 +42,15 @@ add_r_files <- function( module <- paste0("mod_", module, "_") } - where <- path( + where <- fs_path( "R", paste0(module, ext, "_", name, ".R") ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) - if (file_exists(where) & is.null(module)) { + if (fs_file_exists(where) & is.null(module)) { # Must be a function or utility file being created append_roxygen_comment( name = name, diff --git a/R/add_rstudio_files.R b/R/add_rstudio_files.R index 41966f4e..f756e1e1 100644 --- a/R/add_rstudio_files.R +++ b/R/add_rstudio_files.R @@ -1,7 +1,6 @@ #' @importFrom utils capture.output #' @importFrom cli cat_bullet #' @importFrom usethis use_build_ignore use_package -#' @importFrom fs path file_create path_file add_rstudio_files <- function( pkg, open, @@ -12,7 +11,7 @@ add_rstudio_files <- function( ) ) { service <- match.arg(service) - where <- path(pkg, "app.R") + where <- fs_path(pkg, "app.R") rlang::check_installed( "pkgload", @@ -25,14 +24,14 @@ add_rstudio_files <- function( pkg = pkg ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(..., here = where) { write(..., here, append = TRUE) } - use_build_ignore(path_file(where)) + use_build_ignore(basename(where)) use_build_ignore("rsconnect") write_there("# Launch the ShinyApp (Do not remove this comment)") write_there("# To deploy, run: rsconnect::deployApp()") diff --git a/R/addins.R b/R/addins.R index b998a669..50502b9d 100644 --- a/R/addins.R +++ b/R/addins.R @@ -37,16 +37,15 @@ insert_ns <- function() { rstudioapi::modifyRange(sel_rng, mod_text, id = id) } -#' @importFrom fs path file_exists go_to <- function( file, wd = golem::get_golem_wd() ) { - file <- path( + file <- fs_path( wd, file ) - if (!file_exists(file)) { + if (!fs_file_exists(file)) { message(file, "not found.") } diff --git a/R/config.R b/R/config.R index 4f2ac5cc..37df7748 100644 --- a/R/config.R +++ b/R/config.R @@ -8,9 +8,8 @@ # work #' @importFrom attempt attempt is_try_error -#' @importFrom fs path path_abs guess_where_config <- function( - path = ".", + path = golem::pkg_path(), file = "inst/golem-config.yml" ) { # We'll try to guess where the path @@ -19,23 +18,23 @@ guess_where_config <- function( # This one should be correct in 99% of the case # If we don't change the default values of the params. # => current directory /inst/golem-config.yml - ret_path <- path( + ret_path <- fs_path( path, file ) - if (file_exists(ret_path)) { - return(path_abs(ret_path)) + if (fs_file_exists(ret_path)) { + return(fs_path_abs(ret_path)) } # Maybe for some reason we are in inst/ ret_path <- "golem-config.yml" - if (file_exists(ret_path)) { - return(path_abs(ret_path)) + if (fs_file_exists(ret_path)) { + return(fs_path_abs(ret_path)) } # Trying with pkg_path ret_path <- attempt({ - path( + fs_path( golem::pkg_path(), "inst/golem-config.yml" ) @@ -43,10 +42,10 @@ guess_where_config <- function( if ( !is_try_error(ret_path) & - file_exists(ret_path) + fs_file_exists(ret_path) ) { return( - path_abs(ret_path) + fs_path_abs(ret_path) ) } return(NULL) @@ -58,25 +57,23 @@ guess_where_config <- function( #' If it can't find it, this function asks the #' user if they want to set the golem skeleton. #' -#' @importFrom fs file_copy path -#' #' @param path Path to start looking for the config #' #' @export -get_current_config <- function(path = ".") { +get_current_config <- function(path = getwd()) { # We check wether we can guess where the config file is path_conf <- guess_where_config(path) # We default to inst/ if this doesn't exist if (is.null(path_conf)) { - path_conf <- path( + path_conf <- fs_path( path, "inst/golem-config.yml" ) } - if (!file_exists(path_conf)) { + if (!fs_file_exists(path_conf)) { if (interactive()) { ask <- yesno( sprintf( @@ -89,22 +86,22 @@ get_current_config <- function(path = ".") { return(NULL) } - file_copy( + fs_file_copy( path = golem_sys("shinyexample/inst/golem-config.yml"), - new_path = path( + new_path = fs_path( path, "inst/golem-config.yml" ) ) - file_copy( + fs_file_copy( path = golem_sys("shinyexample/R/app_config.R"), - new_path = file.path( + new_path = fs_path( path, "R/app_config.R" ) ) replace_word( - path( + fs_path( path, "R/app_config.R" ), @@ -134,7 +131,7 @@ change_app_config_name <- function( name, path = get_golem_wd() ) { - pth <- fs::path(path, "R", "app_config.R") + pth <- fs_path(path, "R", "app_config.R") app_config <- readLines(pth) where_system.file <- grep("system.file", app_config) diff --git a/R/create_golem.R b/R/create_golem.R index 566ede49..5f4b5651 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -58,7 +58,6 @@ replace_package_name <- function( #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable openProject hasFun #' @importFrom usethis use_latest_dependencies create_project -#' @importFrom fs dir_copy #' @importFrom yaml write_yaml #' #' @export @@ -75,7 +74,10 @@ create_golem <- function( with_git = FALSE, ... ) { - path_to_golem <- normalizePath(path, mustWork = FALSE) + path_to_golem <- normalizePath( + path, + mustWork = FALSE + ) if (check_name) { cat_rule("Checking package name") @@ -84,7 +86,7 @@ create_golem <- function( } - if (dir.exists(path_to_golem)) { + if (fs_dir_exists(path_to_golem)) { if (!isTRUE(overwrite)) { stop( paste( @@ -104,6 +106,7 @@ create_golem <- function( path = path_to_golem, open = FALSE, ) + here::set_here(path_to_golem) cat_green_tick("Created package directory") } @@ -112,7 +115,7 @@ create_golem <- function( from <- golem_sys("shinyexample") # Copy over whole directory - dir_copy( + fs_dir_copy( path = from, new_path = path_to_golem, overwrite = TRUE diff --git a/R/desc.R b/R/desc.R index b26da92d..45bb25fd 100644 --- a/R/desc.R +++ b/R/desc.R @@ -12,7 +12,6 @@ #' #' @importFrom desc description #' @importFrom cli cat_bullet -#' @importFrom fs path path_abs #' #' @export #' @@ -28,10 +27,10 @@ fill_desc <- function( repo_url = NULL, pkg = get_golem_wd() ) { - path <- path_abs(pkg) + path <- fs_path_abs(pkg) desc <- desc::description$new( - file = path(path, "DESCRIPTION") + file = fs_path(path, "DESCRIPTION") ) if (!is.null(author_orcid) & !is.character(author_orcid)) { diff --git a/R/disable_autoload.R b/R/disable_autoload.R index 0e8f7993..64215656 100644 --- a/R/disable_autoload.R +++ b/R/disable_autoload.R @@ -10,12 +10,12 @@ #' } #' @return The path to the file, invisibly. disable_autoload <- function(pkg = get_golem_wd()) { - fls <- fs::path( + fls <- fs_path( pkg, "R", "_disable_autoload.R" ) - if (fs::file_exists(fls)) { + if (fs_file_exists(fls)) { cat_red_bullet( "_disable_autoload.R already exists, skipping its creation." ) diff --git a/R/fs_boostrap.R b/R/fs_boostrap.R new file mode 100644 index 00000000..55ab5b83 --- /dev/null +++ b/R/fs_boostrap.R @@ -0,0 +1,82 @@ +# All the fns here check that {fs} is installed +# before doing anything. +check_fs_installed <- function() { + rlang::check_installed( + "fs", + reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." + ) +} + +fs_dir_exists <- function(path) { + check_fs_installed() + fs::dir_exists(path) +} + +fs_dir_create <- function( + path, + ..., + mode = "u=rwx,go=rx", + recurse = TRUE, + recursive +) { + check_fs_installed() + fs::dir_create( + path, + ..., + mode = mode, + recurse = recurse, + recursive = recursive + ) +} + + +fs_file_create <- function(where) { + check_fs_installed() + fs::file_create(where) +} + +fs_file_delete <- function(path) { + check_fs_installed() + fs::file_delete(path) +} + +fs_file_exists <- function(path) { + check_fs_installed() + fs::file_exists(path) +} + +fs_path_abs <- function(path) { + check_fs_installed() + fs::path_abs(path) +} + +fs_path <- function(..., ext = "") { + check_fs_installed() + fs::path(..., ext = ext) +} + +fs_file_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::file_copy( + path = path, + new_path = new_path, + overwrite + ) +} + +fs_dir_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::dir_copy( + path, + new_path, + overwrite + ) +} diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R index 5eaa7781..613e720f 100644 --- a/R/golem-yaml-get.R +++ b/R/golem-yaml-get.R @@ -29,13 +29,12 @@ get_golem_things <- function( } #' @export -#' @importFrom fs path_abs #' @rdname golem_opts get_golem_wd <- function( use_parent = TRUE, pkg = golem::pkg_path() ) { - path <- path_abs(pkg) + path <- fs_path_abs(pkg) pth <- get_golem_things( value = "golem_wd", @@ -50,7 +49,6 @@ get_golem_wd <- function( } #' @export -#' @importFrom fs path_abs #' @rdname golem_opts get_golem_name <- function( config = Sys.getenv( @@ -63,7 +61,7 @@ get_golem_name <- function( use_parent = TRUE, pkg = golem::pkg_path() ) { - path <- path_abs(pkg) + path <- fs_path_abs(pkg) nm <- get_golem_things( value = "golem_name", config = config, @@ -77,7 +75,6 @@ get_golem_name <- function( } #' @export -#' @importFrom fs path_abs #' @rdname golem_opts get_golem_version <- function( config = Sys.getenv( @@ -90,7 +87,7 @@ get_golem_version <- function( use_parent = TRUE, pkg = golem::pkg_path() ) { - path <- path_abs(pkg) + path <- fs_path_abs(pkg) vers <- get_golem_things( value = "golem_version", config = config, diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index b1807c32..4e5c26c3 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -1,5 +1,4 @@ #' @export -#' @importFrom fs path_abs #' @rdname golem_opts set_golem_wd <- function( golem_wd = golem::pkg_path(), @@ -13,7 +12,7 @@ set_golem_wd <- function( golem_yaml_path <- "golem::pkg_path()" attr(golem_yaml_path, "tag") <- "!expr" } else { - golem_yaml_path <- path_abs(golem_wd) + golem_yaml_path <- fs_path_abs(golem_wd) } amend_golem_config( @@ -24,18 +23,17 @@ set_golem_wd <- function( talkative = talkative ) - invisible(path) + invisible(golem_yaml_path) } #' @export #' @rdname golem_opts -#' @importFrom fs path_abs set_golem_name <- function( name = golem::pkg_name(), pkg = golem::pkg_path(), talkative = TRUE ) { - path <- path_abs(pkg) + path <- fs_path_abs(pkg) # Changing in YAML amend_golem_config( @@ -53,7 +51,7 @@ set_golem_name <- function( # Changing in DESCRIPTION desc <- desc::description$new( - file = fs::path( + file = fs_path( path, "DESCRIPTION" ) @@ -70,13 +68,12 @@ set_golem_name <- function( #' @export #' @rdname golem_opts -#' @importFrom fs path_abs set_golem_version <- function( version = golem::pkg_version(), pkg = golem::pkg_path(), talkative = TRUE ) { - path <- path_abs(pkg) + path <- fs_path_abs(pkg) # Changing in YAML amend_golem_config( @@ -88,7 +85,7 @@ set_golem_version <- function( ) desc <- desc::description$new( - file = fs::path( + file = fs_path( path, "DESCRIPTION" ) diff --git a/R/modules_fn.R b/R/modules_fn.R index 6ae04aa7..c55d0cac 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -21,7 +21,6 @@ #' @export #' @importFrom cli cat_bullet #' @importFrom utils file.edit -#' @importFrom fs path_abs path file_create #' #' @seealso [module_template()] #' @@ -43,11 +42,11 @@ add_module <- function( check_name_length(name) name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( - path(pkg, "R"), + fs_path(pkg, "R"), type = "directory" ) @@ -56,13 +55,13 @@ add_module <- function( return(invisible(FALSE)) } - where <- path( + where <- fs_path( "R", paste0("mod_", name, ".R") ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) module_template(name = name, path = where, export = export, ...) @@ -269,17 +268,17 @@ use_module_test <- function( "fs" ) - old <- setwd(fs::path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) - if (!dir.exists( - path(pkg, "tests", "testthat") + if (!fs_dir_exists( + fs_path(pkg, "tests", "testthat") )) { usethis::use_testthat() } - path <- fs::path( + path <- fs_path( pkg, "tests", "testthat", @@ -290,8 +289,8 @@ use_module_test <- function( ext = "R" ) - if (!fs::file_exists(path)) { - fs::file_create(path) + if (!fs_file_exists(path)) { + fs_file_create(path) write_there <- function(...) { write(..., file = path, append = TRUE) diff --git a/R/pkg_tools.R b/R/pkg_tools.R index 6e743294..ab3aa4b4 100644 --- a/R/pkg_tools.R +++ b/R/pkg_tools.R @@ -9,7 +9,7 @@ daf_desc <- function( as.data.frame( read.dcf( normalizePath( - file.path(path, "DESCRIPTION") + fs_path(path, "DESCRIPTION") ) ) )[entry] @@ -37,7 +37,8 @@ pkg_version <- function(path = ".") { } #' @export #' @rdname pkg_tools -pkg_path <- function(path = ".") { - rlang::check_installed("here") - here::here() +pkg_path <- function() { + # rlang::check_installed("here") + # here::here() + getwd() } diff --git a/R/reload.R b/R/reload.R index 95e062aa..b124da96 100644 --- a/R/reload.R +++ b/R/reload.R @@ -35,7 +35,7 @@ check_name_consistency <- function(pkg) { old_dir <- setwd(pkg) package_name <- desc::desc_get("Package") - pth <- fs::path( + pth <- fs_path( pkg, "R", "app_config.R" diff --git a/R/use_favicon.R b/R/use_favicon.R index 65953ccc..6ad5a7e7 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -7,7 +7,6 @@ #' @export #' #' @importFrom attempt stop_if_not -#' @importFrom fs path_abs path file_copy #' #' @return Used for side-effects. #' @@ -34,7 +33,7 @@ use_favicon <- function( ) - local <- fs::file_exists(path) + local <- fs_file_exists(path) if (!local) { if (getRversion() >= "3.5") { @@ -65,15 +64,15 @@ use_favicon <- function( destfile, method = method ) - path <- path_abs(destfile) + path <- fs_path_abs(destfile) } - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) - to <- path( - path_abs(pkg), + to <- fs_path( + fs_path_abs(pkg), "inst/app/www", sprintf( "favicon.%s", @@ -82,7 +81,7 @@ use_favicon <- function( ) if (!(path == to)) { - file_copy( + fs_file_copy( path, to, overwrite = TRUE @@ -109,16 +108,15 @@ use_favicon <- function( #' @rdname favicon #' @export -#' @importFrom fs file_delete file_exists remove_favicon <- function(path = "inst/app/www/favicon.ico") { - if (file_exists(path)) { + if (fs_file_exists(path)) { cat_green_tick( sprintf( "Removing favicon at %s", path ) ) - file_delete(path) + fs_file_delete(path) } else { cat_red_bullet( sprintf( @@ -148,7 +146,7 @@ favicon <- function( resources_path = "www", ext = "ico" ) { - ico <- fs::path( + ico <- fs_path( resources_path, ico, ext = ext diff --git a/R/use_files.R b/R/use_files.R index f23f4b8c..f1d479a0 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -15,7 +15,6 @@ #' @export #' @rdname use_files #' @importFrom cli cat_bullet -#' @importFrom fs path_abs path file_exists #' #' @return The path to the file, invisibly. use_external_js_file <- function( @@ -26,7 +25,7 @@ use_external_js_file <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { @@ -48,14 +47,14 @@ use_external_js_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -85,7 +84,6 @@ use_external_js_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_external_css_file <- function( url, name, @@ -94,7 +92,7 @@ use_external_css_file <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { @@ -116,14 +114,14 @@ use_external_css_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -153,7 +151,6 @@ use_external_css_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_external_html_template <- function( url, name = "template.html", @@ -162,7 +159,7 @@ use_external_html_template <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) new_file <- sprintf( @@ -182,14 +179,14 @@ use_external_html_template <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -213,7 +210,6 @@ use_external_html_template <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_external_file <- function( url, name, @@ -229,7 +225,7 @@ use_external_file <- function( } - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -242,14 +238,14 @@ use_external_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, name ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -263,7 +259,6 @@ use_external_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_js_file <- function( path, name, @@ -273,7 +268,7 @@ use_internal_js_file <- function( dir_create = TRUE ) { check_name_length(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { @@ -293,14 +288,14 @@ use_internal_js_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -314,7 +309,7 @@ use_internal_js_file <- function( cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) file_created_dance( where, @@ -329,7 +324,6 @@ use_internal_js_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_css_file <- function( path, name, @@ -340,7 +334,7 @@ use_internal_css_file <- function( ) { check_name_length(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { @@ -360,14 +354,14 @@ use_internal_css_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -381,7 +375,7 @@ use_internal_css_file <- function( cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) file_created_dance( where, @@ -396,7 +390,6 @@ use_internal_css_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_html_template <- function( path, name = "template.html", @@ -405,7 +398,7 @@ use_internal_html_template <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) check_name_length(name) @@ -425,21 +418,21 @@ use_internal_html_template <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) cat_copied(where) @@ -455,7 +448,6 @@ use_internal_html_template <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_file <- function( path, name, @@ -470,7 +462,7 @@ use_internal_file <- function( check_name_length(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -483,21 +475,21 @@ use_internal_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, name ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) cat_copied(where) } diff --git a/R/use_recommended.R b/R/use_recommended.R index 3321721e..c72d85a7 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -11,7 +11,6 @@ #' @param spellcheck Whether or not to use a spellcheck test. #' #' @importFrom usethis use_testthat use_package -#' @importFrom fs path_abs #' @rdname use_recommended #' #' @export @@ -26,7 +25,7 @@ use_recommended_deps <- function( msg = "use_recommended_deps() is soft deprecated and will be removed in future versions of {golem}." ) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) for (i in sort(recommended)) { @@ -42,7 +41,6 @@ use_recommended_deps <- function( #' @importFrom usethis use_testthat use_package use_spell_check #' @importFrom utils capture.output #' @importFrom attempt without_warning stop_if -#' @importFrom fs path_abs path file_exists use_recommended_tests <- function( pkg = get_golem_wd(), spellcheck = TRUE, @@ -50,12 +48,17 @@ use_recommended_tests <- function( lang = "en-US", error = FALSE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) + + rlang::check_installed( + "fs", + reason = "for file & directory manipulation." + ) on.exit(setwd(old)) - if (!dir.exists( - path(path_abs(pkg), "tests") + if (!fs_dir_exists( + fs_path(fs_path_abs(pkg), "tests") )) { without_warning(use_testthat)() } @@ -64,14 +67,14 @@ use_recommended_tests <- function( } stop_if( - path(old, "tests", "testthat", "test-golem-recommended.R"), - file_exists, + fs_path(old, "tests", "testthat", "test-golem-recommended.R"), + fs_file_exists, "test-golem-recommended.R already exists. \nPlease remove it first if you need to reinsert it." ) - file_copy( + fs_file_copy( golem_sys("utils", "test-golem-recommended.R"), - path(old, "tests", "testthat"), + fs_path(old, "tests", "testthat"), overwrite = TRUE ) diff --git a/R/use_utils.R b/R/use_utils.R index 23bcd166..e9de1af3 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -29,10 +29,10 @@ use_utils_ui <- function( cat_green_tick("Utils UI added") if (with_test) { - if (!isTRUE(dir.exists("tests"))) { + if (!isTRUE(fs_dir_exists("tests"))) { use_testthat() } - pth <- path( + pth <- fs_path( pkg, "tests", "testthat", @@ -81,10 +81,10 @@ use_utils_server <- function( cat_green_tick("Utils server added") if (with_test) { - if (!isTRUE(dir.exists("tests"))) { + if (!isTRUE(fs_dir_exists("tests"))) { use_testthat() } - pth <- path( + pth <- fs_path( pkg, "tests", "testthat", @@ -131,23 +131,32 @@ use_utils_test_server <- function(pkg = get_golem_wd()) { use_utils_test_(pkg, "server") } - -#' @importFrom fs file_copy path_abs path_file use_utils <- function( file_name, folder_name, pkg = get_golem_wd() ) { + rlang::check_installed( + "fs", + reason = "for file manipulation." + ) + old <- setwd( - path_abs(pkg) + fs_path_abs(pkg) ) on.exit(setwd(old)) - where <- path(path_abs(pkg), folder_name, file_name) - if (file_exists(where)) { + + where <- fs_path( + fs_path_abs(pkg), + folder_name, + file_name + ) + + if (fs_file_exists(where)) { cat_exists(where) return(FALSE) } else { - file_copy( + fs_file_copy( path = golem_sys("utils", file_name), new_path = where ) diff --git a/R/utils.R b/R/utils.R index ad70b6e3..2246d5ea 100644 --- a/R/utils.R +++ b/R/utils.R @@ -16,22 +16,18 @@ darkgrey <- function(x) { x <- crayon::make_style("darkgrey")(x) } -#' @importFrom fs dir_exists file_exists -dir_not_exist <- Negate(fs::dir_exists) -file_not_exist <- Negate(fs::file_exists) - -#' @importFrom fs dir_create file_create create_if_needed <- function( path, type = c("file", "directory"), content = NULL ) { type <- match.arg(type) + # Check if file or dir already exist if (type == "file") { - dont_exist <- file_not_exist(path) + dont_exist <- Negate(fs_file_exists)(path) } else if (type == "directory") { - dont_exist <- dir_not_exist(path) + dont_exist <- Negate(fs_dir_exists)(path) } # If it doesn't exist, ask if we are allowed # to create it @@ -50,10 +46,10 @@ create_if_needed <- function( } else { # Create the file if (type == "file") { - file_create(path) + fs_file_create(path) write(content, path, append = TRUE) } else if (type == "directory") { - dir_create(path, recurse = TRUE) + fs_dir_create(path, recurse = TRUE) } } } else { @@ -72,10 +68,9 @@ create_if_needed <- function( return(TRUE) } -#' @importFrom fs file_exists check_file_exist <- function(file) { res <- TRUE - if (file_exists(file)) { + if (fs_file_exists(file)) { if (interactive()) { res <- yesno("This file already exists, override?") } else { @@ -85,20 +80,6 @@ check_file_exist <- function(file) { return(res) } -# TODO Remove from codebase -#' @importFrom fs dir_exists -check_dir_exist <- function(dir) { - res <- TRUE - if (!dir_exists(dir)) { - if (interactive()) { - res <- yesno(sprintf("The %s does not exists, create?", dir)) - } else { - res <- FALSE - } - } - return(res) -} - # internal replace_word <- function( file, @@ -161,13 +142,13 @@ cat_exists <- function(where) { cat_red_bullet( sprintf( "[Skipped] %s already exists.", - path_file(where) + basename(where) ) ) cat_info( sprintf( "If you want replace it, remove the %s file first.", - path_file(where) + basename(where) ) ) } @@ -257,7 +238,7 @@ open_or_go_to <- function( } desc_exist <- function(pkg) { - file_exists( + fs_file_exists( paste0(pkg, "/DESCRIPTION") ) } @@ -271,7 +252,7 @@ after_creation_message_js <- function( desc_exist(pkg) ) { if ( - fs::path_abs(dir) != fs::path_abs("inst/app/www") & + fs_path_abs(dir) != fs_path_abs("inst/app/www") & utils::packageVersion("golem") < "0.2.0" ) { cat_red_bullet( @@ -293,7 +274,7 @@ after_creation_message_css <- function( if ( desc_exist(pkg) ) { - if (fs::path_abs(dir) != fs::path_abs("inst/app/www") & + if (fs_path_abs(dir) != fs_path_abs("inst/app/www") & utils::packageVersion("golem") < "0.2.0" ) { cat_red_bullet( @@ -316,7 +297,7 @@ after_creation_message_sass <- function( if ( desc_exist(pkg) ) { - if (fs::path_abs(dir) != fs::path_abs("inst/app/www") & + if (fs_path_abs(dir) != fs_path_abs("inst/app/www") & utils::packageVersion("golem") < "0.2.0" ) { cat_red_bullet( @@ -405,10 +386,9 @@ yesno <- function(...) { menu(c("Yes", "No")) == 1 } -#' @importFrom fs file_exists add_sass_code <- function(where, dir, name) { - if (file_exists(where)) { - if (file_exists("dev/run_dev.R")) { + if (fs_file_exists(where)) { + if (fs_file_exists("dev/run_dev.R")) { lines <- readLines("dev/run_dev.R") new_lines <- append( x = lines, @@ -453,3 +433,17 @@ is_existing_module <- function(module) { ) module %in% existing_module_names } + +# This function is used for checking +# that the name argument of the function +# creating files is not of length() > 1 +check_name_length <- function(name) { + stop_if( + name, + ~ length(.x) > 1, + sprintf( + "`name` should be of length 1. Got %d.", + length(name) + ) + ) +} diff --git a/inst/mantests/build.R b/inst/mantests/build.R index e90cca0e..1f7cde70 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -76,22 +76,13 @@ golem::install_dev_deps( withr::with_tempdir({ cli::cat_rule("Install crystalmountains") - # tmp_cm <- tempfile(fileext = ".zip") - - # download.file( - # "https://github.com/ThinkR-open/crystalmountains/archive/refs/heads/main.zip", - # "main.zip" - # ) - - # unzip(tmp_cm) - remotes::install_github( "thinkr-open/crystalmountains", lib.loc = temp_lib, update = "never" ) - # unlink("crystalmountains-main", TRUE, TRUE) + here::set_here(getwd()) # Going to the temp dir and create a new golem cli::cat_rule("Creating a golem based app") library(golem) diff --git a/man/get_current_config.Rd b/man/get_current_config.Rd index e2ed0da8..b795d92b 100644 --- a/man/get_current_config.Rd +++ b/man/get_current_config.Rd @@ -4,7 +4,7 @@ \alias{get_current_config} \title{Get the path to the current config File} \usage{ -get_current_config(path = ".") +get_current_config(path = getwd()) } \arguments{ \item{path}{Path to start looking for the config} diff --git a/man/pkg_tools.Rd b/man/pkg_tools.Rd index 6103d946..a2c40e54 100644 --- a/man/pkg_tools.Rd +++ b/man/pkg_tools.Rd @@ -10,7 +10,7 @@ pkg_name(path = ".") pkg_version(path = ".") -pkg_path(path = ".") +pkg_path() } \arguments{ \item{path}{Path to use to read the DESCRIPTION} diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index 5ff5b4c7..b8d60c07 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -18,7 +18,7 @@ $(document).ready(function () { ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>", + comment = "#>", eval = FALSE ) ``` @@ -28,18 +28,15 @@ knitr::opts_chunk$set( When launching the app, you might have noticed that the `dev/run_dev.R` function calls `run_app()`, which has the following structure: ```{r} -run_app <- function( - ... -) { +run_app <- function(...) { with_golem_options( app = shinyApp( - ui = app_ui, + ui = app_ui, server = app_server - ), + ), golem_opts = list(...) ) } - ``` This function might looks a little bit weird, but there's a long story behind it, and you can read more about it [there](https://rtask.thinkr.fr/shinyapp-runapp-shinyappdir-difference/). @@ -48,7 +45,7 @@ But long story short, this combination of `with_golem_options` & `golem_opts = l ```{r} run_app(this = "that") -# And in the app +# And in the app this <- get_golem_options("this") ``` @@ -92,7 +89,7 @@ golem::add_dockerfile_heroku() > this functions will create a "deploy" folder containing : -```{txt} +``` deploy/ +-- Dockerfile +-- Dockerfile_base @@ -110,15 +107,25 @@ golem::add_dockerfile_with_renv(output_dir = "deploy") # If you want to deploy to ShinyProxy golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy") - ``` If you would like to use {renv} during developpement, you can init a renv.lock file with ```{r} -attachment::create_renv_for_dev(dev_pkg = c("renv", "devtools", "roxygen2", - "usethis", "pkgload", "testthat", "remotes", "covr", "attachment", - "pak", "dockerfiler","golem")) +attachment::create_renv_for_dev(dev_pkg = c( + "renv", + "devtools", + "roxygen2", + "usethis", + "pkgload", + "testthat", + "remotes", + "covr", + "attachment", + "pak", + "dockerfiler", + "golem" +)) ``` an activate {renv} with @@ -136,17 +143,15 @@ renv::activate() ```{r} # If you want to deploy via a generic Dockerfile -golem::add_dockerfile_with_renv(output_dir = "deploy",lockfile = "renv.lock") +golem::add_dockerfile_with_renv(output_dir = "deploy", lockfile = "renv.lock") # If you want to deploy to ShinyProxy -golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy",lockfile = "renv.lock") - - +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy", lockfile = "renv.lock") ``` > this functions will create a "deploy" folder containing : -```{txt} +``` deploy/ +-- Dockerfile +-- Dockerfile_base diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 5b583ea4..4ad83d4d 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -19,7 +19,8 @@ x <- file.path( ) unlink(x, TRUE, TRUE) -create_golem(x, package_name = "golex", open = FALSE) +x <- golem::create_golem(x, package_name = "golex", open = FALSE) +old <- setwd(x) knitr::opts_knit$set(root.dir = x) ``` @@ -43,13 +44,17 @@ This config file is based on the [`{config}`](https://github.com/rstudio/config) Here is what the default config file looks like: -```{r echo = FALSE, comment= "", } -cat( - sep = "\n", - readLines( - "inst/golem-config.yml" - ) -) +``` +default: + golem_name: golex + golem_version: 0.0.0.9000 + app_prod: no + +production: + app_prod: yes + +dev: + golem_wd: !expr golem::pkg_path() ``` + default/golem_name, default/golem_version, default/app_prod are usable across the whole life of your golem app: while developing, and also when in production. From e7ae1a3696a30771eefd7d2909f627faa061d623 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 2 Sep 2022 17:08:33 +0200 Subject: [PATCH 071/190] doc: version bump & NEWS update --- DESCRIPTION | 2 +- NEWS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 040fc83d..db46dc66 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9006 +Version: 0.3.3.9007 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index db65a713..feb3d628 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,7 +15,7 @@ ## New features / user visible changes -+ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}` ++ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}` + Soft dependency check is now done via `rlang::check_installed()` (#835) From 3d3d951ce5b8a82904534114196a9d22b298ab68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pach=C3=A1?= Date: Thu, 6 Oct 2022 04:29:17 -0400 Subject: [PATCH 072/190] feat: removed usethis hard dep closing #834 --- DESCRIPTION | 2 +- NAMESPACE | 7 ------- R/add_dockerfiles.R | 18 +++++++++++++++++- R/add_dockerfiles_renv.R | 4 ++++ R/add_rstudio_files.R | 11 +++++++---- R/create_golem.R | 8 ++++++-- R/options.R | 8 ++++++-- R/use_recommended.R | 19 +++++++++++++------ R/use_utils.R | 10 +++++++--- 9 files changed, 61 insertions(+), 26 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 411b8d2f..963c1013 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -60,7 +60,6 @@ Imports: rlang, rstudioapi, shiny (>= 1.5.0), - usethis (>= 1.6.0), utils, yaml Suggests: @@ -81,6 +80,7 @@ Suggests: stringr, testthat, tools, + usethis (>= 1.6.0), withr, attachment (>= 0.2.5) VignetteBuilder: diff --git a/NAMESPACE b/NAMESPACE index d16aa169..6c22e155 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -118,13 +118,6 @@ importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) importFrom(shiny,includeScript) importFrom(shiny,tags) -importFrom(usethis,create_project) -importFrom(usethis,proj_set) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_latest_dependencies) -importFrom(usethis,use_package) -importFrom(usethis,use_spell_check) -importFrom(usethis,use_testthat) importFrom(utils,capture.output) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 56a62789..9a01fcdb 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -44,7 +44,6 @@ talk_once <- function(.f, msg = "") { #' @export #' @rdname dockerfiles #' -#' @importFrom usethis use_build_ignore #' @importFrom desc desc_get_deps #' @importFrom rstudioapi navigateToFile isAvailable hasFun #' @importFrom fs path path_file @@ -149,6 +148,11 @@ add_dockerfile_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) + + rlang::check_installed( + "usethis", + reason = "to ignore files in the build." + ) where <- path(pkg, output) @@ -260,6 +264,12 @@ add_dockerfile_shinyproxy_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) + + rlang::check_installed( + "usethis", + reason = "to ignore files in the build." + ) + where <- path(pkg, output) usethis::use_build_ignore(output) @@ -364,6 +374,12 @@ add_dockerfile_heroku_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) + + rlang::check_installed( + "usethis", + reason = "to ignore files in the build." + ) + where <- path(pkg, output) usethis::use_build_ignore(output) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index f6639bdf..98d9f25c 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -26,6 +26,10 @@ add_dockerfile_with_renv_ <- function( version = "0.2.5", reason = "to build a Dockerfile." ) + rlang::check_installed( + "usethis", + reason = "to ignore files in the build." + ) # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on diff --git a/R/add_rstudio_files.R b/R/add_rstudio_files.R index 41966f4e..f071021c 100644 --- a/R/add_rstudio_files.R +++ b/R/add_rstudio_files.R @@ -1,6 +1,5 @@ #' @importFrom utils capture.output #' @importFrom cli cat_bullet -#' @importFrom usethis use_build_ignore use_package #' @importFrom fs path file_create path_file add_rstudio_files <- function( pkg, @@ -19,7 +18,10 @@ add_rstudio_files <- function( reason = "to deploy on RStudio products." ) - rlang::check_installed("usethis") + rlang::check_installed( + "usethis", + reason = "to add dependencies to DESCRIPTION and ignore files in the build." + ) disable_autoload( pkg = pkg @@ -32,8 +34,9 @@ add_rstudio_files <- function( write(..., here, append = TRUE) } - use_build_ignore(path_file(where)) - use_build_ignore("rsconnect") + usethis::use_build_ignore(path_file(where)) + usethis::use_build_ignore("rsconnect") + write_there("# Launch the ShinyApp (Do not remove this comment)") write_there("# To deploy, run: rsconnect::deployApp()") write_there("# Or use the blue button on top of this file") diff --git a/R/create_golem.R b/R/create_golem.R index 35e9e8f2..919960e4 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -24,7 +24,6 @@ #' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable openProject hasFun -#' @importFrom usethis use_latest_dependencies create_project #' @importFrom fs dir_copy #' @importFrom yaml write_yaml #' @@ -42,6 +41,11 @@ create_golem <- function( with_git = FALSE, ... ) { + rlang::check_installed( + "usethis", + reason = "to create projects and use the last version for the dependencies." + ) + path <- normalizePath(path, mustWork = FALSE) if (check_name) { @@ -177,7 +181,7 @@ create_golem <- function( old <- setwd(path) - use_latest_dependencies() + usethis::use_latest_dependencies() # No .Rprofile for now # cat_rule("Appending .Rprofile") diff --git a/R/options.R b/R/options.R index 134eb2a9..c47ef4d3 100644 --- a/R/options.R +++ b/R/options.R @@ -32,7 +32,6 @@ #' @export #' @importFrom attempt stop_if_not #' @importFrom yaml read_yaml write_yaml -#' @importFrom usethis proj_set #' #' @return Used for side-effects for the setters, and values from the #' config in the getters. @@ -43,6 +42,11 @@ set_golem_options <- function( app_prod = FALSE, talkative = TRUE ) { + rlang::check_installed( + "usethis", + reason = "to set project path." + ) + change_app_config_name( name = golem_name, path = golem_wd @@ -130,7 +134,7 @@ set_golem_options <- function( "Setting {usethis} project as `golem_wd`", fun = cli::cat_rule ) - proj_set(golem_wd) + usethis::proj_set(golem_wd) } #' @importFrom yaml read_yaml write_yaml diff --git a/R/use_recommended.R b/R/use_recommended.R index e8d62181..ec953529 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -10,7 +10,6 @@ #' @param recommended A vector of recommended packages. #' @param spellcheck Whether or not to use a spellcheck test. #' -#' @importFrom usethis use_testthat use_package #' @importFrom fs path_abs #' @rdname use_recommended #' @@ -26,12 +25,17 @@ use_recommended_deps <- function( "golem::use_recommended_deps", msg <- "use_recommended_deps() is soft deprecated and will be removed in future versions of {golem}." ) + + rlang::check_installed( + "usethis", + reason = "to add dependencies to DESCRIPTION." + ) old <- setwd(path_abs(pkg)) on.exit(setwd(old)) for (i in sort(recommended)) { - try(use_package(i)) + try(usethis::use_package(i)) } cat_green_tick("Dependencies added") @@ -40,7 +44,6 @@ use_recommended_deps <- function( #' @rdname use_recommended #' @export -#' @importFrom usethis use_testthat use_package use_spell_check #' @importFrom utils capture.output #' @importFrom attempt without_warning stop_if #' @importFrom fs path_abs path file_exists @@ -51,6 +54,11 @@ use_recommended_tests <- function( lang = "en-US", error = FALSE ) { + rlang::check_installed( + "usethis", + reason = "to use unit tests and check spelling." + ) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) @@ -58,7 +66,7 @@ use_recommended_tests <- function( if (!dir.exists( path(path_abs(pkg), "tests") )) { - without_warning(use_testthat)() + without_warning(usethis::use_testthat)() } if (!requireNamespace("processx")) { stop("Please install the {processx} package to add the recommended tests.") @@ -77,13 +85,12 @@ use_recommended_tests <- function( ) if (spellcheck) { - use_spell_check( + usethis::use_spell_check( vignettes = vignettes, lang = lang, error = error ) } - cat_green_tick("Tests added") } diff --git a/R/use_utils.R b/R/use_utils.R index 23bcd166..a46e2b57 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -12,13 +12,17 @@ #' #' @importFrom cli cat_bullet #' @importFrom utils capture.output -#' @importFrom usethis use_testthat #' #' @return Used for side-effects. use_utils_ui <- function( pkg = get_golem_wd(), with_test = FALSE ) { + rlang::check_installed( + "usethis", + reason = "to use unit tests." + ) + added <- use_utils( file_name = "golem_utils_ui.R", folder_name = "R", @@ -30,7 +34,7 @@ use_utils_ui <- function( if (with_test) { if (!isTRUE(dir.exists("tests"))) { - use_testthat() + usethis::use_testthat() } pth <- path( pkg, @@ -82,7 +86,7 @@ use_utils_server <- function( if (with_test) { if (!isTRUE(dir.exists("tests"))) { - use_testthat() + usethis::use_testthat() } pth <- path( pkg, From d53e9f6a588a5bcdbc8c2235f30945032ce9040c Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Thu, 6 Oct 2022 12:41:58 +0200 Subject: [PATCH 073/190] Revert "feat: removed usethis hard dep " (#924) --- DESCRIPTION | 2 +- NAMESPACE | 7 +++++++ R/add_dockerfiles.R | 18 +----------------- R/add_dockerfiles_renv.R | 4 ---- R/add_rstudio_files.R | 11 ++++------- R/create_golem.R | 8 ++------ R/options.R | 8 ++------ R/use_recommended.R | 19 ++++++------------- R/use_utils.R | 10 +++------- 9 files changed, 26 insertions(+), 61 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 963c1013..411b8d2f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -60,6 +60,7 @@ Imports: rlang, rstudioapi, shiny (>= 1.5.0), + usethis (>= 1.6.0), utils, yaml Suggests: @@ -80,7 +81,6 @@ Suggests: stringr, testthat, tools, - usethis (>= 1.6.0), withr, attachment (>= 0.2.5) VignetteBuilder: diff --git a/NAMESPACE b/NAMESPACE index 6c22e155..d16aa169 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -118,6 +118,13 @@ importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) importFrom(shiny,includeScript) importFrom(shiny,tags) +importFrom(usethis,create_project) +importFrom(usethis,proj_set) +importFrom(usethis,use_build_ignore) +importFrom(usethis,use_latest_dependencies) +importFrom(usethis,use_package) +importFrom(usethis,use_spell_check) +importFrom(usethis,use_testthat) importFrom(utils,capture.output) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 9a01fcdb..56a62789 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -44,6 +44,7 @@ talk_once <- function(.f, msg = "") { #' @export #' @rdname dockerfiles #' +#' @importFrom usethis use_build_ignore #' @importFrom desc desc_get_deps #' @importFrom rstudioapi navigateToFile isAvailable hasFun #' @importFrom fs path path_file @@ -148,11 +149,6 @@ add_dockerfile_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) - - rlang::check_installed( - "usethis", - reason = "to ignore files in the build." - ) where <- path(pkg, output) @@ -264,12 +260,6 @@ add_dockerfile_shinyproxy_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) - - rlang::check_installed( - "usethis", - reason = "to ignore files in the build." - ) - where <- path(pkg, output) usethis::use_build_ignore(output) @@ -374,12 +364,6 @@ add_dockerfile_heroku_ <- talk_once( version = "0.2.0", reason = "to build a Dockerfile." ) - - rlang::check_installed( - "usethis", - reason = "to ignore files in the build." - ) - where <- path(pkg, output) usethis::use_build_ignore(output) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 98d9f25c..f6639bdf 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -26,10 +26,6 @@ add_dockerfile_with_renv_ <- function( version = "0.2.5", reason = "to build a Dockerfile." ) - rlang::check_installed( - "usethis", - reason = "to ignore files in the build." - ) # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on diff --git a/R/add_rstudio_files.R b/R/add_rstudio_files.R index f071021c..41966f4e 100644 --- a/R/add_rstudio_files.R +++ b/R/add_rstudio_files.R @@ -1,5 +1,6 @@ #' @importFrom utils capture.output #' @importFrom cli cat_bullet +#' @importFrom usethis use_build_ignore use_package #' @importFrom fs path file_create path_file add_rstudio_files <- function( pkg, @@ -18,10 +19,7 @@ add_rstudio_files <- function( reason = "to deploy on RStudio products." ) - rlang::check_installed( - "usethis", - reason = "to add dependencies to DESCRIPTION and ignore files in the build." - ) + rlang::check_installed("usethis") disable_autoload( pkg = pkg @@ -34,9 +32,8 @@ add_rstudio_files <- function( write(..., here, append = TRUE) } - usethis::use_build_ignore(path_file(where)) - usethis::use_build_ignore("rsconnect") - + use_build_ignore(path_file(where)) + use_build_ignore("rsconnect") write_there("# Launch the ShinyApp (Do not remove this comment)") write_there("# To deploy, run: rsconnect::deployApp()") write_there("# Or use the blue button on top of this file") diff --git a/R/create_golem.R b/R/create_golem.R index 919960e4..35e9e8f2 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -24,6 +24,7 @@ #' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable openProject hasFun +#' @importFrom usethis use_latest_dependencies create_project #' @importFrom fs dir_copy #' @importFrom yaml write_yaml #' @@ -41,11 +42,6 @@ create_golem <- function( with_git = FALSE, ... ) { - rlang::check_installed( - "usethis", - reason = "to create projects and use the last version for the dependencies." - ) - path <- normalizePath(path, mustWork = FALSE) if (check_name) { @@ -181,7 +177,7 @@ create_golem <- function( old <- setwd(path) - usethis::use_latest_dependencies() + use_latest_dependencies() # No .Rprofile for now # cat_rule("Appending .Rprofile") diff --git a/R/options.R b/R/options.R index c47ef4d3..134eb2a9 100644 --- a/R/options.R +++ b/R/options.R @@ -32,6 +32,7 @@ #' @export #' @importFrom attempt stop_if_not #' @importFrom yaml read_yaml write_yaml +#' @importFrom usethis proj_set #' #' @return Used for side-effects for the setters, and values from the #' config in the getters. @@ -42,11 +43,6 @@ set_golem_options <- function( app_prod = FALSE, talkative = TRUE ) { - rlang::check_installed( - "usethis", - reason = "to set project path." - ) - change_app_config_name( name = golem_name, path = golem_wd @@ -134,7 +130,7 @@ set_golem_options <- function( "Setting {usethis} project as `golem_wd`", fun = cli::cat_rule ) - usethis::proj_set(golem_wd) + proj_set(golem_wd) } #' @importFrom yaml read_yaml write_yaml diff --git a/R/use_recommended.R b/R/use_recommended.R index ec953529..e8d62181 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -10,6 +10,7 @@ #' @param recommended A vector of recommended packages. #' @param spellcheck Whether or not to use a spellcheck test. #' +#' @importFrom usethis use_testthat use_package #' @importFrom fs path_abs #' @rdname use_recommended #' @@ -25,17 +26,12 @@ use_recommended_deps <- function( "golem::use_recommended_deps", msg <- "use_recommended_deps() is soft deprecated and will be removed in future versions of {golem}." ) - - rlang::check_installed( - "usethis", - reason = "to add dependencies to DESCRIPTION." - ) old <- setwd(path_abs(pkg)) on.exit(setwd(old)) for (i in sort(recommended)) { - try(usethis::use_package(i)) + try(use_package(i)) } cat_green_tick("Dependencies added") @@ -44,6 +40,7 @@ use_recommended_deps <- function( #' @rdname use_recommended #' @export +#' @importFrom usethis use_testthat use_package use_spell_check #' @importFrom utils capture.output #' @importFrom attempt without_warning stop_if #' @importFrom fs path_abs path file_exists @@ -54,11 +51,6 @@ use_recommended_tests <- function( lang = "en-US", error = FALSE ) { - rlang::check_installed( - "usethis", - reason = "to use unit tests and check spelling." - ) - old <- setwd(path_abs(pkg)) on.exit(setwd(old)) @@ -66,7 +58,7 @@ use_recommended_tests <- function( if (!dir.exists( path(path_abs(pkg), "tests") )) { - without_warning(usethis::use_testthat)() + without_warning(use_testthat)() } if (!requireNamespace("processx")) { stop("Please install the {processx} package to add the recommended tests.") @@ -85,12 +77,13 @@ use_recommended_tests <- function( ) if (spellcheck) { - usethis::use_spell_check( + use_spell_check( vignettes = vignettes, lang = lang, error = error ) } + cat_green_tick("Tests added") } diff --git a/R/use_utils.R b/R/use_utils.R index a46e2b57..23bcd166 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -12,17 +12,13 @@ #' #' @importFrom cli cat_bullet #' @importFrom utils capture.output +#' @importFrom usethis use_testthat #' #' @return Used for side-effects. use_utils_ui <- function( pkg = get_golem_wd(), with_test = FALSE ) { - rlang::check_installed( - "usethis", - reason = "to use unit tests." - ) - added <- use_utils( file_name = "golem_utils_ui.R", folder_name = "R", @@ -34,7 +30,7 @@ use_utils_ui <- function( if (with_test) { if (!isTRUE(dir.exists("tests"))) { - usethis::use_testthat() + use_testthat() } pth <- path( pkg, @@ -86,7 +82,7 @@ use_utils_server <- function( if (with_test) { if (!isTRUE(dir.exists("tests"))) { - usethis::use_testthat() + use_testthat() } pth <- path( pkg, From da97c1a18295ca09d791142098295c4cf626a84e Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Wed, 12 Oct 2022 21:22:20 +0200 Subject: [PATCH 074/190] test: skip suggests package if not installed (#927) --- DESCRIPTION | 7 ++-- R/add_dockerfiles.R | 10 +++--- R/add_r_files.R | 4 --- R/test_helpers.R | 5 --- R/with_opt.R | 42 ++++++++++++------------ man/dockerfiles.Rd | 10 +++--- man/get_golem_options.Rd | 42 ++++++++++++------------ man/testhelpers.Rd | 4 --- tests/testthat/test-add_deploy_helpers.R | 5 ++- tests/testthat/test-extra_sysreqs.R | 3 ++ tests/testthat/test-renv_stuff.R | 3 +- 11 files changed, 65 insertions(+), 70 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 411b8d2f..c3cd3c29 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9000 +Version: 0.3.4 Authors@R: c(person(given = "Colin", family = "Fay", @@ -82,11 +82,12 @@ Suggests: testthat, tools, withr, - attachment (>= 0.2.5) + attachment (>= 0.2.5), + renv VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.0 +RoxygenNote: 7.2.1 diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 56a62789..22dc7b87 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -52,25 +52,25 @@ talk_once <- function(.f, msg = "") { #' @examples #' \donttest{ #' # Add a standard Dockerfile -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile() #' } #' # Crete a 'deploy' folder containing everything needed to deploy #' # the golem using docker based on {renv} -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_with_renv( #' # lockfile = "renv.lock", # uncomment to use existing renv.lock file #' output_dir = "deploy" #' ) #' } #' # Add a Dockerfile for ShinyProxy -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_shinyproxy() #' } #' #' # Crete a 'deploy' folder containing everything needed to deploy #' # the golem with ShinyProxy using docker based on {renv} -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_with_renv( #' # lockfile = "renv.lock",# uncomment to use existing renv.lock file #' output_dir = "deploy" @@ -78,7 +78,7 @@ talk_once <- function(.f, msg = "") { #' } #' #' # Add a Dockerfile for Heroku -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_heroku() #' } #' } diff --git a/R/add_r_files.R b/R/add_r_files.R index ef05a1b0..575fc362 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -67,10 +67,6 @@ add_r_files <- function( } if (with_test) { - rlang::check_installed( - "usethis", - "to build the test structure." - ) usethis::use_test( basename( file_path_sans_ext( diff --git a/R/test_helpers.R b/R/test_helpers.R index 5236ecec..14baa080 100644 --- a/R/test_helpers.R +++ b/R/test_helpers.R @@ -8,9 +8,6 @@ #' @return A testthat result. #' @export #' @rdname testhelpers -#' -#' @examples -#' expect_shinytag(shiny::tags$span("1")) expect_shinytag <- function(object) { rlang::check_installed( "testthat", @@ -27,8 +24,6 @@ expect_shinytag <- function(object) { #' @export #' @rdname testhelpers -#' @examples -#' expect_shinytaglist(shiny::tagList(1)) expect_shinytaglist <- function(object) { rlang::check_installed( "testthat", diff --git a/R/with_opt.R b/R/with_opt.R index dfde7a5d..14af6d68 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -64,38 +64,38 @@ with_golem_options <- function( #' @return The value of the option. #' #' @examples -#' \dontrun{ #' #' # Define and use golem_options +#' if (interactive()) { +#' # 1. Pass parameters to `run_app` #' -#' # 1. Pass parameters to `run_app` +#' # to set default value, edit run_app like this : #' -#' # to set default value, edit run_app like this : -#' run_app <- function( +#' run_app <- function( #' title = "this", #' content = "that" -#' ) { -#' with_golem_options( -#' app = shinyApp( -#' ui = app_ui, -#' server = app_server -#' ), -#' golem_opts = list( -#' p1 = p1, -#' p3 = p3 +#' ) { +#' with_golem_options( +#' app = shinyApp( +#' ui = app_ui, +#' server = app_server +#' ), +#' golem_opts = list( +#' p1 = p1, +#' p3 = p3 +#' ) #' ) -#' ) -#' } +#' } #' -#' # 2. Get the values from the UI side +#' # 2. Get the values from the UI side #' -#' h1(get_golem_options("title")) +#' h1(get_golem_options("title")) #' -#' # 3. Get the value from the server-side +#' # 3. Get the value from the server-side #' -#' output$param <- renderPrint({ -#' paste("param p2 = ", get_golem_options("p2")) -#' }) +#' output$param <- renderPrint({ +#' paste("param p2 = ", get_golem_options("p2")) +#' }) #' } #' get_golem_options <- function(which = NULL) { diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 53129521..d5a4f59c 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -167,25 +167,25 @@ a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_docker \examples{ \donttest{ # Add a standard Dockerfile -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile() } # Crete a 'deploy' folder containing everything needed to deploy # the golem using docker based on {renv} -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_with_renv( # lockfile = "renv.lock", # uncomment to use existing renv.lock file output_dir = "deploy" ) } # Add a Dockerfile for ShinyProxy -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_shinyproxy() } # Crete a 'deploy' folder containing everything needed to deploy # the golem with ShinyProxy using docker based on {renv} -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_with_renv( # lockfile = "renv.lock",# uncomment to use existing renv.lock file output_dir = "deploy" @@ -193,7 +193,7 @@ if (interactive()) { } # Add a Dockerfile for Heroku -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_heroku() } } diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index b768d479..a0e2895f 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -18,38 +18,38 @@ server and UI from your app, in order to call the parameters passed to \code{run_app()}. } \examples{ -\dontrun{ # Define and use golem_options +if (interactive()) { + # 1. Pass parameters to `run_app` -# 1. Pass parameters to `run_app` + # to set default value, edit run_app like this : -# to set default value, edit run_app like this : -run_app <- function( + run_app <- function( title = "this", content = "that" -) { - with_golem_options( - app = shinyApp( - ui = app_ui, - server = app_server - ), - golem_opts = list( - p1 = p1, - p3 = p3 + ) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server + ), + golem_opts = list( + p1 = p1, + p3 = p3 + ) ) - ) -} + } -# 2. Get the values from the UI side + # 2. Get the values from the UI side -h1(get_golem_options("title")) + h1(get_golem_options("title")) -# 3. Get the value from the server-side + # 3. Get the value from the server-side -output$param <- renderPrint({ - paste("param p2 = ", get_golem_options("p2")) -}) + output$param <- renderPrint({ + paste("param p2 = ", get_golem_options("p2")) + }) } } diff --git a/man/testhelpers.Rd b/man/testhelpers.Rd index 6ad0ed56..0b516339 100644 --- a/man/testhelpers.Rd +++ b/man/testhelpers.Rd @@ -35,7 +35,3 @@ A testthat result. These functions are designed to be used inside the tests in your Shiny app package. } -\examples{ -expect_shinytag(shiny::tags$span("1")) -expect_shinytaglist(shiny::tagList(1)) -} diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 7057e65a..b798603f 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -1,5 +1,8 @@ test_that("add_dockerfiles", { - skip_if_not_installed("dockerfiler", "0.1.4") + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.2.5") + with_dir(pkg, { for (fun in list( add_dockerfile, diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index 2b5f34d8..6530b095 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -1,4 +1,7 @@ test_that("test extra sysreqs", { + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.2.5") with_dir(pkg, { for (fun in list( add_dockerfile, diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R index 43d74c15..f63449e4 100644 --- a/tests/testthat/test-renv_stuff.R +++ b/tests/testthat/test-renv_stuff.R @@ -1,6 +1,7 @@ test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all output file are present", { + skip_if_not_installed("renv") skip_if_not_installed("dockerfiler", "0.2.0") - + skip_if_not_installed("attachment", "0.2.5") with_dir(pkg, { for (fun in list( add_dockerfile_with_renv, From e64fd04cf5f7737b14cb74b9342711eef6e60a81 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Wed, 12 Oct 2022 21:23:55 +0200 Subject: [PATCH 075/190] chore: merging changes from CRAN upload (#928) --- DESCRIPTION | 5 +-- R/add_dockerfiles.R | 10 +++--- R/add_r_files.R | 4 --- R/test_helpers.R | 5 --- R/with_opt.R | 42 ++++++++++++------------ man/dockerfiles.Rd | 10 +++--- man/get_golem_options.Rd | 42 ++++++++++++------------ man/testhelpers.Rd | 4 --- tests/testthat/test-add_deploy_helpers.R | 5 ++- tests/testthat/test-extra_sysreqs.R | 3 ++ tests/testthat/test-renv_stuff.R | 3 +- 11 files changed, 64 insertions(+), 69 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index db46dc66..f8ec31f7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9007 +Version: 0.3.4 Authors@R: c(person(given = "Colin", family = "Fay", @@ -82,7 +82,8 @@ Suggests: testthat, tools, withr, - attachment (>= 0.2.5) + attachment (>= 0.2.5), + renv VignetteBuilder: knitr Config/testthat/edition: 3 diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 563d6059..c3ae80aa 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -62,12 +62,12 @@ talk_once <- function(.f, msg = "") { #' @examples #' \donttest{ #' # Add a standard Dockerfile -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile() #' } #' # Crete a 'deploy' folder containing everything needed to deploy #' # the golem using docker based on {renv} -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_with_renv( #' # lockfile = "renv.lock", # uncomment to use existing renv.lock file #' output_dir = "deploy" @@ -82,13 +82,13 @@ talk_once <- function(.f, msg = "") { #' ) #' } #' # Add a Dockerfile for ShinyProxy -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_shinyproxy() #' } #' #' # Crete a 'deploy' folder containing everything needed to deploy #' # the golem with ShinyProxy using docker based on {renv} -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_with_renv( #' # lockfile = "renv.lock",# uncomment to use existing renv.lock file #' output_dir = "deploy" @@ -106,7 +106,7 @@ talk_once <- function(.f, msg = "") { #' } #' #' # Add a Dockerfile for Heroku -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_heroku() #' } #' } diff --git a/R/add_r_files.R b/R/add_r_files.R index 7624ba10..1e6e66fa 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -68,10 +68,6 @@ add_r_files <- function( } if (with_test) { - rlang::check_installed( - "usethis", - "to build the test structure." - ) usethis::use_test( basename( file_path_sans_ext( diff --git a/R/test_helpers.R b/R/test_helpers.R index 5236ecec..14baa080 100644 --- a/R/test_helpers.R +++ b/R/test_helpers.R @@ -8,9 +8,6 @@ #' @return A testthat result. #' @export #' @rdname testhelpers -#' -#' @examples -#' expect_shinytag(shiny::tags$span("1")) expect_shinytag <- function(object) { rlang::check_installed( "testthat", @@ -27,8 +24,6 @@ expect_shinytag <- function(object) { #' @export #' @rdname testhelpers -#' @examples -#' expect_shinytaglist(shiny::tagList(1)) expect_shinytaglist <- function(object) { rlang::check_installed( "testthat", diff --git a/R/with_opt.R b/R/with_opt.R index dfde7a5d..14af6d68 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -64,38 +64,38 @@ with_golem_options <- function( #' @return The value of the option. #' #' @examples -#' \dontrun{ #' #' # Define and use golem_options +#' if (interactive()) { +#' # 1. Pass parameters to `run_app` #' -#' # 1. Pass parameters to `run_app` +#' # to set default value, edit run_app like this : #' -#' # to set default value, edit run_app like this : -#' run_app <- function( +#' run_app <- function( #' title = "this", #' content = "that" -#' ) { -#' with_golem_options( -#' app = shinyApp( -#' ui = app_ui, -#' server = app_server -#' ), -#' golem_opts = list( -#' p1 = p1, -#' p3 = p3 +#' ) { +#' with_golem_options( +#' app = shinyApp( +#' ui = app_ui, +#' server = app_server +#' ), +#' golem_opts = list( +#' p1 = p1, +#' p3 = p3 +#' ) #' ) -#' ) -#' } +#' } #' -#' # 2. Get the values from the UI side +#' # 2. Get the values from the UI side #' -#' h1(get_golem_options("title")) +#' h1(get_golem_options("title")) #' -#' # 3. Get the value from the server-side +#' # 3. Get the value from the server-side #' -#' output$param <- renderPrint({ -#' paste("param p2 = ", get_golem_options("p2")) -#' }) +#' output$param <- renderPrint({ +#' paste("param p2 = ", get_golem_options("p2")) +#' }) #' } #' get_golem_options <- function(which = NULL) { diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index dfd5715b..35fdab0c 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -167,12 +167,12 @@ a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_docker \examples{ \donttest{ # Add a standard Dockerfile -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile() } # Crete a 'deploy' folder containing everything needed to deploy # the golem using docker based on {renv} -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_with_renv( # lockfile = "renv.lock", # uncomment to use existing renv.lock file output_dir = "deploy" @@ -187,13 +187,13 @@ if (interactive()) { ) } # Add a Dockerfile for ShinyProxy -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_shinyproxy() } # Crete a 'deploy' folder containing everything needed to deploy # the golem with ShinyProxy using docker based on {renv} -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_with_renv( # lockfile = "renv.lock",# uncomment to use existing renv.lock file output_dir = "deploy" @@ -211,7 +211,7 @@ if (interactive()) { } # Add a Dockerfile for Heroku -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_heroku() } } diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index b768d479..a0e2895f 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -18,38 +18,38 @@ server and UI from your app, in order to call the parameters passed to \code{run_app()}. } \examples{ -\dontrun{ # Define and use golem_options +if (interactive()) { + # 1. Pass parameters to `run_app` -# 1. Pass parameters to `run_app` + # to set default value, edit run_app like this : -# to set default value, edit run_app like this : -run_app <- function( + run_app <- function( title = "this", content = "that" -) { - with_golem_options( - app = shinyApp( - ui = app_ui, - server = app_server - ), - golem_opts = list( - p1 = p1, - p3 = p3 + ) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server + ), + golem_opts = list( + p1 = p1, + p3 = p3 + ) ) - ) -} + } -# 2. Get the values from the UI side + # 2. Get the values from the UI side -h1(get_golem_options("title")) + h1(get_golem_options("title")) -# 3. Get the value from the server-side + # 3. Get the value from the server-side -output$param <- renderPrint({ - paste("param p2 = ", get_golem_options("p2")) -}) + output$param <- renderPrint({ + paste("param p2 = ", get_golem_options("p2")) + }) } } diff --git a/man/testhelpers.Rd b/man/testhelpers.Rd index 6ad0ed56..0b516339 100644 --- a/man/testhelpers.Rd +++ b/man/testhelpers.Rd @@ -35,7 +35,3 @@ A testthat result. These functions are designed to be used inside the tests in your Shiny app package. } -\examples{ -expect_shinytag(shiny::tags$span("1")) -expect_shinytaglist(shiny::tagList(1)) -} diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 7057e65a..b798603f 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -1,5 +1,8 @@ test_that("add_dockerfiles", { - skip_if_not_installed("dockerfiler", "0.1.4") + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.2.5") + with_dir(pkg, { for (fun in list( add_dockerfile, diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index 2b5f34d8..6530b095 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -1,4 +1,7 @@ test_that("test extra sysreqs", { + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.2.5") with_dir(pkg, { for (fun in list( add_dockerfile, diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R index 43d74c15..f63449e4 100644 --- a/tests/testthat/test-renv_stuff.R +++ b/tests/testthat/test-renv_stuff.R @@ -1,6 +1,7 @@ test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all output file are present", { + skip_if_not_installed("renv") skip_if_not_installed("dockerfiler", "0.2.0") - + skip_if_not_installed("attachment", "0.2.5") with_dir(pkg, { for (fun in list( add_dockerfile_with_renv, From 07e31b7042c0575b4e8545d7915ce5c4c178573e Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 12 Oct 2022 21:27:59 +0200 Subject: [PATCH 076/190] chore: version bump We needed to release a version for golem to stay on CRAN but a real release was not ready, hence a small 0.3.4 --- DESCRIPTION | 2 +- NEWS.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index db46dc66..58c2ca47 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.3.9007 +Version: 0.3.3.9008 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index feb3d628..425660a8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. -# golem 0.3.3.9000+ +# golem 0.3.4.9000+ ## Soft deprecated @@ -37,6 +37,10 @@ ## Internal changes +# golem 0.3.4. + +This version has a small internal change in the tests, so that it can stay on CRAN. + # golem 0.3.3 ## New functions From e6f3fad0251ba02c4f0b7f20e2240cf0c14369d9 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Wed, 12 Oct 2022 22:11:39 +0200 Subject: [PATCH 077/190] feat: removed {usethis} hard dep (#929) --- DESCRIPTION | 8 +-- NAMESPACE | 7 --- NEWS.md | 2 +- R/add_dockerfiles.R | 22 ++------ R/add_dockerfiles_renv.R | 2 +- R/add_r_files.R | 2 +- R/add_rstudio_files.R | 9 +-- R/create_golem.R | 17 ++++-- R/modules_fn.R | 2 +- R/set_golem_options.R | 3 +- R/use_recommended.R | 15 +---- R/use_utils.R | 5 +- R/usethis_bootstrap.R | 116 +++++++++++++++++++++++++++++++++++++++ 13 files changed, 151 insertions(+), 59 deletions(-) create mode 100644 R/usethis_bootstrap.R diff --git a/DESCRIPTION b/DESCRIPTION index a507f601..e44fbbda 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.4.9000 +Version: 0.3.4.9001 Authors@R: c(person(given = "Colin", family = "Fay", @@ -55,12 +55,10 @@ Imports: crayon, desc, here, - fs, htmltools, rlang (>= 1.0.0), rstudioapi, shiny (>= 1.5.0), - usethis (>= 1.6.0), utils, yaml Suggests: @@ -83,7 +81,9 @@ Suggests: tools, withr, attachment (>= 0.2.5), - renv + renv, + usethis (>= 1.6.0), + fs VignetteBuilder: knitr Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index 1f59daf5..7cdfebe7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -111,13 +111,6 @@ importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) importFrom(shiny,includeScript) importFrom(shiny,tags) -importFrom(usethis,create_project) -importFrom(usethis,proj_set) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_latest_dependencies) -importFrom(usethis,use_package) -importFrom(usethis,use_spell_check) -importFrom(usethis,use_testthat) importFrom(utils,capture.output) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) diff --git a/NEWS.md b/NEWS.md index 425660a8..d39b15dc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,7 +15,7 @@ ## New features / user visible changes -+ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}` ++ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}`, `{usethis}` + Soft dependency check is now done via `rlang::check_installed()` (#835) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index c3ae80aa..f7821b49 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -9,17 +9,6 @@ talk_once <- function(.f, msg = "") { } } -talk_once <- function(.f, msg = "") { - talk <- TRUE - function(...) { - if (talk) { - talk <<- FALSE - cat_red_bullet(msg) - } - .f(...) - } -} - #' Create a Dockerfile for your App #' #' Build a container containing your Shiny App. `add_dockerfile()` and `add_dockerfile_with_renv()` and `add_dockerfile_with_renv()` creates @@ -55,7 +44,6 @@ talk_once <- function(.f, msg = "") { #' @export #' @rdname dockerfiles #' -#' @importFrom usethis use_build_ignore #' @importFrom desc desc_get_deps #' @importFrom rstudioapi navigateToFile isAvailable hasFun #' @@ -180,7 +168,9 @@ add_dockerfile_ <- talk_once( where <- fs_path(pkg, output) - usethis::use_build_ignore(basename(where)) + usethis_use_build_ignore( + basename(where) + ) dock <- dockerfiler::dock_from_desc( path = path, @@ -289,7 +279,7 @@ add_dockerfile_shinyproxy_ <- talk_once( ) where <- fs_path(pkg, output) - usethis::use_build_ignore(output) + usethis_use_build_ignore(output) dock <- dockerfiler::dock_from_desc( path = path, @@ -392,7 +382,7 @@ add_dockerfile_heroku_ <- talk_once( ) where <- fs_path(pkg, output) - usethis::use_build_ignore(output) + usethis_use_build_ignore(output) dock <- dockerfiler::dock_from_desc( path = path, @@ -455,7 +445,7 @@ add_dockerfile_heroku_ <- talk_once( try(file.edit(output)) } } - usethis::use_build_ignore(files = output) + usethis_use_build_ignore(files = output) return(invisible(dock)) }, " diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index b9c59503..93f5d3fb 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -37,7 +37,7 @@ add_dockerfile_with_renv_ <- function( # add output_dir in Rbuildignore if the output is inside the golem if (normalizePath(dirname(output_dir)) == normalizePath(source_folder)) { - usethis::use_build_ignore(output_dir) + usethis_use_build_ignore(output_dir) } if (is.null(lockfile)) { diff --git a/R/add_r_files.R b/R/add_r_files.R index 1e6e66fa..a4bcb24f 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -68,7 +68,7 @@ add_r_files <- function( } if (with_test) { - usethis::use_test( + usethis_use_test( basename( file_path_sans_ext( where diff --git a/R/add_rstudio_files.R b/R/add_rstudio_files.R index f756e1e1..44955959 100644 --- a/R/add_rstudio_files.R +++ b/R/add_rstudio_files.R @@ -1,6 +1,5 @@ #' @importFrom utils capture.output #' @importFrom cli cat_bullet -#' @importFrom usethis use_build_ignore use_package add_rstudio_files <- function( pkg, open, @@ -18,8 +17,6 @@ add_rstudio_files <- function( reason = "to deploy on RStudio products." ) - rlang::check_installed("usethis") - disable_autoload( pkg = pkg ) @@ -31,8 +28,8 @@ add_rstudio_files <- function( write(..., here, append = TRUE) } - use_build_ignore(basename(where)) - use_build_ignore("rsconnect") + usethis_use_build_ignore(basename(where)) + usethis_use_build_ignore("rsconnect") write_there("# Launch the ShinyApp (Do not remove this comment)") write_there("# To deploy, run: rsconnect::deployApp()") write_there("# Or use the blue button on top of this file") @@ -47,7 +44,7 @@ add_rstudio_files <- function( ) # We add {pkgload} as a dep because it's required to deploy on Connect & stuff - usethis::use_package("pkgload") + usethis_use_package("pkgload") cat_created(where) cat_line("To deploy, run:") diff --git a/R/create_golem.R b/R/create_golem.R index 5f4b5651..12bc8db6 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -57,7 +57,6 @@ replace_package_name <- function( #' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable openProject hasFun -#' @importFrom usethis use_latest_dependencies create_project #' @importFrom yaml write_yaml #' #' @export @@ -81,7 +80,15 @@ create_golem <- function( if (check_name) { cat_rule("Checking package name") - getFromNamespace("check_package_name", "usethis")(package_name) + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = "to check the package name." + ) + getFromNamespace( + "check_package_name", + "usethis" + )(package_name) cat_green_tick("Valid package name") } @@ -102,9 +109,9 @@ create_golem <- function( } } else { cat_rule("Creating dir") - usethis::create_project( + usethis_create_project( path = path_to_golem, - open = FALSE, + open = FALSE ) here::set_here(path_to_golem) cat_green_tick("Created package directory") @@ -188,7 +195,7 @@ create_golem <- function( old <- setwd(path_to_golem) - use_latest_dependencies() + usethis_use_latest_dependencies() # No .Rprofile for now # cat_rule("Appending .Rprofile") diff --git a/R/modules_fn.R b/R/modules_fn.R index c55d0cac..557e1089 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -275,7 +275,7 @@ use_module_test <- function( if (!fs_dir_exists( fs_path(pkg, "tests", "testthat") )) { - usethis::use_testthat() + usethis_use_testthat() } path <- fs_path( diff --git a/R/set_golem_options.R b/R/set_golem_options.R index bf9d59e8..28ddd90c 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -32,7 +32,6 @@ #' #' @export #' @importFrom attempt stop_if_not -#' @importFrom usethis proj_set #' #' @return Used for side-effects for the setters, and values from the #' config in the getters. @@ -96,5 +95,5 @@ set_golem_options <- function( ) } - proj_set(golem_wd) + usethis_proj_set(golem_wd) } diff --git a/R/use_recommended.R b/R/use_recommended.R index c72d85a7..1a348a9c 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -10,7 +10,6 @@ #' @param recommended A vector of recommended packages. #' @param spellcheck Whether or not to use a spellcheck test. #' -#' @importFrom usethis use_testthat use_package #' @rdname use_recommended #' #' @export @@ -29,7 +28,7 @@ use_recommended_deps <- function( on.exit(setwd(old)) for (i in sort(recommended)) { - try(use_package(i)) + try(usethis_use_package(i)) } cat_green_tick("Dependencies added") @@ -38,7 +37,6 @@ use_recommended_deps <- function( #' @rdname use_recommended #' @export -#' @importFrom usethis use_testthat use_package use_spell_check #' @importFrom utils capture.output #' @importFrom attempt without_warning stop_if use_recommended_tests <- function( @@ -50,17 +48,12 @@ use_recommended_tests <- function( ) { old <- setwd(fs_path_abs(pkg)) - rlang::check_installed( - "fs", - reason = "for file & directory manipulation." - ) - on.exit(setwd(old)) if (!fs_dir_exists( fs_path(fs_path_abs(pkg), "tests") )) { - without_warning(use_testthat)() + without_warning(usethis_use_testthat)() } if (!requireNamespace("processx")) { stop("Please install the {processx} package to add the recommended tests.") @@ -79,13 +72,11 @@ use_recommended_tests <- function( ) if (spellcheck) { - use_spell_check( + usethis_use_spell_check( vignettes = vignettes, lang = lang, error = error ) } - - cat_green_tick("Tests added") } diff --git a/R/use_utils.R b/R/use_utils.R index e9de1af3..ef2c1f46 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -12,7 +12,6 @@ #' #' @importFrom cli cat_bullet #' @importFrom utils capture.output -#' @importFrom usethis use_testthat #' #' @return Used for side-effects. use_utils_ui <- function( @@ -30,7 +29,7 @@ use_utils_ui <- function( if (with_test) { if (!isTRUE(fs_dir_exists("tests"))) { - use_testthat() + usethis_use_testthat() } pth <- fs_path( pkg, @@ -82,7 +81,7 @@ use_utils_server <- function( if (with_test) { if (!isTRUE(fs_dir_exists("tests"))) { - use_testthat() + usethis_use_testthat() } pth <- fs_path( pkg, diff --git a/R/usethis_bootstrap.R b/R/usethis_bootstrap.R new file mode 100644 index 00000000..1e513da5 --- /dev/null +++ b/R/usethis_bootstrap.R @@ -0,0 +1,116 @@ + +# All the fns here check that {usethis} is installed +# before doing anything. +check_usethis_installed <- function(reason = "for project and file manipulation.") { + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = reason + ) +} + +usethis_use_build_ignore <- function( + files, + escape = TRUE +) { + check_usethis_installed( + reason = "to ignore files in the build." + ) + usethis::use_build_ignore( + files, + escape + ) +} +usethis_use_package <- function( + package, + type = "Imports", + min_version = NULL +) { + check_usethis_installed( + reason = "to add dependencies to DESCRIPTION." + ) + usethis::use_package( + package, + type, + min_version + ) +} + +usethis_create_project <- function( + path, + rstudio = rstudioapi::isAvailable(), + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to create a project." + ) + usethis::create_project( + path, + rstudio, + open + ) +} +usethis_use_latest_dependencies <- function( + overwrite = FALSE, + source = c("local", "CRAN") +) { + check_usethis_installed( + reason = "to set dependency version." + ) + usethis::use_latest_dependencies( + overwrite, + source + ) +} + +usethis_proj_set <- function( + path = ".", + force = FALSE +) { + check_usethis_installed( + reason = "to set project." + ) + usethis::proj_set( + path, + force + ) +} +usethis_use_testthat <- function( + edition = NULL, + parallel = FALSE +) { + check_usethis_installed( + reason = "to add {testthat} infrastructure." + ) + usethis::use_testthat( + edition, + parallel + ) +} +usethis_use_test <- function( + name = NULL, + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to add tests." + ) + usethis::use_test( + name, + open + ) +} + +usethis_use_spell_check <- function( + vignettes = TRUE, + lang = "en-US", + error = FALSE +) { + check_usethis_installed( + reason = "to add spellcheck." + ) + usethis::use_spell_check( + vignettes, + lang, + error + ) +} From 9a809bb29a75466ac8466db86754524eba911281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rochette?= Date: Thu, 13 Oct 2022 08:43:11 +0200 Subject: [PATCH 078/190] fix: unit test requires dependencies in suggests (#919) --- tests/testthat/test-extra_sysreqs.R | 2 ++ tests/testthat/test-pkg_tools.R | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index 6530b095..d0e47254 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -1,3 +1,5 @@ +skip_if_not_installed("dockerfiler", minimum_version = "0.2.0") + test_that("test extra sysreqs", { skip_if_not_installed("renv") skip_if_not_installed("dockerfiler", "0.2.0") diff --git a/tests/testthat/test-pkg_tools.R b/tests/testthat/test-pkg_tools.R index d56c0a43..bc1af90a 100644 --- a/tests/testthat/test-pkg_tools.R +++ b/tests/testthat/test-pkg_tools.R @@ -1,9 +1,11 @@ +skip_if_not_installed("pkgload") + test_that("pkgtools works", { withr::with_dir(pkg, { - expect_equal(pkg_name(), fakename) - expect_equal(pkg_version(), "0.0.0.9000") + expect_equal(pkgload::pkg_name(), fakename) + expect_equal(as.character(pkgload::pkg_version()), "0.0.0.9000") # F-word windows path skip_on_os("windows") - expect_equal(pkg_path(), pkg) + expect_equal(pkgload::pkg_path(), pkg) }) }) From 4f77e1b8659eb52580a17bb31fe789594e4063b6 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 14 Oct 2022 08:46:29 +0200 Subject: [PATCH 079/190] refactor: check_installed in their own files (#931) --- R/add_dockerfiles.R | 23 ++------ R/add_dockerfiles_renv.R | 21 +++---- R/boostrap_fs.R | 82 +++++++++++++++++++++++++++ R/bootstrap_attachment.R | 23 ++++++++ R/bootstrap_dockerfiler.R | 68 ++++++++++++++++++++++ R/bootstrap_pkgload.R | 37 ++++++++++++ R/bootstrap_roxygen2.R | 23 ++++++++ R/bootstrap_usethis.R | 116 ++++++++++++++++++++++++++++++++++++++ R/fs_boostrap.R | 82 --------------------------- R/modules_fn.R | 10 +--- R/reload.R | 5 +- R/use_utils.R | 4 -- R/usethis_bootstrap.R | 116 -------------------------------------- 13 files changed, 365 insertions(+), 245 deletions(-) create mode 100644 R/boostrap_fs.R create mode 100644 R/bootstrap_attachment.R create mode 100644 R/bootstrap_dockerfiler.R create mode 100644 R/bootstrap_pkgload.R create mode 100644 R/bootstrap_roxygen2.R create mode 100644 R/bootstrap_usethis.R delete mode 100644 R/fs_boostrap.R delete mode 100644 R/usethis_bootstrap.R diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index f7821b49..168c1dc3 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -160,11 +160,6 @@ add_dockerfile_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) where <- fs_path(pkg, output) @@ -172,7 +167,7 @@ add_dockerfile_ <- talk_once( basename(where) ) - dock <- dockerfiler::dock_from_desc( + dock <- dockerfiler_dock_from_desc( path = path, FROM = from, AS = as, @@ -272,16 +267,12 @@ add_dockerfile_shinyproxy_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) + where <- fs_path(pkg, output) usethis_use_build_ignore(output) - dock <- dockerfiler::dock_from_desc( + dock <- dockerfiler_dock_from_desc( path = path, FROM = from, AS = as, @@ -375,16 +366,12 @@ add_dockerfile_heroku_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) + where <- fs_path(pkg, output) usethis_use_build_ignore(output) - dock <- dockerfiler::dock_from_desc( + dock <- dockerfiler_dock_from_desc( path = path, FROM = from, AS = as, diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 93f5d3fb..160a357b 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -16,17 +16,7 @@ add_dockerfile_with_renv_ <- function( "renv", reason = "to build a Dockerfile." ) - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) - rlang::check_installed( - "attachment", - version = "0.2.5", - reason = "to build a Dockerfile." - ) - + # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on x <- suppressWarnings({ @@ -41,7 +31,7 @@ add_dockerfile_with_renv_ <- function( } if (is.null(lockfile)) { - lockfile <- attachment::create_renv_for_prod( + lockfile <- attachment_create_renv_for_prod( path = source_folder, output = file.path(output_dir, "renv.lock.prod") ) @@ -53,7 +43,7 @@ add_dockerfile_with_renv_ <- function( overwrite = TRUE ) - socle <- dockerfiler::dock_from_renv( + socle <- dockerfiler_dock_from_renv( lockfile = lockfile, distro = distro, FROM = FROM, @@ -66,7 +56,10 @@ add_dockerfile_with_renv_ <- function( socle$write(as = file.path(output_dir, "Dockerfile_base")) - my_dock <- dockerfiler::Dockerfile$new(FROM = paste0(golem::get_golem_name(), "_base")) + my_dock <- dockerfiler_Dockerfile()$new( + FROM = paste0(golem::get_golem_name(), + "_base" + )) my_dock$COPY("renv.lock.prod", "renv.lock") diff --git a/R/boostrap_fs.R b/R/boostrap_fs.R new file mode 100644 index 00000000..9b022bf0 --- /dev/null +++ b/R/boostrap_fs.R @@ -0,0 +1,82 @@ +# All the fns here check that {fs} is installed +# before doing anything. +check_fs_installed <- function() { + rlang::check_installed( + "fs", + reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." + ) +} + +fs_dir_exists <- function(path) { + check_fs_installed() + fs::dir_exists(path) +} + +fs_dir_create <- function( + path, + ..., + mode = "u=rwx,go=rx", + recurse = TRUE, + recursive +) { + check_fs_installed() + fs::dir_create( + path, + ..., + mode = mode, + recurse = recurse, + recursive = recursive + ) +} + + +fs_file_create <- function(where) { + check_fs_installed() + fs::file_create(where) +} + +fs_file_delete <- function(path) { + check_fs_installed() + fs::file_delete(path) +} + +fs_file_exists <- function(path) { + check_fs_installed() + fs::file_exists(path) +} + +fs_path_abs <- function(path) { + check_fs_installed() + fs::path_abs(path) +} + +fs_path <- function(..., ext = "") { + check_fs_installed() + fs::path(..., ext = ext) +} + +fs_file_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::file_copy( + path = path, + new_path = new_path, + overwrite + ) +} + +fs_dir_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::dir_copy( + path, + new_path, + overwrite + ) +} diff --git a/R/bootstrap_attachment.R b/R/bootstrap_attachment.R new file mode 100644 index 00000000..08e65c16 --- /dev/null +++ b/R/bootstrap_attachment.R @@ -0,0 +1,23 @@ +# All the fns here check that {attachment} is installed +# before doing anything. +check_attachment_installed <- function() { + rlang::check_installed( + "attachment", + version = "0.2.5", + reason = "to build a Dockerfile." + ) +} + +attachment_create_renv_for_prod <- function( + path = ".", + output = "renv.lock.prod", + dev_pkg = "remotes", + ... +) { + attachment::create_renv_for_prod( + path = path, + output = output, + dev_pkg = dev_pkg, + ... + ) +} diff --git a/R/bootstrap_dockerfiler.R b/R/bootstrap_dockerfiler.R new file mode 100644 index 00000000..5dd79aad --- /dev/null +++ b/R/bootstrap_dockerfiler.R @@ -0,0 +1,68 @@ +# All the fns here check that {dockerfiler} is installed +# before doing anything. +check_dockerfiler_installed <- function() { + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) +} + +dockerfiler_dock_from_renv <- function( + lockfile = "renv.lock", + distro = "focal", + FROM = "rocker/r-base", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL +) { + check_dockerfiler_installed() + dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs + ) +} + +dockerfiler_dock_from_desc <- function( + path = "DESCRIPTION", + FROM = paste0( + "rocker/r-ver:", + R.Version()$major, + ".", + R.Version()$minor + ), + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + update_tar_gz = TRUE, + build_from_source = TRUE, + extra_sysreqs = NULL +) { + check_dockerfiler_installed() + dockerfiler::dock_from_desc( + path = path, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + update_tar_gz = update_tar_gz, + build_from_source = build_from_source, + extra_sysreqs = extra_sysreqs + ) +} + + +dockerfiler_Dockerfile <- function() { + check_dockerfiler_installed() + dockerfiler::Dockerfile +} diff --git a/R/bootstrap_pkgload.R b/R/bootstrap_pkgload.R new file mode 100644 index 00000000..b4db04cc --- /dev/null +++ b/R/bootstrap_pkgload.R @@ -0,0 +1,37 @@ +# All the fns here check that {pkgload} is installed +# before doing anything. +check_pkgload_installed <- function() { + rlang::check_installed( + "pkgload", + reason = "to load the package." + ) +} + +pkgload_load_all <- function( + path = ".", + reset = TRUE, + compile = NA, + attach = TRUE, + export_all = TRUE, + export_imports = export_all, + helpers = TRUE, + attach_testthat = uses_testthat(path), + quiet = NULL, + recompile = FALSE, + warn_conflicts = TRUE +) { + check_roxygen2_installed() + pkgload::load_all( + path = path, + reset = reset, + compile = compile, + attach = attach, + export_all = export_all, + export_imports = export_imports, + helpers = helpers, + attach_testthat = attach_testthat, + quiet = quiet, + recompile = recompile, + warn_conflicts = warn_conflicts + ) +} diff --git a/R/bootstrap_roxygen2.R b/R/bootstrap_roxygen2.R new file mode 100644 index 00000000..e836517c --- /dev/null +++ b/R/bootstrap_roxygen2.R @@ -0,0 +1,23 @@ +# All the fns here check that {roxygen2} is installed +# before doing anything. +check_roxygen2_installed <- function() { + rlang::check_installed( + "roxygen2", + reason = "to document the package." + ) +} + +roxygen2_roxygenise <- function( + package.dir = ".", + roclets = NULL, + load_code = NULL, + clean = FALSE +) { + check_roxygen2_installed() + roxygen2::roxygenise( + package.dir = package.dir, + roclets = roclets, + load_code = load_code, + clean = clean + ) +} diff --git a/R/bootstrap_usethis.R b/R/bootstrap_usethis.R new file mode 100644 index 00000000..a153d78b --- /dev/null +++ b/R/bootstrap_usethis.R @@ -0,0 +1,116 @@ + +# All the fns here check that {usethis} is installed +# before doing anything. +check_usethis_installed <- function(reason = "for project and file manipulation.") { + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = reason + ) +} + +usethis_use_build_ignore <- function( + files, + escape = TRUE +) { + check_usethis_installed( + reason = "to ignore files in the build." + ) + usethis::use_build_ignore( + files, + escape + ) +} +usethis_use_package <- function( + package, + type = "Imports", + min_version = NULL +) { + check_usethis_installed( + reason = "to add dependencies to DESCRIPTION." + ) + usethis::use_package( + package, + type, + min_version + ) +} + +usethis_create_project <- function( + path, + rstudio = rstudioapi::isAvailable(), + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to create a project." + ) + usethis::create_project( + path, + rstudio, + open + ) +} +usethis_use_latest_dependencies <- function( + overwrite = FALSE, + source = c("local", "CRAN") +) { + check_usethis_installed( + reason = "to set dependency version." + ) + usethis::use_latest_dependencies( + overwrite, + source + ) +} + +usethis_proj_set <- function( + path = ".", + force = FALSE +) { + check_usethis_installed( + reason = "to set project." + ) + usethis::proj_set( + path, + force + ) +} +usethis_use_testthat <- function( + edition = NULL, + parallel = FALSE +) { + check_usethis_installed( + reason = "to add {testthat} infrastructure." + ) + usethis::use_testthat( + edition, + parallel + ) +} +usethis_use_test <- function( + name = NULL, + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to add tests." + ) + usethis::use_test( + name, + open + ) +} + +usethis_use_spell_check <- function( + vignettes = TRUE, + lang = "en-US", + error = FALSE +) { + check_usethis_installed( + reason = "to add spellcheck." + ) + usethis::use_spell_check( + vignettes, + lang, + error + ) +} diff --git a/R/fs_boostrap.R b/R/fs_boostrap.R deleted file mode 100644 index 55ab5b83..00000000 --- a/R/fs_boostrap.R +++ /dev/null @@ -1,82 +0,0 @@ -# All the fns here check that {fs} is installed -# before doing anything. -check_fs_installed <- function() { - rlang::check_installed( - "fs", - reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." - ) -} - -fs_dir_exists <- function(path) { - check_fs_installed() - fs::dir_exists(path) -} - -fs_dir_create <- function( - path, - ..., - mode = "u=rwx,go=rx", - recurse = TRUE, - recursive -) { - check_fs_installed() - fs::dir_create( - path, - ..., - mode = mode, - recurse = recurse, - recursive = recursive - ) -} - - -fs_file_create <- function(where) { - check_fs_installed() - fs::file_create(where) -} - -fs_file_delete <- function(path) { - check_fs_installed() - fs::file_delete(path) -} - -fs_file_exists <- function(path) { - check_fs_installed() - fs::file_exists(path) -} - -fs_path_abs <- function(path) { - check_fs_installed() - fs::path_abs(path) -} - -fs_path <- function(..., ext = "") { - check_fs_installed() - fs::path(..., ext = ext) -} - -fs_file_copy <- function( - path, - new_path, - overwrite = FALSE -) { - check_fs_installed() - fs::file_copy( - path = path, - new_path = new_path, - overwrite - ) -} - -fs_dir_copy <- function( - path, - new_path, - overwrite = FALSE -) { - check_fs_installed() - fs::dir_copy( - path, - new_path, - overwrite - ) -} diff --git a/R/modules_fn.R b/R/modules_fn.R index 557e1089..a393b0ed 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -256,18 +256,12 @@ use_module_test <- function( } # We need both testthat, usethis & fs - rlang::check_installed( - "usethis", - "to build the test structure." - ) + rlang::check_installed( "testthat", "to build the test structure." ) - rlang::check_installed( - "fs" - ) - + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) diff --git a/R/reload.R b/R/reload.R index b124da96..3f16d7f4 100644 --- a/R/reload.R +++ b/R/reload.R @@ -106,13 +106,12 @@ document_and_reload <- function( check_name_consistency(pkg) rlang::check_installed("pkgload") - rlang::check_installed("roxygen2") if (rstudioapi::isAvailable() & rstudioapi::hasFun("documentSaveAll")) { rstudioapi::documentSaveAll() } roxed <- try({ - roxygen2::roxygenise( + roxygen2_roxygenise( package.dir = pkg, roclets = roclets, load_code = load_code, @@ -127,7 +126,7 @@ document_and_reload <- function( return(invisible(FALSE)) } loaded <- try({ - pkgload::load_all( + pkgload_load_all( pkg, export_all = export_all, helpers = helpers, diff --git a/R/use_utils.R b/R/use_utils.R index ef2c1f46..ebe09768 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -135,10 +135,6 @@ use_utils <- function( folder_name, pkg = get_golem_wd() ) { - rlang::check_installed( - "fs", - reason = "for file manipulation." - ) old <- setwd( fs_path_abs(pkg) diff --git a/R/usethis_bootstrap.R b/R/usethis_bootstrap.R deleted file mode 100644 index 1e513da5..00000000 --- a/R/usethis_bootstrap.R +++ /dev/null @@ -1,116 +0,0 @@ - -# All the fns here check that {usethis} is installed -# before doing anything. -check_usethis_installed <- function(reason = "for project and file manipulation.") { - rlang::check_installed( - "usethis", - version = "1.6.0", - reason = reason - ) -} - -usethis_use_build_ignore <- function( - files, - escape = TRUE -) { - check_usethis_installed( - reason = "to ignore files in the build." - ) - usethis::use_build_ignore( - files, - escape - ) -} -usethis_use_package <- function( - package, - type = "Imports", - min_version = NULL -) { - check_usethis_installed( - reason = "to add dependencies to DESCRIPTION." - ) - usethis::use_package( - package, - type, - min_version - ) -} - -usethis_create_project <- function( - path, - rstudio = rstudioapi::isAvailable(), - open = rlang::is_interactive() -) { - check_usethis_installed( - reason = "to create a project." - ) - usethis::create_project( - path, - rstudio, - open - ) -} -usethis_use_latest_dependencies <- function( - overwrite = FALSE, - source = c("local", "CRAN") -) { - check_usethis_installed( - reason = "to set dependency version." - ) - usethis::use_latest_dependencies( - overwrite, - source - ) -} - -usethis_proj_set <- function( - path = ".", - force = FALSE -) { - check_usethis_installed( - reason = "to set project." - ) - usethis::proj_set( - path, - force - ) -} -usethis_use_testthat <- function( - edition = NULL, - parallel = FALSE -) { - check_usethis_installed( - reason = "to add {testthat} infrastructure." - ) - usethis::use_testthat( - edition, - parallel - ) -} -usethis_use_test <- function( - name = NULL, - open = rlang::is_interactive() -) { - check_usethis_installed( - reason = "to add tests." - ) - usethis::use_test( - name, - open - ) -} - -usethis_use_spell_check <- function( - vignettes = TRUE, - lang = "en-US", - error = FALSE -) { - check_usethis_installed( - reason = "to add spellcheck." - ) - usethis::use_spell_check( - vignettes, - lang, - error - ) -} From 0313b5919a1290545e2cc2d9106a9aa7022edd87 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 14 Oct 2022 19:56:19 +0200 Subject: [PATCH 080/190] feat: Make cat functions conditional to an options (#934) --- R/add_files.R | 2 +- R/bootstrap_pkgload.R | 3 + R/create_golem.R | 4 +- R/disable_autoload.R | 2 +- R/reload.R | 4 +- R/set_golem_options.R | 4 +- R/utils.R | 100 +++++++++++++++++------ tests/testthat/helper-config.R | 4 +- tests/testthat/test-add_deploy_helpers.R | 62 ++++++++------ tests/testthat/test-add_modules.R | 5 +- tests/testthat/test-desc.R | 23 +++--- tests/testthat/test-extra_sysreqs.R | 6 +- tests/testthat/test-make_dev.R | 5 +- tests/testthat/test-test_helpers.R | 14 +++- tests/testthat/test-zzzzzzzzzz.R | 4 +- 15 files changed, 163 insertions(+), 79 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 08a420ea..1b964d21 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -512,7 +512,7 @@ add_sass_file <- function( name = name ) - cli_alert_info( + cat_green_tick( "After running the compilation, your CSS file will be automatically link in `golem_add_external_resources()`." ) } else { diff --git a/R/bootstrap_pkgload.R b/R/bootstrap_pkgload.R index b4db04cc..3c90630a 100644 --- a/R/bootstrap_pkgload.R +++ b/R/bootstrap_pkgload.R @@ -7,6 +7,9 @@ check_pkgload_installed <- function() { ) } +uses_testthat <- getFromNamespace( "uses_testthat", "pkgload" ) + + pkgload_load_all <- function( path = ".", reset = TRUE, diff --git a/R/create_golem.R b/R/create_golem.R index 12bc8db6..fb77355f 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -113,7 +113,9 @@ create_golem <- function( path = path_to_golem, open = FALSE ) - here::set_here(path_to_golem) + if (!file.exists(".here")){ + here::set_here(path_to_golem) + } cat_green_tick("Created package directory") } diff --git a/R/disable_autoload.R b/R/disable_autoload.R index 64215656..a984b02c 100644 --- a/R/disable_autoload.R +++ b/R/disable_autoload.R @@ -20,7 +20,7 @@ disable_autoload <- function(pkg = get_golem_wd()) { "_disable_autoload.R already exists, skipping its creation." ) } else { - cli::cat_rule("Creating _disable_autoload.R") + cat_rule("Creating _disable_autoload.R") write( "# Disabling shiny autoload\n\n# See ?shiny::loadSupport for more information", fls diff --git a/R/reload.R b/R/reload.R index 3f16d7f4..2bb9e51f 100644 --- a/R/reload.R +++ b/R/reload.R @@ -119,7 +119,7 @@ document_and_reload <- function( ) }) if (attempt::is_try_error(roxed)) { - cli::cat_rule( + cat_rule( "Error documenting your package" ) dialog_if_has("Alert", "Error documenting your package") @@ -136,7 +136,7 @@ document_and_reload <- function( }) if (attempt::is_try_error(loaded)) { - cli::cat_rule( + cat_rule( "Error loading your package" ) dialog_if_has("Alert", "Error loading your package") diff --git a/R/set_golem_options.R b/R/set_golem_options.R index 28ddd90c..2a965ae9 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -48,7 +48,7 @@ set_golem_options <- function( # golem_install_dev_pkg() function if (talkative) { - cli::cat_rule( + cat_rule( "Setting {golem} options in `golem-config.yml`" ) } @@ -90,7 +90,7 @@ set_golem_options <- function( # This part is for {usethis} and {here} if (talkative) { - cli::cat_rule( + cat_rule( "Setting {usethis} project as `golem_wd`" ) } diff --git a/R/utils.R b/R/utils.R index 2246d5ea..f6498796 100644 --- a/R/utils.R +++ b/R/utils.R @@ -113,29 +113,55 @@ remove_comments <- function(file) { #' @importFrom cli cat_bullet cat_green_tick <- function(...) { - cat_bullet( - ..., - bullet = "tick", - bullet_col = "green" - ) + do_if_unquiet({ + cat_bullet( + ..., + bullet = "tick", + bullet_col = "green" + ) + }) } #' @importFrom cli cat_bullet cat_red_bullet <- function(...) { - cat_bullet( - ..., - bullet = "bullet", - bullet_col = "red" - ) + do_if_unquiet({ + cli::cat_bullet( + ..., + bullet = "bullet", + bullet_col = "red" + ) + }) } #' @importFrom cli cat_bullet cat_info <- function(...) { - cat_bullet( - ..., - bullet = "arrow_right", - bullet_col = "grey" - ) + do_if_unquiet({ + cli::cat_bullet( + ..., + bullet = "arrow_right", + bullet_col = "grey" + ) + }) +} + +cat_rule <- function( + ... +) { + do_if_unquiet({ + cli::cat_rule( + ... + ) + }) +} + +cat_line <- function( + ... +) { + do_if_unquiet({ + cli::cat_line( + ... + ) + }) } cat_exists <- function(where) { @@ -160,8 +186,10 @@ cat_dir_necessary <- function() { } cat_start_download <- function() { - cat_line("") - cat_rule("Initiating file download") + do_if_unquiet({ + cat_line("") + cat_rule("Initiating file download") + }) } cat_downloaded <- function( @@ -178,8 +206,10 @@ cat_downloaded <- function( } cat_start_copy <- function() { - cat_line("") - cat_rule("Copying file") + do_if_unquiet({ + cat_line("") + cat_rule("Copying file") + }) } cat_copied <- function( @@ -314,13 +344,15 @@ after_creation_message_html_template <- function( dir, name ) { - cat_line("") - cat_rule("To use this html file as a template, add the following code in your UI:") - cat_line(darkgrey("htmlTemplate(")) - cat_line(darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) - cat_line(darkgrey(" body = tagList()")) - cat_line(darkgrey(" # add here other template arguments")) - cat_line(darkgrey(")")) + do_if_unquiet({ + cat_line("") + cat_rule("To use this html file as a template, add the following code in your UI:") + cat_line(darkgrey("htmlTemplate(")) + cat_line(darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) + cat_line(darkgrey(" body = tagList()")) + cat_line(darkgrey(" # add here other template arguments")) + cat_line(darkgrey(")")) + }) } file_created_dance <- function( @@ -447,3 +479,19 @@ check_name_length <- function(name) { ) ) } + +do_if_unquiet <- function( + expr +){ + if( + ! getOption( + "golem.quiet", + getOption( + "usethis.quiet", + default = FALSE + ) + ) + ){ + force(expr) + } +} \ No newline at end of file diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 52f85e55..f50b9073 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -81,7 +81,9 @@ rand_name <- function() { withr::with_dir(pkg, { # Some weird things with {here} unloadNamespace("here") - here::set_here(".") + if (!file.exists(".here")){ + here::set_here(path_to_golem) + } set_golem_options() usethis::proj_set(pkg) orig_test <- set_golem_wd( diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index b798603f..0bbe2181 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -12,9 +12,14 @@ test_that("add_dockerfiles", { burn_after_reading( "Dockerfile", { - output <- testthat::capture_output( - fun(pkg = pkg, sysreqs = FALSE, open = FALSE) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output( + fun(pkg = pkg, sysreqs = FALSE, open = FALSE) + ) + } ) + expect_exists("Dockerfile") test <- stringr::str_detect( output, @@ -37,27 +42,29 @@ test_that("add_dockerfiles repos variation", { burn_after_reading( c("Dockerfile1", "Dockerfile2"), { - output1 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = "https://cran.rstudio.com/", - output = "Dockerfile1" + withr::with_options( + c("golem.quiet" = FALSE),{ + output1 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = "https://cran.rstudio.com/", + output = "Dockerfile1" + ) ) - ) - output2 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = c("https://cran.rstudio.com/"), - output = "Dockerfile2" + output2 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = c("https://cran.rstudio.com/"), + output = "Dockerfile2" + ) ) - ) - expect_exists("Dockerfile1") - expect_exists("Dockerfile2") - + expect_exists("Dockerfile1") + expect_exists("Dockerfile2") + }) test1 <- stringr::str_detect( output1, @@ -91,12 +98,15 @@ test_that("add_rstudio_files", { burn_after_reading( "app.R", { - output <- testthat::capture_output( - fun( - pkg = pkg, - open = FALSE - ) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output( + fun( + pkg = pkg, + open = FALSE + ) ) + }) expect_exists("app.R") test <- stringr::str_detect( output, diff --git a/tests/testthat/test-add_modules.R b/tests/testthat/test-add_modules.R index a1584aef..af70b65b 100644 --- a/tests/testthat/test-add_modules.R +++ b/tests/testthat/test-add_modules.R @@ -15,7 +15,10 @@ test_that("add_module", { lapply(tools::file_ext(script), function(x) testthat::expect_equal(x, "R")) ## Test message of function remove_file("R/mod_output.R") - output <- testthat::capture_output(add_module("output", open = FALSE)) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output(add_module("output", open = FALSE)) + }) expect_true( stringr::str_detect(output, "File created at R/mod_output.R") ) diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 7af2b91f..86ec958b 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -1,17 +1,20 @@ test_that("desc works", { with_dir(pkg, { - output <- capture_output( - fill_desc( - fakename, - "newtitle", - "Newdescription.", - "firstname", - "lastname", - "name@test.com", - "http://repo_url.com" + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- capture_output( + fill_desc( + fakename, + "newtitle", + "Newdescription.", + "firstname", + "lastname", + "name@test.com", + "http://repo_url.com" + ) ) - ) + }) add_desc <- c( fakename, "newtitle", diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index d0e47254..286f0999 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -13,7 +13,9 @@ test_that("test extra sysreqs", { burn_after_reading( "Dockerfile", { - output <- testthat::capture_output( + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output( fun( pkg = pkg, sysreqs = FALSE, @@ -22,7 +24,7 @@ test_that("test extra sysreqs", { output = "Dockerfile" ) ) - + }) expect_exists("Dockerfile") test <- stringr::str_detect( output, diff --git a/tests/testthat/test-make_dev.R b/tests/testthat/test-make_dev.R index bbf46547..5f736b7a 100644 --- a/tests/testthat/test-make_dev.R +++ b/tests/testthat/test-make_dev.R @@ -60,7 +60,10 @@ test_that("test print_dev", { test_that("test browser_button", { - output <- capture_output_lines(browser_button()) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- capture_output_lines(browser_button()) + }) expect_true( grepl('actionButton\\("browser", "browser"\\)', output[2]) ) diff --git a/tests/testthat/test-test_helpers.R b/tests/testthat/test-test_helpers.R index c1e4ccad..82aa04c2 100644 --- a/tests/testthat/test-test_helpers.R +++ b/tests/testthat/test-test_helpers.R @@ -1,13 +1,19 @@ test_that("test expect_shinytag", { with_dir(pkg, { - expect_equal(capture_output(expect_shinytag(favicon("jean"))), "") - expect_error(expect_shinytag("pierre")) + withr::with_options( + c("golem.quiet" = FALSE),{ + expect_equal(capture_output(expect_shinytag(favicon("jean"))), "") + expect_error(expect_shinytag("pierre")) + }) }) }) test_that("test expect_shinytaglist", { with_dir(pkg, { - expect_equal(capture_output(expect_shinytaglist(shiny::tagList())), "") - expect_error(expect_shinytaglist("test")) + withr::with_options( + c("golem.quiet" = FALSE),{ + expect_equal(capture_output(expect_shinytaglist(shiny::tagList())), "") + expect_error(expect_shinytaglist("test")) + }) }) }) diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 82aa1a70..b5bf278c 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -4,7 +4,9 @@ try({ usethis::proj_set(orig_test) } if (exists("pkg")) { - unlink(pkg, TRUE, TRUE) + try({ + unlink(pkg, TRUE, TRUE) + }) } options("usethis.quiet" = old_usethis.quiet) From 9b072a51480ed9b7f79aac06c96b219917dc1b10 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 14 Oct 2022 19:57:52 +0200 Subject: [PATCH 081/190] doc: NEWS update & version bump --- DESCRIPTION | 2 +- NEWS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index e44fbbda..14b49f15 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.4.9001 +Version: 0.3.4.9002 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index d39b15dc..91c61c1e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,8 @@ + `{golem}` now depends on `{rlang}` version >= 1.0.0 ++ Functions that print to the console are now quiet if `options("golem.quiet" = TRUE)`, #793 + ## Bug fix + The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) From 6bf0f55fd8d37121f9655d6e916caea02316330b Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Thu, 27 Oct 2022 10:31:59 +0200 Subject: [PATCH 082/190] chore: update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 30 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++ .github/ISSUE_TEMPLATE/todo.md | 34 +++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/todo.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..2f36bb85 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: ColinFay + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. +1. ... +2. ... + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**session info** + +please insert here the output of `devtools::session_info()` + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..524940e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FR]" +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/todo.md b/.github/ISSUE_TEMPLATE/todo.md new file mode 100644 index 00000000..d6bcae43 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/todo.md @@ -0,0 +1,34 @@ +--- +name: todo +about: Internal issue template for golem developers +title: '' +labels: '' +assignees: '' + +--- + +## User - Validation + ++ [ ] First thing to do to validate this as an external user ++ [ ] Second thing to do to validate this as an external user + +## Dev - Tech + +_A paragraph that details what this functionality does and how it will be implemented._ + ++ [ ] A sentence that sums up a feature + + [ ] Implementation number 1 + + [ ] Implementation number 2 + + [ ] Technical validation ++ [ ] A sentence that sums up a feature + + [ ] Implementation number 1 + + [ ] Implementation number 2 + + [ ] Technical validation + +## Estimated time + +01h00. + ++ [ ] Add the estimate as a comment with `/estimate` ++ [ ] When I close this issue, `devtools::check()` is 0 0 0 ++ [ ] When I close the issue, I've written the time spent solving this issue, using `/spent` From 9d752bca6e350f45cc9f3bb4ea04659a94fb51ec Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Thu, 27 Oct 2022 10:40:32 +0200 Subject: [PATCH 083/190] test: skip suggests package if not installed (#937) --- DESCRIPTION | 2 +- NEWS.md | 37 ++++---------------------------- R/add_dockerfiles.R | 18 ---------------- R/modules_fn.R | 13 ++--------- cran-comments.md | 15 +++++++++++++ man/dockerfiles.Rd | 18 ---------------- tests/testthat/test-zreload.R | 12 +++++------ tests/testthat/test-zzzzzzzzzz.R | 8 +------ 8 files changed, 29 insertions(+), 94 deletions(-) create mode 100644 cran-comments.md diff --git a/DESCRIPTION b/DESCRIPTION index f8ec31f7..40e88364 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.4 +Version: 0.3.5 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index feb3d628..d653d267 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,41 +1,12 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. -# golem 0.3.3.9000+ +# golem 0.3.5 -## Soft deprecated - - -## Hard deprecated - - -## New functions - -+ Add `add_partial_html_template()` to create a partial html template, with "just" a div and a `{{ }}` (@nathansquan #858). - - -## New features / user visible changes - -+ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}` +Update in the tests for CRAN (commented a test that made new version of testthat fail). -+ Soft dependency check is now done via `rlang::check_installed()` (#835) +# golem 0.3.4 -+ `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. - -+ `{golem}` now depends on `{rlang}` version >= 1.0.0 - -## Bug fix - -+ The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) - -+ The Deprecation message for `use_recommended_deps` no longer suggest to user `use_recommended_deps` (#900) - -+ The setting of the config file has been unified so that we are sure to keep the !expr in `golem_set_wd()`, and the codebase has been simplified for this (#709). - -+ The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) - -+ The typo in `install.packages()` in 02_dev.R has been corrected (@asiripanich) - -## Internal changes +Update in the tests for CRAN (skip not installed + examples). # golem 0.3.3 diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index c3ae80aa..f8cce15d 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -73,14 +73,6 @@ talk_once <- function(.f, msg = "") { #' output_dir = "deploy" #' ) #' } -#' # Crete a 'deploy' folder containing everything needed to deploy -#' # the golem using docker based on {renv} -#' if (interactive()) { -#' add_dockerfile_with_renv( -#' # lockfile = "renv.lock", # uncomment to use existing renv.lock file -#' output_dir = "deploy" -#' ) -#' } #' # Add a Dockerfile for ShinyProxy #' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_shinyproxy() @@ -95,16 +87,6 @@ talk_once <- function(.f, msg = "") { #' ) #' } #' -#' -#' # Crete a 'deploy' folder containing everything needed to deploy -#' # the golem with ShinyProxy using docker based on {renv} -#' if (interactive()) { -#' add_dockerfile_with_renv( -#' # lockfile = "renv.lock",# uncomment to use existing renv.lock file -#' output_dir = "deploy" -#' ) -#' } -#' #' # Add a Dockerfile for Heroku #' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_heroku() diff --git a/R/modules_fn.R b/R/modules_fn.R index c55d0cac..9204494e 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -256,17 +256,8 @@ use_module_test <- function( } # We need both testthat, usethis & fs - rlang::check_installed( - "usethis", - "to build the test structure." - ) - rlang::check_installed( - "testthat", - "to build the test structure." - ) - rlang::check_installed( - "fs" - ) + check_is_installed("testthat") + check_is_installed("fs") old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 00000000..35b82c24 --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,15 @@ +## R CMD check results + +0 errors | 0 warnings | 0 note + +* This is a submission that fixes the issue from the CRAN check page. + +=> The new version of testthat fails when unloaded during the tests. This new version of golem removes the test that unloads testthat. + +## revdepcheck results + +We checked 31 reverse dependencies (17 from CRAN + 14 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package. + + * We saw 0 new problems + * We failed to check 0 packages + diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 35fdab0c..a8a7738e 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -178,14 +178,6 @@ if (interactive() & requireNamespace("dockerfiler")) { output_dir = "deploy" ) } -# Crete a 'deploy' folder containing everything needed to deploy -# the golem using docker based on {renv} -if (interactive()) { - add_dockerfile_with_renv( - # lockfile = "renv.lock", # uncomment to use existing renv.lock file - output_dir = "deploy" - ) -} # Add a Dockerfile for ShinyProxy if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_shinyproxy() @@ -200,16 +192,6 @@ if (interactive() & requireNamespace("dockerfiler")) { ) } - -# Crete a 'deploy' folder containing everything needed to deploy -# the golem with ShinyProxy using docker based on {renv} -if (interactive()) { - add_dockerfile_with_renv( - # lockfile = "renv.lock",# uncomment to use existing renv.lock file - output_dir = "deploy" - ) -} - # Add a Dockerfile for Heroku if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_heroku() diff --git a/tests/testthat/test-zreload.R b/tests/testthat/test-zreload.R index b97b1f22..5f0abd54 100644 --- a/tests/testthat/test-zreload.R +++ b/tests/testthat/test-zreload.R @@ -20,9 +20,9 @@ test_that("test document_and_reload", { }) }) -test_that("test detach_all_attached", { - with_dir(pkg_reload, { - test <- detach_all_attached() - testthat::expect_true(test) - }) -}) +# test_that("test detach_all_attached", { +# with_dir(pkg_reload, { +# test <- detach_all_attached() +# testthat::expect_true(test) +# }) +# }) diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 82aa1a70..63480161 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -1,11 +1,5 @@ # # For setting back old usethis settings try({ - if (exists("orig_test")) { - usethis::proj_set(orig_test) - } - if (exists("pkg")) { - unlink(pkg, TRUE, TRUE) - } - + unlink(pkg, TRUE, TRUE) options("usethis.quiet" = old_usethis.quiet) }) From 6e5d92a4c45ffb9bf8b6ad3a06ae491b02549477 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 27 Oct 2022 10:43:30 +0200 Subject: [PATCH 084/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 40e88364..c65fbc04 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.5 +Version: 0.3.5.9000 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index d653d267..3f134dd0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. +# 0.3.5.9000+ (dev version) + # golem 0.3.5 Update in the tests for CRAN (commented a test that made new version of testthat fail). From 8eb0719a6ff4c511a1916783ca2b4f573c70d6b7 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 12 Oct 2022 21:27:59 +0200 Subject: [PATCH 085/190] chore: version bump We needed to release a version for golem to stay on CRAN but a real release was not ready, hence a small 0.3.4 --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 3f134dd0..6bb804f7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,10 @@ Update in the tests for CRAN (commented a test that made new version of testthat Update in the tests for CRAN (skip not installed + examples). +# golem 0.3.4. + +This version has a small internal change in the tests, so that it can stay on CRAN. + # golem 0.3.3 ## New functions From 1238ce95276a2b10c1bea6c921335bf1ac5ce21f Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Wed, 12 Oct 2022 21:23:55 +0200 Subject: [PATCH 086/190] chore: merging changes from CRAN upload (#928) --- R/add_dockerfiles.R | 2 +- man/dockerfiles.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index f8cce15d..c19d4bcb 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -67,7 +67,7 @@ talk_once <- function(.f, msg = "") { #' } #' # Crete a 'deploy' folder containing everything needed to deploy #' # the golem using docker based on {renv} -#' if (interactive() & requireNamespace("dockerfiler")) { +#' if (interactive()) { #' add_dockerfile_with_renv( #' # lockfile = "renv.lock", # uncomment to use existing renv.lock file #' output_dir = "deploy" diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index a8a7738e..97a8a023 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -172,7 +172,7 @@ if (interactive() & requireNamespace("dockerfiler")) { } # Crete a 'deploy' folder containing everything needed to deploy # the golem using docker based on {renv} -if (interactive() & requireNamespace("dockerfiler")) { +if (interactive()) { add_dockerfile_with_renv( # lockfile = "renv.lock", # uncomment to use existing renv.lock file output_dir = "deploy" From 4f0eb33cfe492cacbfb30041b6b929f03658bc33 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Wed, 12 Oct 2022 22:11:39 +0200 Subject: [PATCH 087/190] feat: removed {usethis} hard dep (#929) --- DESCRIPTION | 6 +- NAMESPACE | 7 --- R/add_dockerfiles.R | 22 ++------ R/add_dockerfiles_renv.R | 2 +- R/add_r_files.R | 2 +- R/add_rstudio_files.R | 9 +-- R/create_golem.R | 17 ++++-- R/modules_fn.R | 2 +- R/set_golem_options.R | 3 +- R/use_recommended.R | 15 +---- R/use_utils.R | 5 +- R/usethis_bootstrap.R | 116 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 149 insertions(+), 57 deletions(-) create mode 100644 R/usethis_bootstrap.R diff --git a/DESCRIPTION b/DESCRIPTION index c65fbc04..4463bffe 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -55,12 +55,10 @@ Imports: crayon, desc, here, - fs, htmltools, rlang (>= 1.0.0), rstudioapi, shiny (>= 1.5.0), - usethis (>= 1.6.0), utils, yaml Suggests: @@ -83,7 +81,9 @@ Suggests: tools, withr, attachment (>= 0.2.5), - renv + renv, + usethis (>= 1.6.0), + fs VignetteBuilder: knitr Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index 1f59daf5..7cdfebe7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -111,13 +111,6 @@ importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) importFrom(shiny,includeScript) importFrom(shiny,tags) -importFrom(usethis,create_project) -importFrom(usethis,proj_set) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_latest_dependencies) -importFrom(usethis,use_package) -importFrom(usethis,use_spell_check) -importFrom(usethis,use_testthat) importFrom(utils,capture.output) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index c19d4bcb..490c10a1 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -9,17 +9,6 @@ talk_once <- function(.f, msg = "") { } } -talk_once <- function(.f, msg = "") { - talk <- TRUE - function(...) { - if (talk) { - talk <<- FALSE - cat_red_bullet(msg) - } - .f(...) - } -} - #' Create a Dockerfile for your App #' #' Build a container containing your Shiny App. `add_dockerfile()` and `add_dockerfile_with_renv()` and `add_dockerfile_with_renv()` creates @@ -55,7 +44,6 @@ talk_once <- function(.f, msg = "") { #' @export #' @rdname dockerfiles #' -#' @importFrom usethis use_build_ignore #' @importFrom desc desc_get_deps #' @importFrom rstudioapi navigateToFile isAvailable hasFun #' @@ -162,7 +150,9 @@ add_dockerfile_ <- talk_once( where <- fs_path(pkg, output) - usethis::use_build_ignore(basename(where)) + usethis_use_build_ignore( + basename(where) + ) dock <- dockerfiler::dock_from_desc( path = path, @@ -271,7 +261,7 @@ add_dockerfile_shinyproxy_ <- talk_once( ) where <- fs_path(pkg, output) - usethis::use_build_ignore(output) + usethis_use_build_ignore(output) dock <- dockerfiler::dock_from_desc( path = path, @@ -374,7 +364,7 @@ add_dockerfile_heroku_ <- talk_once( ) where <- fs_path(pkg, output) - usethis::use_build_ignore(output) + usethis_use_build_ignore(output) dock <- dockerfiler::dock_from_desc( path = path, @@ -437,7 +427,7 @@ add_dockerfile_heroku_ <- talk_once( try(file.edit(output)) } } - usethis::use_build_ignore(files = output) + usethis_use_build_ignore(files = output) return(invisible(dock)) }, " diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index b9c59503..93f5d3fb 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -37,7 +37,7 @@ add_dockerfile_with_renv_ <- function( # add output_dir in Rbuildignore if the output is inside the golem if (normalizePath(dirname(output_dir)) == normalizePath(source_folder)) { - usethis::use_build_ignore(output_dir) + usethis_use_build_ignore(output_dir) } if (is.null(lockfile)) { diff --git a/R/add_r_files.R b/R/add_r_files.R index 1e6e66fa..a4bcb24f 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -68,7 +68,7 @@ add_r_files <- function( } if (with_test) { - usethis::use_test( + usethis_use_test( basename( file_path_sans_ext( where diff --git a/R/add_rstudio_files.R b/R/add_rstudio_files.R index f756e1e1..44955959 100644 --- a/R/add_rstudio_files.R +++ b/R/add_rstudio_files.R @@ -1,6 +1,5 @@ #' @importFrom utils capture.output #' @importFrom cli cat_bullet -#' @importFrom usethis use_build_ignore use_package add_rstudio_files <- function( pkg, open, @@ -18,8 +17,6 @@ add_rstudio_files <- function( reason = "to deploy on RStudio products." ) - rlang::check_installed("usethis") - disable_autoload( pkg = pkg ) @@ -31,8 +28,8 @@ add_rstudio_files <- function( write(..., here, append = TRUE) } - use_build_ignore(basename(where)) - use_build_ignore("rsconnect") + usethis_use_build_ignore(basename(where)) + usethis_use_build_ignore("rsconnect") write_there("# Launch the ShinyApp (Do not remove this comment)") write_there("# To deploy, run: rsconnect::deployApp()") write_there("# Or use the blue button on top of this file") @@ -47,7 +44,7 @@ add_rstudio_files <- function( ) # We add {pkgload} as a dep because it's required to deploy on Connect & stuff - usethis::use_package("pkgload") + usethis_use_package("pkgload") cat_created(where) cat_line("To deploy, run:") diff --git a/R/create_golem.R b/R/create_golem.R index 5f4b5651..12bc8db6 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -57,7 +57,6 @@ replace_package_name <- function( #' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable openProject hasFun -#' @importFrom usethis use_latest_dependencies create_project #' @importFrom yaml write_yaml #' #' @export @@ -81,7 +80,15 @@ create_golem <- function( if (check_name) { cat_rule("Checking package name") - getFromNamespace("check_package_name", "usethis")(package_name) + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = "to check the package name." + ) + getFromNamespace( + "check_package_name", + "usethis" + )(package_name) cat_green_tick("Valid package name") } @@ -102,9 +109,9 @@ create_golem <- function( } } else { cat_rule("Creating dir") - usethis::create_project( + usethis_create_project( path = path_to_golem, - open = FALSE, + open = FALSE ) here::set_here(path_to_golem) cat_green_tick("Created package directory") @@ -188,7 +195,7 @@ create_golem <- function( old <- setwd(path_to_golem) - use_latest_dependencies() + usethis_use_latest_dependencies() # No .Rprofile for now # cat_rule("Appending .Rprofile") diff --git a/R/modules_fn.R b/R/modules_fn.R index 9204494e..c871b6cf 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -266,7 +266,7 @@ use_module_test <- function( if (!fs_dir_exists( fs_path(pkg, "tests", "testthat") )) { - usethis::use_testthat() + usethis_use_testthat() } path <- fs_path( diff --git a/R/set_golem_options.R b/R/set_golem_options.R index bf9d59e8..28ddd90c 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -32,7 +32,6 @@ #' #' @export #' @importFrom attempt stop_if_not -#' @importFrom usethis proj_set #' #' @return Used for side-effects for the setters, and values from the #' config in the getters. @@ -96,5 +95,5 @@ set_golem_options <- function( ) } - proj_set(golem_wd) + usethis_proj_set(golem_wd) } diff --git a/R/use_recommended.R b/R/use_recommended.R index c72d85a7..1a348a9c 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -10,7 +10,6 @@ #' @param recommended A vector of recommended packages. #' @param spellcheck Whether or not to use a spellcheck test. #' -#' @importFrom usethis use_testthat use_package #' @rdname use_recommended #' #' @export @@ -29,7 +28,7 @@ use_recommended_deps <- function( on.exit(setwd(old)) for (i in sort(recommended)) { - try(use_package(i)) + try(usethis_use_package(i)) } cat_green_tick("Dependencies added") @@ -38,7 +37,6 @@ use_recommended_deps <- function( #' @rdname use_recommended #' @export -#' @importFrom usethis use_testthat use_package use_spell_check #' @importFrom utils capture.output #' @importFrom attempt without_warning stop_if use_recommended_tests <- function( @@ -50,17 +48,12 @@ use_recommended_tests <- function( ) { old <- setwd(fs_path_abs(pkg)) - rlang::check_installed( - "fs", - reason = "for file & directory manipulation." - ) - on.exit(setwd(old)) if (!fs_dir_exists( fs_path(fs_path_abs(pkg), "tests") )) { - without_warning(use_testthat)() + without_warning(usethis_use_testthat)() } if (!requireNamespace("processx")) { stop("Please install the {processx} package to add the recommended tests.") @@ -79,13 +72,11 @@ use_recommended_tests <- function( ) if (spellcheck) { - use_spell_check( + usethis_use_spell_check( vignettes = vignettes, lang = lang, error = error ) } - - cat_green_tick("Tests added") } diff --git a/R/use_utils.R b/R/use_utils.R index e9de1af3..ef2c1f46 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -12,7 +12,6 @@ #' #' @importFrom cli cat_bullet #' @importFrom utils capture.output -#' @importFrom usethis use_testthat #' #' @return Used for side-effects. use_utils_ui <- function( @@ -30,7 +29,7 @@ use_utils_ui <- function( if (with_test) { if (!isTRUE(fs_dir_exists("tests"))) { - use_testthat() + usethis_use_testthat() } pth <- fs_path( pkg, @@ -82,7 +81,7 @@ use_utils_server <- function( if (with_test) { if (!isTRUE(fs_dir_exists("tests"))) { - use_testthat() + usethis_use_testthat() } pth <- fs_path( pkg, diff --git a/R/usethis_bootstrap.R b/R/usethis_bootstrap.R new file mode 100644 index 00000000..1e513da5 --- /dev/null +++ b/R/usethis_bootstrap.R @@ -0,0 +1,116 @@ + +# All the fns here check that {usethis} is installed +# before doing anything. +check_usethis_installed <- function(reason = "for project and file manipulation.") { + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = reason + ) +} + +usethis_use_build_ignore <- function( + files, + escape = TRUE +) { + check_usethis_installed( + reason = "to ignore files in the build." + ) + usethis::use_build_ignore( + files, + escape + ) +} +usethis_use_package <- function( + package, + type = "Imports", + min_version = NULL +) { + check_usethis_installed( + reason = "to add dependencies to DESCRIPTION." + ) + usethis::use_package( + package, + type, + min_version + ) +} + +usethis_create_project <- function( + path, + rstudio = rstudioapi::isAvailable(), + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to create a project." + ) + usethis::create_project( + path, + rstudio, + open + ) +} +usethis_use_latest_dependencies <- function( + overwrite = FALSE, + source = c("local", "CRAN") +) { + check_usethis_installed( + reason = "to set dependency version." + ) + usethis::use_latest_dependencies( + overwrite, + source + ) +} + +usethis_proj_set <- function( + path = ".", + force = FALSE +) { + check_usethis_installed( + reason = "to set project." + ) + usethis::proj_set( + path, + force + ) +} +usethis_use_testthat <- function( + edition = NULL, + parallel = FALSE +) { + check_usethis_installed( + reason = "to add {testthat} infrastructure." + ) + usethis::use_testthat( + edition, + parallel + ) +} +usethis_use_test <- function( + name = NULL, + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to add tests." + ) + usethis::use_test( + name, + open + ) +} + +usethis_use_spell_check <- function( + vignettes = TRUE, + lang = "en-US", + error = FALSE +) { + check_usethis_installed( + reason = "to add spellcheck." + ) + usethis::use_spell_check( + vignettes, + lang, + error + ) +} From 9b68398dba46bc2ac6a4306188670ddec5b75e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rochette?= Date: Thu, 13 Oct 2022 08:43:11 +0200 Subject: [PATCH 088/190] fix: unit test requires dependencies in suggests (#919) --- tests/testthat/test-extra_sysreqs.R | 2 ++ tests/testthat/test-pkg_tools.R | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index 6530b095..d0e47254 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -1,3 +1,5 @@ +skip_if_not_installed("dockerfiler", minimum_version = "0.2.0") + test_that("test extra sysreqs", { skip_if_not_installed("renv") skip_if_not_installed("dockerfiler", "0.2.0") diff --git a/tests/testthat/test-pkg_tools.R b/tests/testthat/test-pkg_tools.R index d56c0a43..bc1af90a 100644 --- a/tests/testthat/test-pkg_tools.R +++ b/tests/testthat/test-pkg_tools.R @@ -1,9 +1,11 @@ +skip_if_not_installed("pkgload") + test_that("pkgtools works", { withr::with_dir(pkg, { - expect_equal(pkg_name(), fakename) - expect_equal(pkg_version(), "0.0.0.9000") + expect_equal(pkgload::pkg_name(), fakename) + expect_equal(as.character(pkgload::pkg_version()), "0.0.0.9000") # F-word windows path skip_on_os("windows") - expect_equal(pkg_path(), pkg) + expect_equal(pkgload::pkg_path(), pkg) }) }) From fccedc885174d09c8af74e38a713b1b6fcf93b82 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 14 Oct 2022 08:46:29 +0200 Subject: [PATCH 089/190] refactor: check_installed in their own files (#931) --- R/add_dockerfiles.R | 23 ++------ R/add_dockerfiles_renv.R | 21 +++---- R/boostrap_fs.R | 82 +++++++++++++++++++++++++++ R/bootstrap_attachment.R | 23 ++++++++ R/bootstrap_dockerfiler.R | 68 ++++++++++++++++++++++ R/bootstrap_pkgload.R | 37 ++++++++++++ R/bootstrap_roxygen2.R | 23 ++++++++ R/bootstrap_usethis.R | 116 ++++++++++++++++++++++++++++++++++++++ R/fs_boostrap.R | 82 --------------------------- R/modules_fn.R | 1 - R/reload.R | 5 +- R/use_utils.R | 4 -- R/usethis_bootstrap.R | 116 -------------------------------------- 13 files changed, 363 insertions(+), 238 deletions(-) create mode 100644 R/boostrap_fs.R create mode 100644 R/bootstrap_attachment.R create mode 100644 R/bootstrap_dockerfiler.R create mode 100644 R/bootstrap_pkgload.R create mode 100644 R/bootstrap_roxygen2.R create mode 100644 R/bootstrap_usethis.R delete mode 100644 R/fs_boostrap.R delete mode 100644 R/usethis_bootstrap.R diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 490c10a1..aa48f190 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -142,11 +142,6 @@ add_dockerfile_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) where <- fs_path(pkg, output) @@ -154,7 +149,7 @@ add_dockerfile_ <- talk_once( basename(where) ) - dock <- dockerfiler::dock_from_desc( + dock <- dockerfiler_dock_from_desc( path = path, FROM = from, AS = as, @@ -254,16 +249,12 @@ add_dockerfile_shinyproxy_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) + where <- fs_path(pkg, output) usethis_use_build_ignore(output) - dock <- dockerfiler::dock_from_desc( + dock <- dockerfiler_dock_from_desc( path = path, FROM = from, AS = as, @@ -357,16 +348,12 @@ add_dockerfile_heroku_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) + where <- fs_path(pkg, output) usethis_use_build_ignore(output) - dock <- dockerfiler::dock_from_desc( + dock <- dockerfiler_dock_from_desc( path = path, FROM = from, AS = as, diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 93f5d3fb..160a357b 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -16,17 +16,7 @@ add_dockerfile_with_renv_ <- function( "renv", reason = "to build a Dockerfile." ) - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) - rlang::check_installed( - "attachment", - version = "0.2.5", - reason = "to build a Dockerfile." - ) - + # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on x <- suppressWarnings({ @@ -41,7 +31,7 @@ add_dockerfile_with_renv_ <- function( } if (is.null(lockfile)) { - lockfile <- attachment::create_renv_for_prod( + lockfile <- attachment_create_renv_for_prod( path = source_folder, output = file.path(output_dir, "renv.lock.prod") ) @@ -53,7 +43,7 @@ add_dockerfile_with_renv_ <- function( overwrite = TRUE ) - socle <- dockerfiler::dock_from_renv( + socle <- dockerfiler_dock_from_renv( lockfile = lockfile, distro = distro, FROM = FROM, @@ -66,7 +56,10 @@ add_dockerfile_with_renv_ <- function( socle$write(as = file.path(output_dir, "Dockerfile_base")) - my_dock <- dockerfiler::Dockerfile$new(FROM = paste0(golem::get_golem_name(), "_base")) + my_dock <- dockerfiler_Dockerfile()$new( + FROM = paste0(golem::get_golem_name(), + "_base" + )) my_dock$COPY("renv.lock.prod", "renv.lock") diff --git a/R/boostrap_fs.R b/R/boostrap_fs.R new file mode 100644 index 00000000..9b022bf0 --- /dev/null +++ b/R/boostrap_fs.R @@ -0,0 +1,82 @@ +# All the fns here check that {fs} is installed +# before doing anything. +check_fs_installed <- function() { + rlang::check_installed( + "fs", + reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." + ) +} + +fs_dir_exists <- function(path) { + check_fs_installed() + fs::dir_exists(path) +} + +fs_dir_create <- function( + path, + ..., + mode = "u=rwx,go=rx", + recurse = TRUE, + recursive +) { + check_fs_installed() + fs::dir_create( + path, + ..., + mode = mode, + recurse = recurse, + recursive = recursive + ) +} + + +fs_file_create <- function(where) { + check_fs_installed() + fs::file_create(where) +} + +fs_file_delete <- function(path) { + check_fs_installed() + fs::file_delete(path) +} + +fs_file_exists <- function(path) { + check_fs_installed() + fs::file_exists(path) +} + +fs_path_abs <- function(path) { + check_fs_installed() + fs::path_abs(path) +} + +fs_path <- function(..., ext = "") { + check_fs_installed() + fs::path(..., ext = ext) +} + +fs_file_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::file_copy( + path = path, + new_path = new_path, + overwrite + ) +} + +fs_dir_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::dir_copy( + path, + new_path, + overwrite + ) +} diff --git a/R/bootstrap_attachment.R b/R/bootstrap_attachment.R new file mode 100644 index 00000000..08e65c16 --- /dev/null +++ b/R/bootstrap_attachment.R @@ -0,0 +1,23 @@ +# All the fns here check that {attachment} is installed +# before doing anything. +check_attachment_installed <- function() { + rlang::check_installed( + "attachment", + version = "0.2.5", + reason = "to build a Dockerfile." + ) +} + +attachment_create_renv_for_prod <- function( + path = ".", + output = "renv.lock.prod", + dev_pkg = "remotes", + ... +) { + attachment::create_renv_for_prod( + path = path, + output = output, + dev_pkg = dev_pkg, + ... + ) +} diff --git a/R/bootstrap_dockerfiler.R b/R/bootstrap_dockerfiler.R new file mode 100644 index 00000000..5dd79aad --- /dev/null +++ b/R/bootstrap_dockerfiler.R @@ -0,0 +1,68 @@ +# All the fns here check that {dockerfiler} is installed +# before doing anything. +check_dockerfiler_installed <- function() { + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) +} + +dockerfiler_dock_from_renv <- function( + lockfile = "renv.lock", + distro = "focal", + FROM = "rocker/r-base", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL +) { + check_dockerfiler_installed() + dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs + ) +} + +dockerfiler_dock_from_desc <- function( + path = "DESCRIPTION", + FROM = paste0( + "rocker/r-ver:", + R.Version()$major, + ".", + R.Version()$minor + ), + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + update_tar_gz = TRUE, + build_from_source = TRUE, + extra_sysreqs = NULL +) { + check_dockerfiler_installed() + dockerfiler::dock_from_desc( + path = path, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + update_tar_gz = update_tar_gz, + build_from_source = build_from_source, + extra_sysreqs = extra_sysreqs + ) +} + + +dockerfiler_Dockerfile <- function() { + check_dockerfiler_installed() + dockerfiler::Dockerfile +} diff --git a/R/bootstrap_pkgload.R b/R/bootstrap_pkgload.R new file mode 100644 index 00000000..b4db04cc --- /dev/null +++ b/R/bootstrap_pkgload.R @@ -0,0 +1,37 @@ +# All the fns here check that {pkgload} is installed +# before doing anything. +check_pkgload_installed <- function() { + rlang::check_installed( + "pkgload", + reason = "to load the package." + ) +} + +pkgload_load_all <- function( + path = ".", + reset = TRUE, + compile = NA, + attach = TRUE, + export_all = TRUE, + export_imports = export_all, + helpers = TRUE, + attach_testthat = uses_testthat(path), + quiet = NULL, + recompile = FALSE, + warn_conflicts = TRUE +) { + check_roxygen2_installed() + pkgload::load_all( + path = path, + reset = reset, + compile = compile, + attach = attach, + export_all = export_all, + export_imports = export_imports, + helpers = helpers, + attach_testthat = attach_testthat, + quiet = quiet, + recompile = recompile, + warn_conflicts = warn_conflicts + ) +} diff --git a/R/bootstrap_roxygen2.R b/R/bootstrap_roxygen2.R new file mode 100644 index 00000000..e836517c --- /dev/null +++ b/R/bootstrap_roxygen2.R @@ -0,0 +1,23 @@ +# All the fns here check that {roxygen2} is installed +# before doing anything. +check_roxygen2_installed <- function() { + rlang::check_installed( + "roxygen2", + reason = "to document the package." + ) +} + +roxygen2_roxygenise <- function( + package.dir = ".", + roclets = NULL, + load_code = NULL, + clean = FALSE +) { + check_roxygen2_installed() + roxygen2::roxygenise( + package.dir = package.dir, + roclets = roclets, + load_code = load_code, + clean = clean + ) +} diff --git a/R/bootstrap_usethis.R b/R/bootstrap_usethis.R new file mode 100644 index 00000000..a153d78b --- /dev/null +++ b/R/bootstrap_usethis.R @@ -0,0 +1,116 @@ + +# All the fns here check that {usethis} is installed +# before doing anything. +check_usethis_installed <- function(reason = "for project and file manipulation.") { + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = reason + ) +} + +usethis_use_build_ignore <- function( + files, + escape = TRUE +) { + check_usethis_installed( + reason = "to ignore files in the build." + ) + usethis::use_build_ignore( + files, + escape + ) +} +usethis_use_package <- function( + package, + type = "Imports", + min_version = NULL +) { + check_usethis_installed( + reason = "to add dependencies to DESCRIPTION." + ) + usethis::use_package( + package, + type, + min_version + ) +} + +usethis_create_project <- function( + path, + rstudio = rstudioapi::isAvailable(), + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to create a project." + ) + usethis::create_project( + path, + rstudio, + open + ) +} +usethis_use_latest_dependencies <- function( + overwrite = FALSE, + source = c("local", "CRAN") +) { + check_usethis_installed( + reason = "to set dependency version." + ) + usethis::use_latest_dependencies( + overwrite, + source + ) +} + +usethis_proj_set <- function( + path = ".", + force = FALSE +) { + check_usethis_installed( + reason = "to set project." + ) + usethis::proj_set( + path, + force + ) +} +usethis_use_testthat <- function( + edition = NULL, + parallel = FALSE +) { + check_usethis_installed( + reason = "to add {testthat} infrastructure." + ) + usethis::use_testthat( + edition, + parallel + ) +} +usethis_use_test <- function( + name = NULL, + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to add tests." + ) + usethis::use_test( + name, + open + ) +} + +usethis_use_spell_check <- function( + vignettes = TRUE, + lang = "en-US", + error = FALSE +) { + check_usethis_installed( + reason = "to add spellcheck." + ) + usethis::use_spell_check( + vignettes, + lang, + error + ) +} diff --git a/R/fs_boostrap.R b/R/fs_boostrap.R deleted file mode 100644 index 55ab5b83..00000000 --- a/R/fs_boostrap.R +++ /dev/null @@ -1,82 +0,0 @@ -# All the fns here check that {fs} is installed -# before doing anything. -check_fs_installed <- function() { - rlang::check_installed( - "fs", - reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." - ) -} - -fs_dir_exists <- function(path) { - check_fs_installed() - fs::dir_exists(path) -} - -fs_dir_create <- function( - path, - ..., - mode = "u=rwx,go=rx", - recurse = TRUE, - recursive -) { - check_fs_installed() - fs::dir_create( - path, - ..., - mode = mode, - recurse = recurse, - recursive = recursive - ) -} - - -fs_file_create <- function(where) { - check_fs_installed() - fs::file_create(where) -} - -fs_file_delete <- function(path) { - check_fs_installed() - fs::file_delete(path) -} - -fs_file_exists <- function(path) { - check_fs_installed() - fs::file_exists(path) -} - -fs_path_abs <- function(path) { - check_fs_installed() - fs::path_abs(path) -} - -fs_path <- function(..., ext = "") { - check_fs_installed() - fs::path(..., ext = ext) -} - -fs_file_copy <- function( - path, - new_path, - overwrite = FALSE -) { - check_fs_installed() - fs::file_copy( - path = path, - new_path = new_path, - overwrite - ) -} - -fs_dir_copy <- function( - path, - new_path, - overwrite = FALSE -) { - check_fs_installed() - fs::dir_copy( - path, - new_path, - overwrite - ) -} diff --git a/R/modules_fn.R b/R/modules_fn.R index c871b6cf..864c666e 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -257,7 +257,6 @@ use_module_test <- function( # We need both testthat, usethis & fs check_is_installed("testthat") - check_is_installed("fs") old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) diff --git a/R/reload.R b/R/reload.R index b124da96..3f16d7f4 100644 --- a/R/reload.R +++ b/R/reload.R @@ -106,13 +106,12 @@ document_and_reload <- function( check_name_consistency(pkg) rlang::check_installed("pkgload") - rlang::check_installed("roxygen2") if (rstudioapi::isAvailable() & rstudioapi::hasFun("documentSaveAll")) { rstudioapi::documentSaveAll() } roxed <- try({ - roxygen2::roxygenise( + roxygen2_roxygenise( package.dir = pkg, roclets = roclets, load_code = load_code, @@ -127,7 +126,7 @@ document_and_reload <- function( return(invisible(FALSE)) } loaded <- try({ - pkgload::load_all( + pkgload_load_all( pkg, export_all = export_all, helpers = helpers, diff --git a/R/use_utils.R b/R/use_utils.R index ef2c1f46..ebe09768 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -135,10 +135,6 @@ use_utils <- function( folder_name, pkg = get_golem_wd() ) { - rlang::check_installed( - "fs", - reason = "for file manipulation." - ) old <- setwd( fs_path_abs(pkg) diff --git a/R/usethis_bootstrap.R b/R/usethis_bootstrap.R deleted file mode 100644 index 1e513da5..00000000 --- a/R/usethis_bootstrap.R +++ /dev/null @@ -1,116 +0,0 @@ - -# All the fns here check that {usethis} is installed -# before doing anything. -check_usethis_installed <- function(reason = "for project and file manipulation.") { - rlang::check_installed( - "usethis", - version = "1.6.0", - reason = reason - ) -} - -usethis_use_build_ignore <- function( - files, - escape = TRUE -) { - check_usethis_installed( - reason = "to ignore files in the build." - ) - usethis::use_build_ignore( - files, - escape - ) -} -usethis_use_package <- function( - package, - type = "Imports", - min_version = NULL -) { - check_usethis_installed( - reason = "to add dependencies to DESCRIPTION." - ) - usethis::use_package( - package, - type, - min_version - ) -} - -usethis_create_project <- function( - path, - rstudio = rstudioapi::isAvailable(), - open = rlang::is_interactive() -) { - check_usethis_installed( - reason = "to create a project." - ) - usethis::create_project( - path, - rstudio, - open - ) -} -usethis_use_latest_dependencies <- function( - overwrite = FALSE, - source = c("local", "CRAN") -) { - check_usethis_installed( - reason = "to set dependency version." - ) - usethis::use_latest_dependencies( - overwrite, - source - ) -} - -usethis_proj_set <- function( - path = ".", - force = FALSE -) { - check_usethis_installed( - reason = "to set project." - ) - usethis::proj_set( - path, - force - ) -} -usethis_use_testthat <- function( - edition = NULL, - parallel = FALSE -) { - check_usethis_installed( - reason = "to add {testthat} infrastructure." - ) - usethis::use_testthat( - edition, - parallel - ) -} -usethis_use_test <- function( - name = NULL, - open = rlang::is_interactive() -) { - check_usethis_installed( - reason = "to add tests." - ) - usethis::use_test( - name, - open - ) -} - -usethis_use_spell_check <- function( - vignettes = TRUE, - lang = "en-US", - error = FALSE -) { - check_usethis_installed( - reason = "to add spellcheck." - ) - usethis::use_spell_check( - vignettes, - lang, - error - ) -} From beeda815ab49a64743d62cb59adfeede205a1997 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Fri, 14 Oct 2022 19:56:19 +0200 Subject: [PATCH 090/190] feat: Make cat functions conditional to an options (#934) --- R/add_files.R | 2 +- R/bootstrap_pkgload.R | 3 + R/create_golem.R | 4 +- R/disable_autoload.R | 2 +- R/reload.R | 4 +- R/set_golem_options.R | 4 +- R/utils.R | 100 +++++++++++++++++------ tests/testthat/helper-config.R | 4 +- tests/testthat/test-add_deploy_helpers.R | 62 ++++++++------ tests/testthat/test-add_modules.R | 5 +- tests/testthat/test-desc.R | 23 +++--- tests/testthat/test-extra_sysreqs.R | 6 +- tests/testthat/test-make_dev.R | 5 +- tests/testthat/test-test_helpers.R | 14 +++- 14 files changed, 160 insertions(+), 78 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 08a420ea..1b964d21 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -512,7 +512,7 @@ add_sass_file <- function( name = name ) - cli_alert_info( + cat_green_tick( "After running the compilation, your CSS file will be automatically link in `golem_add_external_resources()`." ) } else { diff --git a/R/bootstrap_pkgload.R b/R/bootstrap_pkgload.R index b4db04cc..3c90630a 100644 --- a/R/bootstrap_pkgload.R +++ b/R/bootstrap_pkgload.R @@ -7,6 +7,9 @@ check_pkgload_installed <- function() { ) } +uses_testthat <- getFromNamespace( "uses_testthat", "pkgload" ) + + pkgload_load_all <- function( path = ".", reset = TRUE, diff --git a/R/create_golem.R b/R/create_golem.R index 12bc8db6..fb77355f 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -113,7 +113,9 @@ create_golem <- function( path = path_to_golem, open = FALSE ) - here::set_here(path_to_golem) + if (!file.exists(".here")){ + here::set_here(path_to_golem) + } cat_green_tick("Created package directory") } diff --git a/R/disable_autoload.R b/R/disable_autoload.R index 64215656..a984b02c 100644 --- a/R/disable_autoload.R +++ b/R/disable_autoload.R @@ -20,7 +20,7 @@ disable_autoload <- function(pkg = get_golem_wd()) { "_disable_autoload.R already exists, skipping its creation." ) } else { - cli::cat_rule("Creating _disable_autoload.R") + cat_rule("Creating _disable_autoload.R") write( "# Disabling shiny autoload\n\n# See ?shiny::loadSupport for more information", fls diff --git a/R/reload.R b/R/reload.R index 3f16d7f4..2bb9e51f 100644 --- a/R/reload.R +++ b/R/reload.R @@ -119,7 +119,7 @@ document_and_reload <- function( ) }) if (attempt::is_try_error(roxed)) { - cli::cat_rule( + cat_rule( "Error documenting your package" ) dialog_if_has("Alert", "Error documenting your package") @@ -136,7 +136,7 @@ document_and_reload <- function( }) if (attempt::is_try_error(loaded)) { - cli::cat_rule( + cat_rule( "Error loading your package" ) dialog_if_has("Alert", "Error loading your package") diff --git a/R/set_golem_options.R b/R/set_golem_options.R index 28ddd90c..2a965ae9 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -48,7 +48,7 @@ set_golem_options <- function( # golem_install_dev_pkg() function if (talkative) { - cli::cat_rule( + cat_rule( "Setting {golem} options in `golem-config.yml`" ) } @@ -90,7 +90,7 @@ set_golem_options <- function( # This part is for {usethis} and {here} if (talkative) { - cli::cat_rule( + cat_rule( "Setting {usethis} project as `golem_wd`" ) } diff --git a/R/utils.R b/R/utils.R index 2246d5ea..f6498796 100644 --- a/R/utils.R +++ b/R/utils.R @@ -113,29 +113,55 @@ remove_comments <- function(file) { #' @importFrom cli cat_bullet cat_green_tick <- function(...) { - cat_bullet( - ..., - bullet = "tick", - bullet_col = "green" - ) + do_if_unquiet({ + cat_bullet( + ..., + bullet = "tick", + bullet_col = "green" + ) + }) } #' @importFrom cli cat_bullet cat_red_bullet <- function(...) { - cat_bullet( - ..., - bullet = "bullet", - bullet_col = "red" - ) + do_if_unquiet({ + cli::cat_bullet( + ..., + bullet = "bullet", + bullet_col = "red" + ) + }) } #' @importFrom cli cat_bullet cat_info <- function(...) { - cat_bullet( - ..., - bullet = "arrow_right", - bullet_col = "grey" - ) + do_if_unquiet({ + cli::cat_bullet( + ..., + bullet = "arrow_right", + bullet_col = "grey" + ) + }) +} + +cat_rule <- function( + ... +) { + do_if_unquiet({ + cli::cat_rule( + ... + ) + }) +} + +cat_line <- function( + ... +) { + do_if_unquiet({ + cli::cat_line( + ... + ) + }) } cat_exists <- function(where) { @@ -160,8 +186,10 @@ cat_dir_necessary <- function() { } cat_start_download <- function() { - cat_line("") - cat_rule("Initiating file download") + do_if_unquiet({ + cat_line("") + cat_rule("Initiating file download") + }) } cat_downloaded <- function( @@ -178,8 +206,10 @@ cat_downloaded <- function( } cat_start_copy <- function() { - cat_line("") - cat_rule("Copying file") + do_if_unquiet({ + cat_line("") + cat_rule("Copying file") + }) } cat_copied <- function( @@ -314,13 +344,15 @@ after_creation_message_html_template <- function( dir, name ) { - cat_line("") - cat_rule("To use this html file as a template, add the following code in your UI:") - cat_line(darkgrey("htmlTemplate(")) - cat_line(darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) - cat_line(darkgrey(" body = tagList()")) - cat_line(darkgrey(" # add here other template arguments")) - cat_line(darkgrey(")")) + do_if_unquiet({ + cat_line("") + cat_rule("To use this html file as a template, add the following code in your UI:") + cat_line(darkgrey("htmlTemplate(")) + cat_line(darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) + cat_line(darkgrey(" body = tagList()")) + cat_line(darkgrey(" # add here other template arguments")) + cat_line(darkgrey(")")) + }) } file_created_dance <- function( @@ -447,3 +479,19 @@ check_name_length <- function(name) { ) ) } + +do_if_unquiet <- function( + expr +){ + if( + ! getOption( + "golem.quiet", + getOption( + "usethis.quiet", + default = FALSE + ) + ) + ){ + force(expr) + } +} \ No newline at end of file diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 52f85e55..f50b9073 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -81,7 +81,9 @@ rand_name <- function() { withr::with_dir(pkg, { # Some weird things with {here} unloadNamespace("here") - here::set_here(".") + if (!file.exists(".here")){ + here::set_here(path_to_golem) + } set_golem_options() usethis::proj_set(pkg) orig_test <- set_golem_wd( diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index b798603f..0bbe2181 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -12,9 +12,14 @@ test_that("add_dockerfiles", { burn_after_reading( "Dockerfile", { - output <- testthat::capture_output( - fun(pkg = pkg, sysreqs = FALSE, open = FALSE) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output( + fun(pkg = pkg, sysreqs = FALSE, open = FALSE) + ) + } ) + expect_exists("Dockerfile") test <- stringr::str_detect( output, @@ -37,27 +42,29 @@ test_that("add_dockerfiles repos variation", { burn_after_reading( c("Dockerfile1", "Dockerfile2"), { - output1 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = "https://cran.rstudio.com/", - output = "Dockerfile1" + withr::with_options( + c("golem.quiet" = FALSE),{ + output1 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = "https://cran.rstudio.com/", + output = "Dockerfile1" + ) ) - ) - output2 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = c("https://cran.rstudio.com/"), - output = "Dockerfile2" + output2 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = c("https://cran.rstudio.com/"), + output = "Dockerfile2" + ) ) - ) - expect_exists("Dockerfile1") - expect_exists("Dockerfile2") - + expect_exists("Dockerfile1") + expect_exists("Dockerfile2") + }) test1 <- stringr::str_detect( output1, @@ -91,12 +98,15 @@ test_that("add_rstudio_files", { burn_after_reading( "app.R", { - output <- testthat::capture_output( - fun( - pkg = pkg, - open = FALSE - ) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output( + fun( + pkg = pkg, + open = FALSE + ) ) + }) expect_exists("app.R") test <- stringr::str_detect( output, diff --git a/tests/testthat/test-add_modules.R b/tests/testthat/test-add_modules.R index a1584aef..af70b65b 100644 --- a/tests/testthat/test-add_modules.R +++ b/tests/testthat/test-add_modules.R @@ -15,7 +15,10 @@ test_that("add_module", { lapply(tools::file_ext(script), function(x) testthat::expect_equal(x, "R")) ## Test message of function remove_file("R/mod_output.R") - output <- testthat::capture_output(add_module("output", open = FALSE)) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output(add_module("output", open = FALSE)) + }) expect_true( stringr::str_detect(output, "File created at R/mod_output.R") ) diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 7af2b91f..86ec958b 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -1,17 +1,20 @@ test_that("desc works", { with_dir(pkg, { - output <- capture_output( - fill_desc( - fakename, - "newtitle", - "Newdescription.", - "firstname", - "lastname", - "name@test.com", - "http://repo_url.com" + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- capture_output( + fill_desc( + fakename, + "newtitle", + "Newdescription.", + "firstname", + "lastname", + "name@test.com", + "http://repo_url.com" + ) ) - ) + }) add_desc <- c( fakename, "newtitle", diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index d0e47254..286f0999 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -13,7 +13,9 @@ test_that("test extra sysreqs", { burn_after_reading( "Dockerfile", { - output <- testthat::capture_output( + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- testthat::capture_output( fun( pkg = pkg, sysreqs = FALSE, @@ -22,7 +24,7 @@ test_that("test extra sysreqs", { output = "Dockerfile" ) ) - + }) expect_exists("Dockerfile") test <- stringr::str_detect( output, diff --git a/tests/testthat/test-make_dev.R b/tests/testthat/test-make_dev.R index bbf46547..5f736b7a 100644 --- a/tests/testthat/test-make_dev.R +++ b/tests/testthat/test-make_dev.R @@ -60,7 +60,10 @@ test_that("test print_dev", { test_that("test browser_button", { - output <- capture_output_lines(browser_button()) + withr::with_options( + c("golem.quiet" = FALSE),{ + output <- capture_output_lines(browser_button()) + }) expect_true( grepl('actionButton\\("browser", "browser"\\)', output[2]) ) diff --git a/tests/testthat/test-test_helpers.R b/tests/testthat/test-test_helpers.R index c1e4ccad..82aa04c2 100644 --- a/tests/testthat/test-test_helpers.R +++ b/tests/testthat/test-test_helpers.R @@ -1,13 +1,19 @@ test_that("test expect_shinytag", { with_dir(pkg, { - expect_equal(capture_output(expect_shinytag(favicon("jean"))), "") - expect_error(expect_shinytag("pierre")) + withr::with_options( + c("golem.quiet" = FALSE),{ + expect_equal(capture_output(expect_shinytag(favicon("jean"))), "") + expect_error(expect_shinytag("pierre")) + }) }) }) test_that("test expect_shinytaglist", { with_dir(pkg, { - expect_equal(capture_output(expect_shinytaglist(shiny::tagList())), "") - expect_error(expect_shinytaglist("test")) + withr::with_options( + c("golem.quiet" = FALSE),{ + expect_equal(capture_output(expect_shinytaglist(shiny::tagList())), "") + expect_error(expect_shinytaglist("test")) + }) }) }) From a1f80f5f8a5ed1d7c25816d609afba3a8ccadd77 Mon Sep 17 00:00:00 2001 From: Antoine Languillaume <43757522+ALanguillaume@users.noreply.github.com> Date: Thu, 27 Oct 2022 11:26:37 +0200 Subject: [PATCH 091/190] doc: improve documentation of dockerfile related functions (#939) --- R/add_dockerfiles.R | 9 ++++++--- man/dockerfiles.Rd | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 0cf7ea6e..3ff28048 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -11,8 +11,10 @@ talk_once <- function(.f, msg = "") { #' Create a Dockerfile for your App #' -#' Build a container containing your Shiny App. `add_dockerfile()` and `add_dockerfile_with_renv()` and `add_dockerfile_with_renv()` creates -#' a generic Dockerfile, while `add_dockerfile_shinyproxy()`, `add_dockerfile_with_renv_shinyproxy()` , `add_dockerfile_with_renv_shinyproxy()` and +#' Build a container containing your Shiny App. `add_dockerfile()` and +#' `add_dockerfile_with_renv()` and `add_dockerfile_with_renv()` creates +#' a generic Dockerfile, while `add_dockerfile_shinyproxy()`, +#' `add_dockerfile_with_renv_shinyproxy()` , `add_dockerfile_with_renv_shinyproxy()` and #' `add_dockerfile_heroku()` creates platform specific Dockerfile. #' #' @inheritParams add_module @@ -31,7 +33,8 @@ talk_once <- function(.f, msg = "") { #' Default is 80. #' @param host The `options('shiny.host')` on which to run the App. #' Default is 0.0.0.0. -#' @param sysreqs boolean. If TRUE, the Dockerfile will contain sysreq installation. +#' @param sysreqs boolean. If TRUE, RUN statements to install packages +#' system requirements will be included in the Dockerfile. #' @param repos character. The URL(s) of the repositories to use for `options("repos")`. #' @param expand boolean. If `TRUE` each system requirement will have its own `RUN` line. #' @param open boolean. Should the Dockerfile/README/README be open after creation? Default is `TRUE`. diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index a8a7738e..6a54e1a0 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -127,7 +127,8 @@ Default is 80.} \item{host}{The \code{options('shiny.host')} on which to run the App. Default is 0.0.0.0.} -\item{sysreqs}{boolean. If TRUE, the Dockerfile will contain sysreq installation.} +\item{sysreqs}{boolean. If TRUE, RUN statements to install packages +system requirements will be included in the Dockerfile.} \item{repos}{character. The URL(s) of the repositories to use for \code{options("repos")}.} @@ -160,8 +161,10 @@ the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}') The \code{{dockerfiler}} object, invisibly. } \description{ -Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} and \code{add_dockerfile_with_renv()} creates -a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} , \code{add_dockerfile_with_renv_shinyproxy()} and +Build a container containing your Shiny App. \code{add_dockerfile()} and +\code{add_dockerfile_with_renv()} and \code{add_dockerfile_with_renv()} creates +a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, +\code{add_dockerfile_with_renv_shinyproxy()} , \code{add_dockerfile_with_renv_shinyproxy()} and \code{add_dockerfile_heroku()} creates platform specific Dockerfile. } \examples{ From 5c4d10e7ee01492a76d578c66931f6e72937136f Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 27 Oct 2022 11:29:34 +0200 Subject: [PATCH 092/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index f8af5849..5ec8d78d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.5.9001 +Version: 0.3.5.9002 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index 7591fb56..8cf9bb2f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,7 @@ + `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. + `{golem}` now depends on `{rlang}` version >= 1.0.0 + Functions that print to the console are now quiet if `options("golem.quiet" = TRUE)`, #793 ++ Small documentation update in dockerfile related functions (#939) ## Bug fix From 842d0c32c82d558dd321af00486234d7944a1e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arthur=20Br=C3=A9ant?= <35060481+ArthurData@users.noreply.github.com> Date: Thu, 27 Oct 2022 11:59:10 +0200 Subject: [PATCH 093/190] feat: fill_desc allows to set package version (#941) --- R/desc.R | 6 ++++-- inst/mantests/build.R | 3 ++- inst/shinyexample/dev/01_start.R | 3 ++- man/fill_desc.Rd | 3 +++ tests/testthat/test-desc.R | 18 ++++++++++-------- vignettes/a_start.Rmd | 3 ++- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/R/desc.R b/R/desc.R index 45bb25fd..ffb68d8a 100644 --- a/R/desc.R +++ b/R/desc.R @@ -8,6 +8,7 @@ #' @param author_email Email of the author #' @param author_orcid ORCID of the author #' @param repo_url URL (if needed) +#' @param pkg_version The version of the package. Default is 0.0.0.9000 #' @param pkg Path to look for the DESCRIPTION. Default is `get_golem_wd()`. #' #' @importFrom desc description @@ -25,6 +26,7 @@ fill_desc <- function( author_email, author_orcid = NULL, repo_url = NULL, + pkg_version = "0.0.0.9000", pkg = get_golem_wd() ) { path <- fs_path_abs(pkg) @@ -64,10 +66,10 @@ fill_desc <- function( keys = "Maintainer" ) desc$set_version( - version = "0.0.0.9000" + version = pkg_version ) set_golem_version( - version = "0.0.0.9000", + version = pkg_version, pkg = path ) desc$set( diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 1f7cde70..0cc9aa44 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -204,7 +204,8 @@ withr::with_tempdir({ author_first_name = "Colin", # Your First Name author_last_name = "Fay", # Your Last Name author_email = "colin@thinkr.fr", # Your Email - repo_url = NULL # The URL of the GitHub Repo (optional) + repo_url = NULL, # The URL of the GitHub Repo (optional) + pkg_version = "0.0.0.9000" # The Version of the package containing the App ) cli::cat_rule("checking package name") diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 5ea36dc4..24eee49e 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -25,7 +25,8 @@ golem::fill_desc( author_first_name = "AUTHOR_FIRST", # Your First Name author_last_name = "AUTHOR_LAST", # Your Last Name author_email = "AUTHOR@MAIL.COM", # Your Email - repo_url = NULL # The URL of the GitHub Repo (optional) + repo_url = NULL, # The URL of the GitHub Repo (optional), + pkg_version = "0.0.0.9000" # The Version of the package containing the App ) ## Set {golem} options ---- diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index 87c4885a..a851ae08 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -13,6 +13,7 @@ fill_desc( author_email, author_orcid = NULL, repo_url = NULL, + pkg_version = "0.0.0.9000", pkg = get_golem_wd() ) } @@ -33,6 +34,8 @@ fill_desc( \item{repo_url}{URL (if needed)} +\item{pkg_version}{The version of the package. Default is 0.0.0.9000} + \item{pkg}{Path to look for the DESCRIPTION. Default is \code{get_golem_wd()}.} } \value{ diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 86ec958b..e147f82e 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -5,13 +5,14 @@ test_that("desc works", { c("golem.quiet" = FALSE),{ output <- capture_output( fill_desc( - fakename, - "newtitle", - "Newdescription.", - "firstname", - "lastname", - "name@test.com", - "http://repo_url.com" + pkg_name = fakename, + pkg_title = "newtitle", + pkg_description = "Newdescription.", + author_first_name = "firstname", + author_last_name = "lastname", + author_email = "name@test.com", + repo_url = "http://repo_url.com", + pkg_version = "0.0.0.9000" ) ) }) @@ -22,7 +23,8 @@ test_that("desc works", { "firstname", "lastname", "name@test.com", - "http://repo_url.com" + "http://repo_url.com", + "0.0.0.9000" ) desc <- readLines("DESCRIPTION") diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 18e474e0..0dd31ef2 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -119,7 +119,8 @@ golem::fill_desc( author_first_name = "AUTHOR_FIRST", # Your First Name author_last_name = "AUTHOR_LAST", # Your Last Name author_email = "AUTHOR@MAIL.COM", # Your Email - repo_url = NULL # The (optional) URL of the GitHub Repo + repo_url = NULL, # The (optional) URL of the GitHub Repo + pkg_version = "0.0.0.9000" # The Version of the package containing the App ) ``` From 38df42cf84a6b24f982c5f9170b6075764e53ddb Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 27 Oct 2022 12:00:27 +0200 Subject: [PATCH 094/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 5ec8d78d..b0011c5d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.5.9002 +Version: 0.3.5.9003 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index 8cf9bb2f..d9ccf69c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -18,7 +18,7 @@ + `{golem}` now depends on `{rlang}` version >= 1.0.0 + Functions that print to the console are now quiet if `options("golem.quiet" = TRUE)`, #793 + Small documentation update in dockerfile related functions (#939) - ++ `fill_desc()` now allows to set the version (#877) ## Bug fix + The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) From a75e828f13814c547a57600cafd4154aa3ebdca7 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 27 Oct 2022 12:11:35 +0200 Subject: [PATCH 095/190] style: styler changes --- R/add_dockerfiles.R | 3 - R/add_dockerfiles_renv.R | 10 +- R/boostrap_fs.R | 94 ++++++------- R/bootstrap_attachment.R | 30 ++-- R/bootstrap_dockerfiler.R | 104 +++++++------- R/bootstrap_pkgload.R | 60 ++++---- R/bootstrap_roxygen2.R | 30 ++-- R/bootstrap_usethis.R | 166 +++++++++++------------ R/create_golem.R | 2 +- R/modules_fn.R | 2 +- R/use_utils.R | 1 - R/utils.R | 36 ++--- tests/testthat/helper-config.R | 2 +- tests/testthat/test-add_deploy_helpers.R | 63 +++++---- tests/testthat/test-add_modules.R | 8 +- tests/testthat/test-desc.R | 8 +- tests/testthat/test-extra_sysreqs.R | 24 ++-- tests/testthat/test-make_dev.R | 8 +- tests/testthat/test-test_helpers.R | 12 +- 19 files changed, 336 insertions(+), 327 deletions(-) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 3ff28048..323eae56 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -145,7 +145,6 @@ add_dockerfile_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - where <- fs_path(pkg, output) usethis_use_build_ignore( @@ -252,7 +251,6 @@ add_dockerfile_shinyproxy_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - where <- fs_path(pkg, output) usethis_use_build_ignore(output) @@ -351,7 +349,6 @@ add_dockerfile_heroku_ <- talk_once( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - where <- fs_path(pkg, output) usethis_use_build_ignore(output) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 160a357b..bed36cfa 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -16,7 +16,7 @@ add_dockerfile_with_renv_ <- function( "renv", reason = "to build a Dockerfile." ) - + # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on x <- suppressWarnings({ @@ -57,9 +57,11 @@ add_dockerfile_with_renv_ <- function( socle$write(as = file.path(output_dir, "Dockerfile_base")) my_dock <- dockerfiler_Dockerfile()$new( - FROM = paste0(golem::get_golem_name(), - "_base" - )) + FROM = paste0( + golem::get_golem_name(), + "_base" + ) + ) my_dock$COPY("renv.lock.prod", "renv.lock") diff --git a/R/boostrap_fs.R b/R/boostrap_fs.R index 9b022bf0..55ab5b83 100644 --- a/R/boostrap_fs.R +++ b/R/boostrap_fs.R @@ -1,82 +1,82 @@ # All the fns here check that {fs} is installed # before doing anything. check_fs_installed <- function() { - rlang::check_installed( - "fs", - reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." - ) + rlang::check_installed( + "fs", + reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." + ) } fs_dir_exists <- function(path) { - check_fs_installed() - fs::dir_exists(path) + check_fs_installed() + fs::dir_exists(path) } fs_dir_create <- function( - path, - ..., - mode = "u=rwx,go=rx", - recurse = TRUE, - recursive + path, + ..., + mode = "u=rwx,go=rx", + recurse = TRUE, + recursive ) { - check_fs_installed() - fs::dir_create( - path, - ..., - mode = mode, - recurse = recurse, - recursive = recursive - ) + check_fs_installed() + fs::dir_create( + path, + ..., + mode = mode, + recurse = recurse, + recursive = recursive + ) } fs_file_create <- function(where) { - check_fs_installed() - fs::file_create(where) + check_fs_installed() + fs::file_create(where) } fs_file_delete <- function(path) { - check_fs_installed() - fs::file_delete(path) + check_fs_installed() + fs::file_delete(path) } fs_file_exists <- function(path) { - check_fs_installed() - fs::file_exists(path) + check_fs_installed() + fs::file_exists(path) } fs_path_abs <- function(path) { - check_fs_installed() - fs::path_abs(path) + check_fs_installed() + fs::path_abs(path) } fs_path <- function(..., ext = "") { - check_fs_installed() - fs::path(..., ext = ext) + check_fs_installed() + fs::path(..., ext = ext) } fs_file_copy <- function( - path, - new_path, - overwrite = FALSE + path, + new_path, + overwrite = FALSE ) { - check_fs_installed() - fs::file_copy( - path = path, - new_path = new_path, - overwrite - ) + check_fs_installed() + fs::file_copy( + path = path, + new_path = new_path, + overwrite + ) } fs_dir_copy <- function( - path, - new_path, - overwrite = FALSE + path, + new_path, + overwrite = FALSE ) { - check_fs_installed() - fs::dir_copy( - path, - new_path, - overwrite - ) + check_fs_installed() + fs::dir_copy( + path, + new_path, + overwrite + ) } diff --git a/R/bootstrap_attachment.R b/R/bootstrap_attachment.R index 08e65c16..887b6939 100644 --- a/R/bootstrap_attachment.R +++ b/R/bootstrap_attachment.R @@ -1,23 +1,23 @@ # All the fns here check that {attachment} is installed # before doing anything. check_attachment_installed <- function() { - rlang::check_installed( - "attachment", - version = "0.2.5", - reason = "to build a Dockerfile." - ) + rlang::check_installed( + "attachment", + version = "0.2.5", + reason = "to build a Dockerfile." + ) } attachment_create_renv_for_prod <- function( - path = ".", - output = "renv.lock.prod", - dev_pkg = "remotes", - ... + path = ".", + output = "renv.lock.prod", + dev_pkg = "remotes", + ... ) { - attachment::create_renv_for_prod( - path = path, - output = output, - dev_pkg = dev_pkg, - ... - ) + attachment::create_renv_for_prod( + path = path, + output = output, + dev_pkg = dev_pkg, + ... + ) } diff --git a/R/bootstrap_dockerfiler.R b/R/bootstrap_dockerfiler.R index 5dd79aad..9d284875 100644 --- a/R/bootstrap_dockerfiler.R +++ b/R/bootstrap_dockerfiler.R @@ -1,68 +1,68 @@ # All the fns here check that {dockerfiler} is installed # before doing anything. check_dockerfiler_installed <- function() { - rlang::check_installed( - "dockerfiler", - version = "0.2.0", - reason = "to build a Dockerfile." - ) + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) } dockerfiler_dock_from_renv <- function( - lockfile = "renv.lock", - distro = "focal", - FROM = "rocker/r-base", - AS = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - extra_sysreqs = NULL + lockfile = "renv.lock", + distro = "focal", + FROM = "rocker/r-base", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL ) { - check_dockerfiler_installed() - dockerfiler::dock_from_renv( - lockfile = lockfile, - distro = distro, - FROM = FROM, - AS = AS, - sysreqs = sysreqs, - repos = repos, - expand = expand, - extra_sysreqs = extra_sysreqs - ) + check_dockerfiler_installed() + dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs + ) } dockerfiler_dock_from_desc <- function( - path = "DESCRIPTION", - FROM = paste0( - "rocker/r-ver:", - R.Version()$major, - ".", - R.Version()$minor - ), - AS = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - update_tar_gz = TRUE, - build_from_source = TRUE, - extra_sysreqs = NULL + path = "DESCRIPTION", + FROM = paste0( + "rocker/r-ver:", + R.Version()$major, + ".", + R.Version()$minor + ), + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + update_tar_gz = TRUE, + build_from_source = TRUE, + extra_sysreqs = NULL ) { - check_dockerfiler_installed() - dockerfiler::dock_from_desc( - path = path, - FROM = FROM, - AS = AS, - sysreqs = sysreqs, - repos = repos, - expand = expand, - update_tar_gz = update_tar_gz, - build_from_source = build_from_source, - extra_sysreqs = extra_sysreqs - ) + check_dockerfiler_installed() + dockerfiler::dock_from_desc( + path = path, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + update_tar_gz = update_tar_gz, + build_from_source = build_from_source, + extra_sysreqs = extra_sysreqs + ) } dockerfiler_Dockerfile <- function() { - check_dockerfiler_installed() - dockerfiler::Dockerfile + check_dockerfiler_installed() + dockerfiler::Dockerfile } diff --git a/R/bootstrap_pkgload.R b/R/bootstrap_pkgload.R index 3c90630a..c2666a5a 100644 --- a/R/bootstrap_pkgload.R +++ b/R/bootstrap_pkgload.R @@ -1,40 +1,40 @@ # All the fns here check that {pkgload} is installed # before doing anything. check_pkgload_installed <- function() { - rlang::check_installed( - "pkgload", - reason = "to load the package." - ) + rlang::check_installed( + "pkgload", + reason = "to load the package." + ) } -uses_testthat <- getFromNamespace( "uses_testthat", "pkgload" ) +uses_testthat <- getFromNamespace("uses_testthat", "pkgload") pkgload_load_all <- function( - path = ".", - reset = TRUE, - compile = NA, - attach = TRUE, - export_all = TRUE, - export_imports = export_all, - helpers = TRUE, - attach_testthat = uses_testthat(path), - quiet = NULL, - recompile = FALSE, - warn_conflicts = TRUE + path = ".", + reset = TRUE, + compile = NA, + attach = TRUE, + export_all = TRUE, + export_imports = export_all, + helpers = TRUE, + attach_testthat = uses_testthat(path), + quiet = NULL, + recompile = FALSE, + warn_conflicts = TRUE ) { - check_roxygen2_installed() - pkgload::load_all( - path = path, - reset = reset, - compile = compile, - attach = attach, - export_all = export_all, - export_imports = export_imports, - helpers = helpers, - attach_testthat = attach_testthat, - quiet = quiet, - recompile = recompile, - warn_conflicts = warn_conflicts - ) + check_roxygen2_installed() + pkgload::load_all( + path = path, + reset = reset, + compile = compile, + attach = attach, + export_all = export_all, + export_imports = export_imports, + helpers = helpers, + attach_testthat = attach_testthat, + quiet = quiet, + recompile = recompile, + warn_conflicts = warn_conflicts + ) } diff --git a/R/bootstrap_roxygen2.R b/R/bootstrap_roxygen2.R index e836517c..f958842a 100644 --- a/R/bootstrap_roxygen2.R +++ b/R/bootstrap_roxygen2.R @@ -1,23 +1,23 @@ # All the fns here check that {roxygen2} is installed # before doing anything. check_roxygen2_installed <- function() { - rlang::check_installed( - "roxygen2", - reason = "to document the package." - ) + rlang::check_installed( + "roxygen2", + reason = "to document the package." + ) } roxygen2_roxygenise <- function( - package.dir = ".", - roclets = NULL, - load_code = NULL, - clean = FALSE + package.dir = ".", + roclets = NULL, + load_code = NULL, + clean = FALSE ) { - check_roxygen2_installed() - roxygen2::roxygenise( - package.dir = package.dir, - roclets = roclets, - load_code = load_code, - clean = clean - ) + check_roxygen2_installed() + roxygen2::roxygenise( + package.dir = package.dir, + roclets = roclets, + load_code = load_code, + clean = clean + ) } diff --git a/R/bootstrap_usethis.R b/R/bootstrap_usethis.R index a153d78b..1e513da5 100644 --- a/R/bootstrap_usethis.R +++ b/R/bootstrap_usethis.R @@ -2,115 +2,115 @@ # All the fns here check that {usethis} is installed # before doing anything. check_usethis_installed <- function(reason = "for project and file manipulation.") { - rlang::check_installed( - "usethis", - version = "1.6.0", - reason = reason - ) + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = reason + ) } usethis_use_build_ignore <- function( - files, - escape = TRUE + files, + escape = TRUE ) { - check_usethis_installed( - reason = "to ignore files in the build." - ) - usethis::use_build_ignore( - files, - escape - ) + check_usethis_installed( + reason = "to ignore files in the build." + ) + usethis::use_build_ignore( + files, + escape + ) } usethis_use_package <- function( - package, - type = "Imports", - min_version = NULL + package, + type = "Imports", + min_version = NULL ) { - check_usethis_installed( - reason = "to add dependencies to DESCRIPTION." - ) - usethis::use_package( - package, - type, - min_version - ) + check_usethis_installed( + reason = "to add dependencies to DESCRIPTION." + ) + usethis::use_package( + package, + type, + min_version + ) } usethis_create_project <- function( - path, - rstudio = rstudioapi::isAvailable(), - open = rlang::is_interactive() + path, + rstudio = rstudioapi::isAvailable(), + open = rlang::is_interactive() ) { - check_usethis_installed( - reason = "to create a project." - ) - usethis::create_project( - path, - rstudio, - open - ) + check_usethis_installed( + reason = "to create a project." + ) + usethis::create_project( + path, + rstudio, + open + ) } usethis_use_latest_dependencies <- function( - overwrite = FALSE, - source = c("local", "CRAN") + overwrite = FALSE, + source = c("local", "CRAN") ) { - check_usethis_installed( - reason = "to set dependency version." - ) - usethis::use_latest_dependencies( - overwrite, - source - ) + check_usethis_installed( + reason = "to set dependency version." + ) + usethis::use_latest_dependencies( + overwrite, + source + ) } usethis_proj_set <- function( - path = ".", - force = FALSE + path = ".", + force = FALSE ) { - check_usethis_installed( - reason = "to set project." - ) - usethis::proj_set( - path, - force - ) + check_usethis_installed( + reason = "to set project." + ) + usethis::proj_set( + path, + force + ) } usethis_use_testthat <- function( - edition = NULL, - parallel = FALSE + edition = NULL, + parallel = FALSE ) { - check_usethis_installed( - reason = "to add {testthat} infrastructure." - ) - usethis::use_testthat( - edition, - parallel - ) + check_usethis_installed( + reason = "to add {testthat} infrastructure." + ) + usethis::use_testthat( + edition, + parallel + ) } usethis_use_test <- function( - name = NULL, - open = rlang::is_interactive() + name = NULL, + open = rlang::is_interactive() ) { - check_usethis_installed( - reason = "to add tests." - ) - usethis::use_test( - name, - open - ) + check_usethis_installed( + reason = "to add tests." + ) + usethis::use_test( + name, + open + ) } usethis_use_spell_check <- function( - vignettes = TRUE, - lang = "en-US", - error = FALSE + vignettes = TRUE, + lang = "en-US", + error = FALSE ) { - check_usethis_installed( - reason = "to add spellcheck." - ) - usethis::use_spell_check( - vignettes, - lang, - error - ) + check_usethis_installed( + reason = "to add spellcheck." + ) + usethis::use_spell_check( + vignettes, + lang, + error + ) } diff --git a/R/create_golem.R b/R/create_golem.R index fb77355f..468f51ac 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -113,7 +113,7 @@ create_golem <- function( path = path_to_golem, open = FALSE ) - if (!file.exists(".here")){ + if (!file.exists(".here")) { here::set_here(path_to_golem) } cat_green_tick("Created package directory") diff --git a/R/modules_fn.R b/R/modules_fn.R index 989c6421..1476f12a 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -260,7 +260,7 @@ use_module_test <- function( "testthat", "to build the test structure." ) - + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) diff --git a/R/use_utils.R b/R/use_utils.R index ebe09768..d004b5e1 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -135,7 +135,6 @@ use_utils <- function( folder_name, pkg = get_golem_wd() ) { - old <- setwd( fs_path_abs(pkg) ) diff --git a/R/utils.R b/R/utils.R index f6498796..a461e9a9 100644 --- a/R/utils.R +++ b/R/utils.R @@ -115,10 +115,10 @@ remove_comments <- function(file) { cat_green_tick <- function(...) { do_if_unquiet({ cat_bullet( - ..., - bullet = "tick", - bullet_col = "green" - ) + ..., + bullet = "tick", + bullet_col = "green" + ) }) } @@ -144,9 +144,7 @@ cat_info <- function(...) { }) } -cat_rule <- function( - ... -) { +cat_rule <- function(...) { do_if_unquiet({ cli::cat_rule( ... @@ -154,11 +152,9 @@ cat_rule <- function( }) } -cat_line <- function( - ... -) { +cat_line <- function(...) { do_if_unquiet({ - cli::cat_line( + cli::cat_line( ... ) }) @@ -480,18 +476,16 @@ check_name_length <- function(name) { ) } -do_if_unquiet <- function( - expr -){ - if( - ! getOption( - "golem.quiet", +do_if_unquiet <- function(expr) { + if ( + !getOption( + "golem.quiet", getOption( - "usethis.quiet", + "usethis.quiet", default = FALSE - ) + ) ) - ){ + ) { force(expr) } -} \ No newline at end of file +} diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index f50b9073..7c80580b 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -81,7 +81,7 @@ rand_name <- function() { withr::with_dir(pkg, { # Some weird things with {here} unloadNamespace("here") - if (!file.exists(".here")){ + if (!file.exists(".here")) { here::set_here(path_to_golem) } set_golem_options() diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 0bbe2181..70d9e9e0 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -13,13 +13,14 @@ test_that("add_dockerfiles", { "Dockerfile", { withr::with_options( - c("golem.quiet" = FALSE),{ + c("golem.quiet" = FALSE), + { output <- testthat::capture_output( fun(pkg = pkg, sysreqs = FALSE, open = FALSE) ) } ) - + expect_exists("Dockerfile") test <- stringr::str_detect( output, @@ -43,28 +44,30 @@ test_that("add_dockerfiles repos variation", { c("Dockerfile1", "Dockerfile2"), { withr::with_options( - c("golem.quiet" = FALSE),{ - output1 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = "https://cran.rstudio.com/", - output = "Dockerfile1" + c("golem.quiet" = FALSE), + { + output1 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = "https://cran.rstudio.com/", + output = "Dockerfile1" + ) ) - ) - output2 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = c("https://cran.rstudio.com/"), - output = "Dockerfile2" + output2 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = c("https://cran.rstudio.com/"), + output = "Dockerfile2" + ) ) - ) - expect_exists("Dockerfile1") - expect_exists("Dockerfile2") - }) + expect_exists("Dockerfile1") + expect_exists("Dockerfile2") + } + ) test1 <- stringr::str_detect( output1, @@ -98,15 +101,17 @@ test_that("add_rstudio_files", { burn_after_reading( "app.R", { - withr::with_options( - c("golem.quiet" = FALSE),{ - output <- testthat::capture_output( - fun( - pkg = pkg, - open = FALSE + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- testthat::capture_output( + fun( + pkg = pkg, + open = FALSE + ) ) + } ) - }) expect_exists("app.R") test <- stringr::str_detect( output, diff --git a/tests/testthat/test-add_modules.R b/tests/testthat/test-add_modules.R index af70b65b..85c767ea 100644 --- a/tests/testthat/test-add_modules.R +++ b/tests/testthat/test-add_modules.R @@ -16,9 +16,11 @@ test_that("add_module", { ## Test message of function remove_file("R/mod_output.R") withr::with_options( - c("golem.quiet" = FALSE),{ - output <- testthat::capture_output(add_module("output", open = FALSE)) - }) + c("golem.quiet" = FALSE), + { + output <- testthat::capture_output(add_module("output", open = FALSE)) + } + ) expect_true( stringr::str_detect(output, "File created at R/mod_output.R") ) diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index e147f82e..6b851eef 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -2,7 +2,8 @@ test_that("desc works", { with_dir(pkg, { withr::with_options( - c("golem.quiet" = FALSE),{ + c("golem.quiet" = FALSE), + { output <- capture_output( fill_desc( pkg_name = fakename, @@ -14,8 +15,9 @@ test_that("desc works", { repo_url = "http://repo_url.com", pkg_version = "0.0.0.9000" ) - ) - }) + ) + } + ) add_desc <- c( fakename, "newtitle", diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index 286f0999..53b906cb 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -13,18 +13,20 @@ test_that("test extra sysreqs", { burn_after_reading( "Dockerfile", { - withr::with_options( - c("golem.quiet" = FALSE),{ - output <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - extra_sysreqs = c("test1", "test2"), - output = "Dockerfile" - ) + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + extra_sysreqs = c("test1", "test2"), + output = "Dockerfile" + ) + ) + } ) - }) expect_exists("Dockerfile") test <- stringr::str_detect( output, diff --git a/tests/testthat/test-make_dev.R b/tests/testthat/test-make_dev.R index 5f736b7a..66013a26 100644 --- a/tests/testthat/test-make_dev.R +++ b/tests/testthat/test-make_dev.R @@ -61,9 +61,11 @@ test_that("test print_dev", { test_that("test browser_button", { withr::with_options( - c("golem.quiet" = FALSE),{ - output <- capture_output_lines(browser_button()) - }) + c("golem.quiet" = FALSE), + { + output <- capture_output_lines(browser_button()) + } + ) expect_true( grepl('actionButton\\("browser", "browser"\\)', output[2]) ) diff --git a/tests/testthat/test-test_helpers.R b/tests/testthat/test-test_helpers.R index 82aa04c2..307e7e3d 100644 --- a/tests/testthat/test-test_helpers.R +++ b/tests/testthat/test-test_helpers.R @@ -1,19 +1,23 @@ test_that("test expect_shinytag", { with_dir(pkg, { withr::with_options( - c("golem.quiet" = FALSE),{ + c("golem.quiet" = FALSE), + { expect_equal(capture_output(expect_shinytag(favicon("jean"))), "") expect_error(expect_shinytag("pierre")) - }) + } + ) }) }) test_that("test expect_shinytaglist", { with_dir(pkg, { withr::with_options( - c("golem.quiet" = FALSE),{ + c("golem.quiet" = FALSE), + { expect_equal(capture_output(expect_shinytaglist(shiny::tagList())), "") expect_error(expect_shinytaglist("test")) - }) + } + ) }) }) From f8461c227e07a8e3b80f2e1e2779734ca96365e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arthur=20Br=C3=A9ant?= <35060481+ArthurData@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:58:33 +0200 Subject: [PATCH 096/190] feat: remove {desc} as hard dep (#940) --- DESCRIPTION | 4 +-- NAMESPACE | 2 -- R/add_dockerfiles.R | 1 - R/bootstrap_desc.R | 40 ++++++++++++++++++++++++++++ R/create_golem.R | 5 ++++ R/desc.R | 3 +-- R/golem-yaml-set.R | 4 +-- R/reload.R | 2 +- README.Rmd | 7 +---- README.md | 7 +---- inst/mantests/build.R | 24 ++++++++--------- tests/testthat/helper-config.R | 4 ++- tests/testthat/test-desc.R | 1 + tests/testthat/test-use_recomended.R | 3 ++- 14 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 R/bootstrap_desc.R diff --git a/DESCRIPTION b/DESCRIPTION index b0011c5d..ac647cab 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -53,7 +53,6 @@ Imports: cli (>= 2.0.0), config, crayon, - desc, here, htmltools, rlang (>= 1.0.0), @@ -83,7 +82,8 @@ Suggests: attachment (>= 0.2.5), renv, usethis (>= 1.6.0), - fs + fs, + desc VignetteBuilder: knitr Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index 7cdfebe7..0369c546 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -96,8 +96,6 @@ importFrom(cli,cat_line) importFrom(cli,cat_rule) importFrom(cli,cli_alert_info) importFrom(config,get) -importFrom(desc,desc_get_deps) -importFrom(desc,description) importFrom(htmltools,htmlDependency) importFrom(rstudioapi,documentSaveAll) importFrom(rstudioapi,getSourceEditorContext) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 323eae56..b5632c95 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -47,7 +47,6 @@ talk_once <- function(.f, msg = "") { #' @export #' @rdname dockerfiles #' -#' @importFrom desc desc_get_deps #' @importFrom rstudioapi navigateToFile isAvailable hasFun #' #' @examples diff --git a/R/bootstrap_desc.R b/R/bootstrap_desc.R new file mode 100644 index 00000000..a91196df --- /dev/null +++ b/R/bootstrap_desc.R @@ -0,0 +1,40 @@ +# All the fns here check that {desc} is installed +# before doing anything. +check_desc_installed <- function() { + rlang::check_installed( + "desc", + reason = "to fill DESCRIPTION." + ) +} + +desc_description <- function(file) { + check_desc_installed() + desc::description$new( + file = file + ) +} + +desc_get <- function(keys) { + check_desc_installed() + desc::desc_get(keys) +} + +desc_get_version <- function() { + check_desc_installed() + desc::desc_get_version() +} + +desc_get_deps <- function(file = NULL) { + check_desc_installed() + desc::desc_get_deps(file) +} + +desc_get_field <- function(key) { + check_desc_installed() + desc::desc_get_field(key) +} + +desc_get_author <- function() { + check_desc_installed() + desc::desc_get_author() +} diff --git a/R/create_golem.R b/R/create_golem.R index 468f51ac..3c8a6f04 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -197,6 +197,11 @@ create_golem <- function( old <- setwd(path_to_golem) + + if (!requireNamespace("desc", quietly = TRUE)) { + check_desc_installed() + } # incase of {desc} not installed by {usethis} + usethis_use_latest_dependencies() # No .Rprofile for now diff --git a/R/desc.R b/R/desc.R index ffb68d8a..074ca81b 100644 --- a/R/desc.R +++ b/R/desc.R @@ -11,7 +11,6 @@ #' @param pkg_version The version of the package. Default is 0.0.0.9000 #' @param pkg Path to look for the DESCRIPTION. Default is `get_golem_wd()`. #' -#' @importFrom desc description #' @importFrom cli cat_bullet #' #' @export @@ -31,7 +30,7 @@ fill_desc <- function( ) { path <- fs_path_abs(pkg) - desc <- desc::description$new( + desc <- desc_description( file = fs_path(path, "DESCRIPTION") ) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 4e5c26c3..6326acad 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -50,7 +50,7 @@ set_golem_name <- function( ) # Changing in DESCRIPTION - desc <- desc::description$new( + desc <- desc_description( file = fs_path( path, "DESCRIPTION" @@ -84,7 +84,7 @@ set_golem_version <- function( talkative = talkative ) - desc <- desc::description$new( + desc <- desc_description( file = fs_path( path, "DESCRIPTION" diff --git a/R/reload.R b/R/reload.R index 2bb9e51f..759a9a48 100644 --- a/R/reload.R +++ b/R/reload.R @@ -34,7 +34,7 @@ detach_all_attached <- function() { check_name_consistency <- function(pkg) { old_dir <- setwd(pkg) - package_name <- desc::desc_get("Package") + package_name <- desc_get(keys = "Package") pth <- fs_path( pkg, "R", diff --git a/README.Rmd b/README.Rmd index 61060376..468a9f85 100644 --- a/README.Rmd +++ b/README.Rmd @@ -27,12 +27,7 @@ knitr::opts_chunk$set( ## About -You're reading the doc about version : - -```{r eval = TRUE} -desc::desc_get_version() -``` - +You're reading the doc about version : ``r as.data.frame(read.dcf("DESCRIPTION"))$Version `` ## Tool series diff --git a/README.md b/README.md index d241396b..d90de195 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,7 @@ shiny applications. ## About -You’re reading the doc about version : - -``` r -desc::desc_get_version() -#> [1] '0.3.3.9000' -``` +You’re reading the doc about version : `0.3.4.9002` ## Tool series diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 0cc9aa44..3e761bf1 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -69,7 +69,7 @@ install_local( ) golem::install_dev_deps( - force_install = TRUE, + force_install = TRUE, lib = temp_lib ) @@ -145,19 +145,19 @@ withr::with_tempdir({ file.exists("LICENSE") ) expect_true( - desc::desc_get("License") == "MIT + file LICENSE" + desc_get("License") == "MIT + file LICENSE" ) cat_ok() cli::cat_rule("Checking the DESCRIPTION is correct") expect_true( - desc::desc_get("Package") == "golemmetrics" + desc_get("Package") == "golemmetrics" ) expect_true( - desc::desc_get("Title") == "An Amazing Shiny App" + desc_get("Title") == "An Amazing Shiny App" ) expect_true( - all(desc::desc_get_deps()$package %in% c("config", "golem", "shiny")) + all(desc_get_deps()$package %in% c("config", "golem", "shiny")) ) cat_ok() @@ -210,31 +210,31 @@ withr::with_tempdir({ cli::cat_rule("checking package name") expect_equal( - desc::desc_get_field("Package"), + desc_get_field("Package"), "golemmetrics" ) cat_ok() cli::cat_rule("checking pkg_title name") expect_equal( - desc::desc_get_field("Title"), + desc_get_field("Title"), "A App with Metrics about 'Golem'" ) cat_ok() cli::cat_rule("checking package name") expect_equal( - desc::desc_get_field("Description"), + desc_get_field("Description"), "Read metrics about {golem}." ) cat_ok() cli::cat_rule("checking package name") expect_equal( - as.character(desc::desc_get_author()), + as.character(desc_get_author()), "Colin Fay [cre, aut]" ) cat_ok() cli::cat_rule("checking package version") expect_equal( - as.character(desc::desc_get_version()), + as.character(desc_get_version()), "0.0.0.9000" ) cat_ok() @@ -265,7 +265,7 @@ withr::with_tempdir({ # usethis::use_mit_license( "Golem User" ) expect_equal( - desc::desc_get_field("License"), + desc_get_field("License"), "MIT + file LICENSE" ) expect_true( @@ -321,7 +321,7 @@ withr::with_tempdir({ } usethis::use_package("cranlogs") expect_true( - "cranlogs" %in% desc::desc_get_deps()$package + "cranlogs" %in% desc_get_deps()$package ) cat_ok() diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 7c80580b..5105773b 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -84,7 +84,9 @@ withr::with_dir(pkg, { if (!file.exists(".here")) { here::set_here(path_to_golem) } - set_golem_options() + if (requireNamespace("desc", quietly = TRUE)) { + set_golem_options() + } usethis::proj_set(pkg) orig_test <- set_golem_wd( pkg = pkg diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 6b851eef..7561442f 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -1,5 +1,6 @@ test_that("desc works", { + testthat::skip_if_not_installed("desc") with_dir(pkg, { withr::with_options( c("golem.quiet" = FALSE), diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 738c7370..cb457b13 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -1,5 +1,6 @@ test_that("test use_recommended_deps", { testthat::skip_on_cran() + testthat::skip_if_not_installed("desc") with_dir(pkg, { packages <- c("shiny", "DT", "attempt", "glue", "golem", "htmltools") to_add <- c() @@ -11,7 +12,7 @@ test_that("test use_recommended_deps", { expect_warning( use_recommended_deps(recommended = to_add) ) - deps <- desc::desc_get_deps(file = "DESCRIPTION") + deps <- desc_get_deps(file = "DESCRIPTION") expect_true( all(to_add %in% deps$package) ) From 43a40f4f070bdd3fc3ccf6819bebb6c9467ccf61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arthur=20Br=C3=A9ant?= Date: Fri, 28 Oct 2022 17:26:20 +0200 Subject: [PATCH 097/190] feat: Create html template for maintenance mode Issue Add a maintenance mode ? #820 --- NAMESPACE | 2 + R/with_opt.R | 33 +++- inst/app/maintenance.html | 274 ++++++++++++++++++++++++++++++ man/maintenance_page.Rd | 14 ++ man/with_golem_options.Rd | 9 +- tests/testthat/test-maintenance.R | 4 + 6 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 inst/app/maintenance.html create mode 100644 man/maintenance_page.Rd create mode 100644 tests/testthat/test-maintenance.R diff --git a/NAMESPACE b/NAMESPACE index 0369c546..6848cce5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -52,6 +52,7 @@ export(invoke_js) export(is_running) export(js_handler_template) export(js_template) +export(maintenance_page) export(make_dev) export(message_dev) export(module_template) @@ -107,6 +108,7 @@ importFrom(rstudioapi,openProject) importFrom(rstudioapi,sourceMarkers) importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) +importFrom(shiny,htmlTemplate) importFrom(shiny,includeScript) importFrom(shiny,tags) importFrom(utils,capture.output) diff --git a/R/with_opt.R b/R/with_opt.R index 14af6d68..2da07dba 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -6,17 +6,25 @@ #' #' @param app the app object. #' @param golem_opts A list of Options to be added to the app +#' @param maintenance_page an html_document or a shiny tag list. By default is golem page #' @param print Whether or not to print the app. Default is to `FALSE`, which #' should be what you need 99.99% of the time In case you need to #' actively print the app object, you can set it to `TRUE`. #' #' @return a shiny.appObj object #' @export -with_golem_options <- function( - app, - golem_opts, - print = FALSE -) { +with_golem_options <- function(app, + golem_opts, + maintenance_page = golem::maintenance_page, + print = FALSE) { + + # Check if app is in maintenance + if (Sys.getenv("GOLEM_SET_MAINTENANCE_ACTIVE") == "TRUE") { + app <- shiny::shinyApp( + ui = maintenance_page, + server = function(input, output, session) {} + ) + } # Setting the running option set_golem_global( @@ -105,3 +113,18 @@ get_golem_options <- function(which = NULL) { getShinyOption("golem_options")[[which]] } } + +#' maintenance_page +#' +#' A default html page for maintenance mode +#' +#' @importFrom shiny htmlTemplate +#' +#' @return an html_document +#' +#' @export +maintenance_page <- function() { + shiny::htmlTemplate( + filename = system.file("app", "maintenance.html", package = "golem") + ) +} diff --git a/inst/app/maintenance.html b/inst/app/maintenance.html new file mode 100644 index 00000000..6566c4ca --- /dev/null +++ b/inst/app/maintenance.html @@ -0,0 +1,274 @@ + + + + + + + Coming soon... + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/man/maintenance_page.Rd b/man/maintenance_page.Rd new file mode 100644 index 00000000..148e3932 --- /dev/null +++ b/man/maintenance_page.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{maintenance_page} +\alias{maintenance_page} +\title{maintenance_page} +\usage{ +maintenance_page() +} +\value{ +an html_document +} +\description{ +A default html page for maintenance mode +} diff --git a/man/with_golem_options.Rd b/man/with_golem_options.Rd index 21bb7303..05b595c9 100644 --- a/man/with_golem_options.Rd +++ b/man/with_golem_options.Rd @@ -4,13 +4,20 @@ \alias{with_golem_options} \title{Add Golem options to a Shiny App} \usage{ -with_golem_options(app, golem_opts, print = FALSE) +with_golem_options( + app, + golem_opts, + maintenance_page = golem::maintenance_page, + print = FALSE +) } \arguments{ \item{app}{the app object.} \item{golem_opts}{A list of Options to be added to the app} +\item{maintenance_page}{an html_document or a shiny tag list. By default is golem page} + \item{print}{Whether or not to print the app. Default is to \code{FALSE}, which should be what you need 99.99\% of the time In case you need to actively print the app object, you can set it to \code{TRUE}.} diff --git a/tests/testthat/test-maintenance.R b/tests/testthat/test-maintenance.R new file mode 100644 index 00000000..8464e04f --- /dev/null +++ b/tests/testthat/test-maintenance.R @@ -0,0 +1,4 @@ +test_that("test maintenance_page", { + html <- maintenance_page() + expect_true(inherits(html, c("html_document", "shiny.tag.list", "list"))) +}) From 568a624a172137f1bb04b5cd211bd87662f0ba69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arthur=20Br=C3=A9ant?= Date: Mon, 31 Oct 2022 23:55:27 +0100 Subject: [PATCH 098/190] feat: Create documentation for maintenance page --- vignettes/f_extending_golem.Rmd | 88 +++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index 5f14c655..f16deb7d 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -198,6 +198,94 @@ my_tmpl <- function(path, ...) { golem::add_css_file(name = "custom", template = my_tmpl) ``` +## Use a maintenance module +## What it is +When you need to pause your application for some reasons: breaking changes, database update, etc., you can use a __maintenance mode__. +During the maintenance period, your application will be paused and a warning message will be displayed to your users. +{golem} comes with a default maintenance page. But, you can replace it with you own maintenance page. + +## How to set the maintenance mode + +To use a maintenance mode, you have to create a an environment variable `GOLEM_SET_MAINTENANCE_ACTIVE`. + +```{r} +usethis::edit_r_environ(scope = "project") +``` + +Then create the environment variable: + +```{r} +GOLEM_SET_MAINTENANCE_ACTIVE=TRUE +``` + +To see the effects, restart your session: + +```{r} +rstudioapi::restartSession() +``` + +## The maintenance page + +By default, {golem} comes with a default maintenance page. + +You can override it and use your own custom maintenance page. + +You have to pass a `html_document` or a `shiny tag list`. For that, go to to your `run_app.R` : + +```{r} +rstudioapi::navigateToFile("R/run_app.R") +``` + +Then, use the `maintenance_page` parameter from `with_golem_options`: + +```{r} +run_app <- function(onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ...) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server, + onStart = onStart, + options = options, + enableBookmarking = enableBookmarking, + uiPattern = uiPattern + ), + golem_opts = list(...), + maintenance_page = shiny::htmlTemplate(filename = app_sys("your_custom.html")) + ) +} +``` + +or: + +```{r} +run_app <- function(onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ...) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server, + onStart = onStart, + options = options, + enableBookmarking = enableBookmarking, + uiPattern = uiPattern + ), + golem_opts = list(...), + maintenance_page = tagList( + fluidRow( + h1("Under maintenance"), + span("Coming soon...") + ) + ) + ) +} +``` \ No newline at end of file From 32673239f7d898bfa9fa44ab8c5766e6610393fa Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 7 Nov 2022 13:52:56 +0100 Subject: [PATCH 099/190] refactor: changed the text on screen for maintenance page --- inst/app/maintenance.html | 522 +++++++++++++++++++------------------- 1 file changed, 258 insertions(+), 264 deletions(-) diff --git a/inst/app/maintenance.html b/inst/app/maintenance.html index 6566c4ca..ed996309 100644 --- a/inst/app/maintenance.html +++ b/inst/app/maintenance.html @@ -1,274 +1,268 @@ - - - - Coming soon... - + + + + Under Maintenance + -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file From f32f75334e390cf82987d4313d85fc4e33520b87 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 7 Nov 2022 13:53:16 +0100 Subject: [PATCH 100/190] refactor: renamed env var to GOLEM_MAINTENANCE_ACTIVE --- R/with_opt.R | 30 ++++++++++++++++++------------ man/with_golem_options.Rd | 8 ++++---- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/R/with_opt.R b/R/with_opt.R index 2da07dba..3b33a40b 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -5,21 +5,23 @@ #' launch. #' #' @param app the app object. -#' @param golem_opts A list of Options to be added to the app -#' @param maintenance_page an html_document or a shiny tag list. By default is golem page +#' @param golem_opts A list of options to be added to the app +#' @param maintenance_page an html_document or a shiny tag list. Default is golem template. #' @param print Whether or not to print the app. Default is to `FALSE`, which -#' should be what you need 99.99% of the time In case you need to -#' actively print the app object, you can set it to `TRUE`. +#' should be what you need 99.99% of the time. In case you need to +#' actively print() the app object, you can set it to `TRUE`. #' #' @return a shiny.appObj object #' @export -with_golem_options <- function(app, - golem_opts, - maintenance_page = golem::maintenance_page, - print = FALSE) { +with_golem_options <- function( + app, + golem_opts, + maintenance_page = golem::maintenance_page, + print = FALSE +) { # Check if app is in maintenance - if (Sys.getenv("GOLEM_SET_MAINTENANCE_ACTIVE") == "TRUE") { + if (Sys.getenv("GOLEM_MAINTENANCE_ACTIVE", "FALSE") == "TRUE") { app <- shiny::shinyApp( ui = maintenance_page, server = function(input, output, session) {} @@ -49,9 +51,9 @@ with_golem_options <- function(app, print <- FALSE } - # Almost all cases will be ok with explicitely printing the + # Almost all cases will be ok with not explicitely printing the # application object, but for corner cases like direct shinyApp - # object manipulation, this feature can be turned off + # object manipulation, this feature can be turned on if (print) { print(app) } else { @@ -125,6 +127,10 @@ get_golem_options <- function(which = NULL) { #' @export maintenance_page <- function() { shiny::htmlTemplate( - filename = system.file("app", "maintenance.html", package = "golem") + filename = system.file( + "app", + "maintenance.html", + package = "golem" + ) ) } diff --git a/man/with_golem_options.Rd b/man/with_golem_options.Rd index 05b595c9..ad35ab25 100644 --- a/man/with_golem_options.Rd +++ b/man/with_golem_options.Rd @@ -14,13 +14,13 @@ with_golem_options( \arguments{ \item{app}{the app object.} -\item{golem_opts}{A list of Options to be added to the app} +\item{golem_opts}{A list of options to be added to the app} -\item{maintenance_page}{an html_document or a shiny tag list. By default is golem page} +\item{maintenance_page}{an html_document or a shiny tag list. Default is golem template.} \item{print}{Whether or not to print the app. Default is to \code{FALSE}, which -should be what you need 99.99\% of the time In case you need to -actively print the app object, you can set it to \code{TRUE}.} +should be what you need 99.99\% of the time. In case you need to +actively print() the app object, you can set it to \code{TRUE}.} } \value{ a shiny.appObj object From cc608c97b1a53b5f5203b4a5cf1ee2b625312cfd Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 7 Nov 2022 13:53:29 +0100 Subject: [PATCH 101/190] doc: some vignette rewritting and additions --- vignettes/f_extending_golem.Rmd | 84 ++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index f16deb7d..bb623ca5 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -198,55 +198,55 @@ my_tmpl <- function(path, ...) { golem::add_css_file(name = "custom", template = my_tmpl) ``` -## Use a maintenance module +## Turn on the maintenance mode ## What it is -When you need to pause your application for some reasons: breaking changes, database update, etc., you can use a __maintenance mode__. -During the maintenance period, your application will be paused and a warning message will be displayed to your users. +From time to time, you need your application to be unavailbel: database update, API changes, etc. +In order to keep your app running but make it unavailable, you can use a __maintenance mode__. +When this maintenance mode is turned on, your application will be paused and a specific page will be displayed to your users. -{golem} comes with a default maintenance page. But, you can replace it with you own maintenance page. +`{golem}` comes with a default maintenance page, and you can replace it with you own page. ## How to set the maintenance mode -To use a maintenance mode, you have to create a an environment variable `GOLEM_SET_MAINTENANCE_ACTIVE`. +The maintenance mode will be turned on whenever the R process detects that the `GOLEM_MAINTENANCE_ACTIVE` environment variable is set to TRUE. -```{r} -usethis::edit_r_environ(scope = "project") -``` - -Then create the environment variable: +To visualise the maintenance page locally, you can run the following: -```{r} -GOLEM_SET_MAINTENANCE_ACTIVE=TRUE +```{r eval = FALSE} +withr::with_envvar( + c("GOLEM_MAINTENANCE_ACTIVE" = TRUE), + { + golem::run_dev() + } +) ``` -To see the effects, restart your session: -```{r} -rstudioapi::restartSession() -``` +If you're deploying on Posit Connect, you can set this variable in the setup panel. -## The maintenance page +If in command line, you can also do -By default, {golem} comes with a default maintenance page. +``` +export GOLEM_MAINTENANCE_ACTIVE=TRUE && Rscript -e "mypkg::run_app()" +``` -You can override it and use your own custom maintenance page. -You have to pass a `html_document` or a `shiny tag list`. For that, go to to your `run_app.R` : +## The maintenance page -```{r} -rstudioapi::navigateToFile("R/run_app.R") -``` +`{golem}` comes with a default maintenance page, but you can override it and use your own custom page. -Then, use the `maintenance_page` parameter from `with_golem_options`: +In order to use your own page, you need to pass either an `html_document` or a `tagList` to the `with_golem_options` function in `run_app.R`: ```{r} -run_app <- function(onStart = NULL, - options = list(), - enableBookmarking = NULL, - uiPattern = "/", - ...) { +run_app <- function( + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ... +) { with_golem_options( app = shinyApp( ui = app_ui, @@ -257,7 +257,12 @@ run_app <- function(onStart = NULL, uiPattern = uiPattern ), golem_opts = list(...), - maintenance_page = shiny::htmlTemplate(filename = app_sys("your_custom.html")) + maintenance_page = tagList( + fluidRow( + h1("Under maintenance"), + span("Coming soon...") + ) + ) ) } ``` @@ -265,11 +270,13 @@ run_app <- function(onStart = NULL, or: ```{r} -run_app <- function(onStart = NULL, - options = list(), - enableBookmarking = NULL, - uiPattern = "/", - ...) { +run_app <- function( + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ... +) { with_golem_options( app = shinyApp( ui = app_ui, @@ -280,10 +287,9 @@ run_app <- function(onStart = NULL, uiPattern = uiPattern ), golem_opts = list(...), - maintenance_page = tagList( - fluidRow( - h1("Under maintenance"), - span("Coming soon...") + maintenance_page = shiny::htmlTemplate( + filename = app_sys( + "custom_maintenance_page.html" ) ) ) From 89b40cb568ed2d8edd75909cbbf7dc693f25b82f Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Wed, 7 Dec 2022 13:19:03 +0100 Subject: [PATCH 102/190] Fix 944 and correct CI * fix #944 force lowercase for package name in Dockerfile * check_is_installed is back again instead of fs_file_copy for dockerfile creation * set version to 0.3.5.9001 --- DESCRIPTION | 2 +- NEWS.md | 3 +++ R/add_dockerfiles_renv.R | 20 +++++++++---------- R/utils.R | 42 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index c65fbc04..59c3a177 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.5.9000 +Version: 0.3.5.9001 Authors@R: c(person(given = "Colin", family = "Fay", diff --git a/NEWS.md b/NEWS.md index 3f134dd0..fa2e782a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,9 @@ # 0.3.5.9000+ (dev version) +add_dockerfile_with_renv now works well with uppercase in package name + + # golem 0.3.5 Update in the tests for CRAN (commented a test that made new version of testthat fail). diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index b9c59503..b60fd4cd 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -47,12 +47,12 @@ add_dockerfile_with_renv_ <- function( ) } - fs_file_copy( - path = lockfile, - new_path = output_dir, - overwrite = TRUE - ) - + # fs_file_copy( + # path = lockfile, + # new_path = output_dir, + # overwrite = TRUE + # ) + file.copy(from = lockfile, to = output_dir) socle <- dockerfiler::dock_from_renv( lockfile = lockfile, distro = distro, @@ -66,7 +66,7 @@ add_dockerfile_with_renv_ <- function( socle$write(as = file.path(output_dir, "Dockerfile_base")) - my_dock <- dockerfiler::Dockerfile$new(FROM = paste0(golem::get_golem_name(), "_base")) + my_dock <- dockerfiler::Dockerfile$new(FROM = tolower(paste0(golem::get_golem_name(), "_base"))) my_dock$COPY("renv.lock.prod", "renv.lock") @@ -191,11 +191,11 @@ add_dockerfile_with_renv <- function( docker build -f Dockerfile --progress=plain -t %s . docker run -p %s:%s %s # then go to 127.0.0.1:%s", - paste0(golem::get_golem_name(), "_base"), - paste0(golem::get_golem_name(), ":latest"), + tolower(paste0(golem::get_golem_name(), "_base")), + tolower( paste0(golem::get_golem_name(), ":latest")), port, port, - paste0(golem::get_golem_name(), ":latest"), + tolower(paste0(golem::get_golem_name(), ":latest")), port ) diff --git a/R/utils.R b/R/utils.R index 2246d5ea..794b1ec1 100644 --- a/R/utils.R +++ b/R/utils.R @@ -386,6 +386,48 @@ yesno <- function(...) { menu(c("Yes", "No")) == 1 } + +# Checking that a package is installed +check_is_installed <- function( + pak, + ... +) { + if ( + !requireNamespace(pak, ..., quietly = TRUE) + ) { + stop( + sprintf( + "The {%s} package is required to run this function.\nYou can install it with `install.packages('%s')`.", + pak, + pak + ), + call. = FALSE + ) + } +} + +required_version <- function( + pak, + version +) { + if ( + utils::packageVersion(pak) < version + ) { + stop( + sprintf( + "This function require the version '%s' of the {%s} package.\nYou can update with `install.packages('%s')`.", + version, + pak, + pak + ), + call. = FALSE + ) + } +} + + + + add_sass_code <- function(where, dir, name) { if (fs_file_exists(where)) { if (fs_file_exists("dev/run_dev.R")) { From acc054bfd24a8dfbc4b9b2ce6ca49682dfe443ea Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Wed, 7 Dec 2022 13:39:29 +0100 Subject: [PATCH 103/190] Fix 944 and correct CI (#947) * fix #944 force lowercase for package name in Dockerfile * check_is_installed is back again instead of fs_file_copy for dockerfile creation * set version to 0.3.5.9001 --- NEWS.md | 4 ++++ R/add_dockerfiles_renv.R | 23 ++++++++-------------- R/utils.R | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index d9ccf69c..e94bc37b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,9 @@ # 0.3.5.9000+ (dev version) + +add_dockerfile_with_renv now works well with uppercase in package name + ## Soft deprecated ## Hard deprecated @@ -30,6 +33,7 @@ ## Internal changes + # golem 0.3.5 Update in the tests for CRAN (commented a test that made new version of testthat fail). diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index bed36cfa..8e146487 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -37,13 +37,9 @@ add_dockerfile_with_renv_ <- function( ) } - fs_file_copy( - path = lockfile, - new_path = output_dir, - overwrite = TRUE - ) + file.copy(from = lockfile, to = output_dir) + socle <- dockerfiler::dock_from_renv( - socle <- dockerfiler_dock_from_renv( lockfile = lockfile, distro = distro, FROM = FROM, @@ -56,12 +52,9 @@ add_dockerfile_with_renv_ <- function( socle$write(as = file.path(output_dir, "Dockerfile_base")) - my_dock <- dockerfiler_Dockerfile()$new( - FROM = paste0( - golem::get_golem_name(), - "_base" - ) - ) + + my_dock <- dockerfiler::Dockerfile$new(FROM = tolower(paste0(golem::get_golem_name(), "_base"))) + my_dock$COPY("renv.lock.prod", "renv.lock") @@ -186,11 +179,11 @@ add_dockerfile_with_renv <- function( docker build -f Dockerfile --progress=plain -t %s . docker run -p %s:%s %s # then go to 127.0.0.1:%s", - paste0(golem::get_golem_name(), "_base"), - paste0(golem::get_golem_name(), ":latest"), + tolower(paste0(golem::get_golem_name(), "_base")), + tolower( paste0(golem::get_golem_name(), ":latest")), port, port, - paste0(golem::get_golem_name(), ":latest"), + tolower(paste0(golem::get_golem_name(), ":latest")), port ) diff --git a/R/utils.R b/R/utils.R index a461e9a9..4b021a16 100644 --- a/R/utils.R +++ b/R/utils.R @@ -414,6 +414,48 @@ yesno <- function(...) { menu(c("Yes", "No")) == 1 } + +# Checking that a package is installed +check_is_installed <- function( + pak, + ... +) { + if ( + !requireNamespace(pak, ..., quietly = TRUE) + ) { + stop( + sprintf( + "The {%s} package is required to run this function.\nYou can install it with `install.packages('%s')`.", + pak, + pak + ), + call. = FALSE + ) + } +} + +required_version <- function( + pak, + version +) { + if ( + utils::packageVersion(pak) < version + ) { + stop( + sprintf( + "This function require the version '%s' of the {%s} package.\nYou can update with `install.packages('%s')`.", + version, + pak, + pak + ), + call. = FALSE + ) + } +} + + + + add_sass_code <- function(where, dir, name) { if (fs_file_exists(where)) { if (fs_file_exists("dev/run_dev.R")) { From 5c756186fe02b8ad1ee3fcfc09c2e2928453e91d Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Wed, 7 Dec 2022 15:01:05 +0100 Subject: [PATCH 104/190] fix dev branch (#948) * fix allow pkgload no to be installed * fix document_and_reload rd * chore update NEWS and bump version * fix force repos in test to avoid "trying to use CRAN without setting a mirror" --- DESCRIPTION | 4 ++-- NEWS.md | 5 +---- R/bootstrap_pkgload.R | 5 ++++- tests/testthat/test-install_dev_deps.R | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ac647cab..ac0ed4a3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.5.9003 +Version: 0.3.5.9004 Authors@R: c(person(given = "Colin", family = "Fay", @@ -90,4 +90,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.1 +RoxygenNote: 7.2.2 diff --git a/NEWS.md b/NEWS.md index e94bc37b..63b3891c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,9 +2,6 @@ # 0.3.5.9000+ (dev version) - -add_dockerfile_with_renv now works well with uppercase in package name - ## Soft deprecated ## Hard deprecated @@ -29,7 +26,7 @@ add_dockerfile_with_renv now works well with uppercase in package name + The setting of the config file has been unified so that we are sure to keep the !expr in `golem_set_wd()`, and the codebase has been simplified for this (#709). + The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) + The typo in `install.packages()` in 02_dev.R has been corrected (@asiripanich) - ++ `add_dockerfile_with_renv()` now works well with uppercase in package name ## Internal changes diff --git a/R/bootstrap_pkgload.R b/R/bootstrap_pkgload.R index c2666a5a..7429f31b 100644 --- a/R/bootstrap_pkgload.R +++ b/R/bootstrap_pkgload.R @@ -7,7 +7,7 @@ check_pkgload_installed <- function() { ) } -uses_testthat <- getFromNamespace("uses_testthat", "pkgload") + pkgload_load_all <- function( @@ -24,6 +24,9 @@ pkgload_load_all <- function( warn_conflicts = TRUE ) { check_roxygen2_installed() + check_pkgload_installed() + + uses_testthat <- getFromNamespace("uses_testthat", "pkgload") pkgload::load_all( path = path, reset = reset, diff --git a/tests/testthat/test-install_dev_deps.R b/tests/testthat/test-install_dev_deps.R index 8baa1047..8eebb5fb 100644 --- a/tests/testthat/test-install_dev_deps.R +++ b/tests/testthat/test-install_dev_deps.R @@ -1,6 +1,7 @@ test_that("install_dev_deps works", { install_dev_deps( - force_install = TRUE + force_install = TRUE, + repos = "https://cran.rstudio.com" ) paks <- unique( c( From 7a9debb1fab42368ee3b50803483416180508c6f Mon Sep 17 00:00:00 2001 From: vincent Date: Thu, 8 Dec 2022 11:25:14 +0100 Subject: [PATCH 105/190] chore hotfix pkgdown --- _pkgdown.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index a2345294..4bac04b8 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -25,7 +25,7 @@ reference: - title: Create a Dockerfile desc: | - Build a container containing your Shiny App. `add_dockerfile()` creates a "classical" Dockerfile, + Build a container containing your Shiny App. `add_dockerfile()` creates a "classical" Dockerfile, while `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` creates platform specific Dockerfile. contents: - add_dockerfile @@ -34,7 +34,7 @@ reference: - title: Use files desc: | - `use_external_js_file` and `use_external_css_file`download files from external sources and install + `use_external_js_file` and `use_external_css_file`download files from external sources and install them inside the appropriate directory. `use_utils_ui` copies the golem_utils_ui.R to the R folder and `use_utils_server` copies the golem_utils_server.R to the R folder. contents: @@ -88,7 +88,7 @@ reference: - title: Addins desc: | - `insert_ns()` takes a selected character vector and wrap it in `ns()`. The series of `go_to_*()` + `insert_ns()` takes a selected character vector and wrap it in `ns()`. The series of `go_to_*()` addins help you go to common files used in developing a {golem} application. contents: - insert_ns @@ -102,7 +102,7 @@ reference: - title: JavaScript interaction functions desc: | - `activate_js` is used in your UI to insert directly the JavaScript functions contained in golem. + `activate_js` is used in your UI to insert directly the JavaScript functions contained in golem. These functions can be called from the server with `invoke_js`. `invoke_js` can also be used to launch any JS function created inside a Shiny JavaScript handler. contents: @@ -111,7 +111,7 @@ reference: - title: Options desc: | - Set and get a series of options to be used with `{golem}`. These options are found inside the + Set and get a series of options to be used with `{golem}`. These options are found inside the `golem-config.yml` file, found in most cases inside the inst folder. contents: - set_golem_options @@ -126,9 +126,11 @@ reference: desc: | `use_recommended_deps` adds shiny, DT, attempt, glue, golem, htmltools to dependencies `use_recommended_tests` adds a test folder and copy the golem tests + `install_dev_deps` install all packages needed for golem developpement contents: - use_recommended_deps - use_recommended_tests + - install_dev_deps - title: Misc contents: @@ -157,3 +159,5 @@ reference: - sanity_check - js_handler_template - use_module_test + - get_current_config + - pkg_name From 63ac8523ba48af432b129c421e1595807e487177 Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Fri, 9 Dec 2022 13:39:49 +0100 Subject: [PATCH 106/190] draft : 950 without cli (#951) * remove cli and crayon from hard deps --- DESCRIPTION | 60 +++++++-------------- NAMESPACE | 4 -- NEWS.md | 2 +- R/add_dockerfiles.R | 12 ++--- R/add_files.R | 2 - R/add_rstudio_files.R | 5 +- R/boostrap_cli.R | 38 ++++++++++++++ R/boostrap_crayon.R | 15 ++++++ R/browser_button.R | 26 ++++----- R/create_golem.R | 17 +++--- R/desc.R | 3 +- R/disable_autoload.R | 2 +- R/enable_roxygenize.R | 4 +- R/golem-yaml-utils.R | 2 +- R/install_dev_deps.R | 19 +++---- R/modules_fn.R | 1 - R/reload.R | 4 +- R/set_golem_options.R | 4 +- R/use_favicon.R | 2 +- R/use_files.R | 1 - R/use_utils.R | 1 - R/utils.R | 93 ++++++--------------------------- _pkgdown.yml | 14 +++-- vignettes/f_extending_golem.Rmd | 4 +- 24 files changed, 147 insertions(+), 188 deletions(-) create mode 100644 R/boostrap_cli.R create mode 100644 R/boostrap_crayon.R diff --git a/DESCRIPTION b/DESCRIPTION index ac0ed4a3..5c5fcc07 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,45 +1,21 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.5.9004 -Authors@R: - c(person(given = "Colin", - family = "Fay", - role = c("cre", "aut"), - email = "contact@colinfay.me", - comment = c(ORCID = "0000-0001-7343-1846")), - person(given = "Vincent", - family = "Guyader", - role = "aut", - email = "vincent@thinkr.fr", - comment = c(ORCID = "0000-0003-0671-9270", "previous maintainer")), - person(given = "Sébastien", - family = "Rochette", - role = "aut", - email = "sebastien@thinkr.fr", - comment = c(ORCID = "0000-0002-1565-9313")), - person(given = "Cervan", - family = "Girard", - role = "aut", - email = "cervan@thinkr.fr", - comment = c(ORCID = "0000-0002-4816-4624")), - person(given = "Novica", - family = "Nakov", - role = "ctb", - email = "nnovica@gmail.com"), - person(given = "David", - family = "Granjon", - role = "ctb", - email = "dgranjon@ymail.com"), - person(given = "Arthur", - family = "Bréant", - role = "ctb", - email = "arthur@thinkr.fr"), - person(given = "Antoine", - family = "Languillaume", - role = "ctb", - email = "antoine@thinkr.fr"), - person(given = "ThinkR", - role = "cph")) +Version: 0.3.5.9005 +Authors@R: c( + person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), + comment = c(ORCID = "0000-0001-7343-1846")), + person("Vincent", "Guyader", , "vincent@thinkr.fr", role = "aut", + comment = c(ORCID = "0000-0003-0671-9270", "previous maintainer")), + person("Sébastien", "Rochette", , "sebastien@thinkr.fr", role = "aut", + comment = c(ORCID = "0000-0002-1565-9313")), + person("Cervan", "Girard", , "cervan@thinkr.fr", role = "aut", + comment = c(ORCID = "0000-0002-4816-4624")), + person("Novica", "Nakov", , "nnovica@gmail.com", role = "ctb"), + person("David", "Granjon", , "dgranjon@ymail.com", role = "ctb"), + person("Arthur", "Bréant", , "arthur@thinkr.fr", role = "ctb"), + person("Antoine", "Languillaume", , "antoine@thinkr.fr", role = "ctb"), + person("ThinkR", role = "cph") + ) Description: An opinionated framework for building a production-ready 'Shiny' application. This package contains a series of tools for building a robust 'Shiny' application from start to finish. @@ -50,9 +26,7 @@ Depends: R (>= 3.0) Imports: attempt (>= 0.3.0), - cli (>= 2.0.0), config, - crayon, here, htmltools, rlang (>= 1.0.0), @@ -62,6 +36,8 @@ Imports: yaml Suggests: covr, + cli (>= 2.0.0), + crayon, devtools, dockerfiler (>= 0.2.0), knitr, diff --git a/NAMESPACE b/NAMESPACE index 6848cce5..de7f62cb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -92,10 +92,6 @@ importFrom(attempt,is_try_error) importFrom(attempt,stop_if) importFrom(attempt,stop_if_not) importFrom(attempt,without_warning) -importFrom(cli,cat_bullet) -importFrom(cli,cat_line) -importFrom(cli,cat_rule) -importFrom(cli,cli_alert_info) importFrom(config,get) importFrom(htmltools,htmlDependency) importFrom(rstudioapi,documentSaveAll) diff --git a/NEWS.md b/NEWS.md index 63b3891c..b565d0e9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,7 +12,7 @@ ## New features / user visible changes -+ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}`, `{usethis}` ++ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}`, `{usethis}`, `{cli}`, `{crayon}` + Soft dependency check is now done via `rlang::check_installed()` (#835) + `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. + `{golem}` now depends on `{rlang}` version >= 1.0.0 diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index b5632c95..bb1e915a 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -388,18 +388,18 @@ add_dockerfile_heroku_ <- talk_once( ) ) - cat_rule("From your command line, run:") - cat_line("heroku container:login") - cat_line( + cli_cat_rule("From your command line, run:") + cli_cat_line("heroku container:login") + cli_cat_line( sprintf("heroku create %s", apps_h) ) - cat_line( + cli_cat_line( sprintf("heroku container:push web --app %s", apps_h) ) - cat_line( + cli_cat_line( sprintf("heroku container:release web --app %s", apps_h) ) - cat_line( + cli_cat_line( sprintf("heroku open --app %s", apps_h) ) cat_red_bullet("Be sure to have the heroku CLI installed.") diff --git a/R/add_files.R b/R/add_files.R index 1b964d21..663cdd2d 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -22,7 +22,6 @@ #' @export #' @rdname add_files #' @importFrom attempt stop_if -#' @importFrom cli cat_bullet #' @importFrom utils file.edit #' #' @note `add_ui_server_files` will be deprecated in future version of `{golem}` @@ -452,7 +451,6 @@ add_css_file <- function( #' @export #' @rdname add_files -#' @importFrom cli cli_alert_info add_sass_file <- function( name, pkg = get_golem_wd(), diff --git a/R/add_rstudio_files.R b/R/add_rstudio_files.R index 44955959..50631f0b 100644 --- a/R/add_rstudio_files.R +++ b/R/add_rstudio_files.R @@ -1,5 +1,4 @@ #' @importFrom utils capture.output -#' @importFrom cli cat_bullet add_rstudio_files <- function( pkg, open, @@ -47,8 +46,8 @@ add_rstudio_files <- function( usethis_use_package("pkgload") cat_created(where) - cat_line("To deploy, run:") - cat_bullet(darkgrey("rsconnect::deployApp()\n")) + cli_cat_line("To deploy, run:") + cli_cat_bullet(crayon_darkgrey("rsconnect::deployApp()\n")) cat_red_bullet( sprintf( "Note that you'll need to upload the whole package to %s", diff --git a/R/boostrap_cli.R b/R/boostrap_cli.R new file mode 100644 index 00000000..c9f6da44 --- /dev/null +++ b/R/boostrap_cli.R @@ -0,0 +1,38 @@ +# All the fns here check that {cli} is installed +# before doing anything. +check_cli_installed <- function(reason = "to have attractive command line interfaces with {golem}.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`.") { + rlang::check_installed( + "cli", + version = "2.0.0", + reason = reason + ) +} + +cli_cat_bullet <- function(...) { + check_cli_installed() + cli::cat_bullet(...) +} + + +cli_cat_line <- function(...) { + check_cli_installed() + + do_if_unquiet({ + cli::cat_line(...) + }) +} + +cli_cat_rule <- function(...) { + check_cli_installed() + + do_if_unquiet({ + cli::cat_rule( + ... + ) + }) + + +} + + + diff --git a/R/boostrap_crayon.R b/R/boostrap_crayon.R new file mode 100644 index 00000000..dda3a842 --- /dev/null +++ b/R/boostrap_crayon.R @@ -0,0 +1,15 @@ +# All the fns here check that {cli} is installed +# before doing anything. +check_crayon_installed <- function(reason = "to have attractive command line interfaces with {golem}.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`.") { + rlang::check_installed( + "crayon", + reason = reason + ) +} + + +# from usethis https://github.com/r-lib/usethis/ +crayon_darkgrey <- function(x) { + check_crayon_installed() + x <- crayon::make_style("darkgrey")(x) +} diff --git a/R/browser_button.R b/R/browser_button.R index 55e11776..4e3bff4c 100644 --- a/R/browser_button.R +++ b/R/browser_button.R @@ -6,18 +6,18 @@ #' Prints the code to the console. #' @export #' -#' @importFrom cli cat_rule cat_line + browser_button <- function() { - cat_rule("To be copied in your UI") - cat_line(darkgrey('actionButton("browser", "browser"),')) - cat_line(darkgrey('tags$script("$(\'#browser\').hide();")')) - cat_line() - cat_rule("To be copied in your server") - cat_line(darkgrey("observeEvent(input$browser,{")) - cat_line(darkgrey(" browser()")) - cat_line(darkgrey("})")) - cat_line() - cat_line("By default, this button will be hidden.") - cat_line("To show it, open your web browser JavaScript console") - cat_line("And run $('#browser').show();") + cli_cat_rule("To be copied in your UI") + cli_cat_line(crayon_darkgrey('actionButton("browser", "browser"),')) + cli_cat_line(crayon_darkgrey('tags$script("$(\'#browser\').hide();")')) + cli_cat_line() + cli_cat_rule("To be copied in your server") + cli_cat_line(crayon_darkgrey("observeEvent(input$browser,{")) + cli_cat_line(crayon_darkgrey(" browser()")) + cli_cat_line(crayon_darkgrey("})")) + cli_cat_line() + cli_cat_line("By default, this button will be hidden.") + cli_cat_line("To show it, open your web browser JavaScript console") + cli_cat_line("And run $('#browser').show();") } diff --git a/R/create_golem.R b/R/create_golem.R index 3c8a6f04..76d421e3 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -54,7 +54,6 @@ replace_package_name <- function( #' For compatibility issue, this function turns `options(shiny.autoload.r)` #' to `FALSE`. See https://github.com/ThinkR-open/golem/issues/468 for more background. #' -#' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable openProject hasFun #' @importFrom yaml write_yaml @@ -79,7 +78,7 @@ create_golem <- function( ) if (check_name) { - cat_rule("Checking package name") + cli_cat_rule("Checking package name") rlang::check_installed( "usethis", version = "1.6.0", @@ -108,7 +107,7 @@ create_golem <- function( cat_red_bullet("Overwriting existing project.") } } else { - cat_rule("Creating dir") + cli_cat_rule("Creating dir") usethis_create_project( path = path_to_golem, open = FALSE @@ -120,7 +119,7 @@ create_golem <- function( } - cat_rule("Copying package skeleton") + cli_cat_rule("Copying package skeleton") from <- golem_sys("shinyexample") # Copy over whole directory @@ -148,7 +147,7 @@ create_golem <- function( old <- setwd(path_to_golem) - cat_rule("Running project hook function") + cli_cat_rule("Running project hook function") # TODO fix # for some weird reason test() fails here when using golem::create_golem @@ -182,7 +181,7 @@ create_golem <- function( if (isTRUE(with_git)) { - cat_rule("Initializing git repository") + cli_cat_rule("Initializing git repository") git_output <- system( command = paste("git init", path_to_golem), ignore.stdout = TRUE, @@ -205,7 +204,7 @@ create_golem <- function( usethis_use_latest_dependencies() # No .Rprofile for now - # cat_rule("Appending .Rprofile") + # cli_cat_rule("Appending .Rprofile") # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) # write("home_profile <- file.path(", ".Rprofile", append = TRUE) # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) @@ -222,9 +221,9 @@ create_golem <- function( setwd(old) - cat_rule("Done") + cli_cat_rule("Done") - cat_line( + cli_cat_line( paste0( "A new golem named ", package_name, diff --git a/R/desc.R b/R/desc.R index 074ca81b..c939c4a2 100644 --- a/R/desc.R +++ b/R/desc.R @@ -11,7 +11,6 @@ #' @param pkg_version The version of the package. Default is 0.0.0.9000 #' @param pkg Path to look for the DESCRIPTION. Default is `get_golem_wd()`. #' -#' @importFrom cli cat_bullet #' #' @export #' @@ -108,7 +107,7 @@ fill_desc <- function( file = "DESCRIPTION" ) - cat_bullet( + cli_cat_bullet( "DESCRIPTION file modified", bullet = "tick", bullet_col = "green" diff --git a/R/disable_autoload.R b/R/disable_autoload.R index a984b02c..9414ada2 100644 --- a/R/disable_autoload.R +++ b/R/disable_autoload.R @@ -20,7 +20,7 @@ disable_autoload <- function(pkg = get_golem_wd()) { "_disable_autoload.R already exists, skipping its creation." ) } else { - cat_rule("Creating _disable_autoload.R") + cli_cat_rule("Creating _disable_autoload.R") write( "# Disabling shiny autoload\n\n# See ?shiny::loadSupport for more information", fls diff --git a/R/enable_roxygenize.R b/R/enable_roxygenize.R index de76ac87..46173661 100644 --- a/R/enable_roxygenize.R +++ b/R/enable_roxygenize.R @@ -8,13 +8,13 @@ enable_roxygenize <- function(path = list.files( pattern = "Rproj$", full.names = TRUE )[1]) { - cat_bullet( + cli_cat_bullet( sprintf("Reading %s content ", basename(path)), bullet = "info", bullet_col = "green" ) source <- yaml::read_yaml(file = path) - cat_bullet( + cli_cat_bullet( "Enable roxygen2", bullet = "info", bullet_col = "green" diff --git a/R/golem-yaml-utils.R b/R/golem-yaml-utils.R index a6f61463..fe495cef 100644 --- a/R/golem-yaml-utils.R +++ b/R/golem-yaml-utils.R @@ -94,7 +94,7 @@ amend_golem_config <- function( if (key == "golem_wd") { cat_if_talk( "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line + fun = cli_cat_line ) } diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R index 80c05476..0ab4b48d 100644 --- a/R/install_dev_deps.R +++ b/R/install_dev_deps.R @@ -62,21 +62,22 @@ install_dev_deps <- function( for ( pak in unique( c( - "usethis", - "pkgload", - "dockerfiler", - "devtools", - "roxygen2", "attachment", - "rstudioapi", - "here", - "fs", + "cli", + "crayon", "desc", + "devtools", + "dockerfiler", + "fs", + "here", "pkgbuild", + "pkgload", "processx", + "roxygen2", "rsconnect", + "rstudioapi", "testthat", - "rstudioapi" + "usethis" ) ) ) { diff --git a/R/modules_fn.R b/R/modules_fn.R index 1476f12a..9ef1cf34 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -19,7 +19,6 @@ #' @note This function will prefix the `name` argument with `mod_`. #' #' @export -#' @importFrom cli cat_bullet #' @importFrom utils file.edit #' #' @seealso [module_template()] diff --git a/R/reload.R b/R/reload.R index 759a9a48..0eb2e150 100644 --- a/R/reload.R +++ b/R/reload.R @@ -119,7 +119,7 @@ document_and_reload <- function( ) }) if (attempt::is_try_error(roxed)) { - cat_rule( + cli_cat_rule( "Error documenting your package" ) dialog_if_has("Alert", "Error documenting your package") @@ -136,7 +136,7 @@ document_and_reload <- function( }) if (attempt::is_try_error(loaded)) { - cat_rule( + cli_cat_rule( "Error loading your package" ) dialog_if_has("Alert", "Error loading your package") diff --git a/R/set_golem_options.R b/R/set_golem_options.R index 2a965ae9..fce0c411 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -48,7 +48,7 @@ set_golem_options <- function( # golem_install_dev_pkg() function if (talkative) { - cat_rule( + cli_cat_rule( "Setting {golem} options in `golem-config.yml`" ) } @@ -90,7 +90,7 @@ set_golem_options <- function( # This part is for {usethis} and {here} if (talkative) { - cat_rule( + cli_cat_rule( "Setting {usethis} project as `golem_wd`" ) } diff --git a/R/use_favicon.R b/R/use_favicon.R index 6ad5a7e7..1ffd3743 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -100,7 +100,7 @@ use_favicon <- function( "You choose a png favicon, please add `ext = 'png'` to `favicon()` within the `golem_add_external_resources()` function in 'app_ui.R'." ) } else { - cat_line( + cli_cat_line( "Favicon is automatically linked in app_ui via `golem_add_external_resources()`" ) } diff --git a/R/use_files.R b/R/use_files.R index f1d479a0..1c322f5d 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -14,7 +14,6 @@ #' #' @export #' @rdname use_files -#' @importFrom cli cat_bullet #' #' @return The path to the file, invisibly. use_external_js_file <- function( diff --git a/R/use_utils.R b/R/use_utils.R index d004b5e1..f54f786e 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -10,7 +10,6 @@ #' @export #' @rdname utils_files #' -#' @importFrom cli cat_bullet #' @importFrom utils capture.output #' #' @return Used for side-effects. diff --git a/R/utils.R b/R/utils.R index 4b021a16..497a4551 100644 --- a/R/utils.R +++ b/R/utils.R @@ -11,10 +11,7 @@ golem_sys <- function( ) } -# from usethis https://github.com/r-lib/usethis/ -darkgrey <- function(x) { - x <- crayon::make_style("darkgrey")(x) -} + create_if_needed <- function( path, @@ -111,10 +108,9 @@ remove_comments <- function(file) { writeLines(text = lines_without_comment, con = file) } -#' @importFrom cli cat_bullet cat_green_tick <- function(...) { do_if_unquiet({ - cat_bullet( + cli_cat_bullet( ..., bullet = "tick", bullet_col = "green" @@ -122,10 +118,9 @@ cat_green_tick <- function(...) { }) } -#' @importFrom cli cat_bullet cat_red_bullet <- function(...) { do_if_unquiet({ - cli::cat_bullet( + cli_cat_bullet( ..., bullet = "bullet", bullet_col = "red" @@ -133,10 +128,9 @@ cat_red_bullet <- function(...) { }) } -#' @importFrom cli cat_bullet cat_info <- function(...) { do_if_unquiet({ - cli::cat_bullet( + cli_cat_bullet( ..., bullet = "arrow_right", bullet_col = "grey" @@ -144,21 +138,6 @@ cat_info <- function(...) { }) } -cat_rule <- function(...) { - do_if_unquiet({ - cli::cat_rule( - ... - ) - }) -} - -cat_line <- function(...) { - do_if_unquiet({ - cli::cat_line( - ... - ) - }) -} cat_exists <- function(where) { cat_red_bullet( @@ -183,8 +162,8 @@ cat_dir_necessary <- function() { cat_start_download <- function() { do_if_unquiet({ - cat_line("") - cat_rule("Initiating file download") + cli_cat_line("") + cli_cat_line("Initiating file download") }) } @@ -203,8 +182,8 @@ cat_downloaded <- function( cat_start_copy <- function() { do_if_unquiet({ - cat_line("") - cat_rule("Copying file") + cli_cat_line("") + cli_cat_line("Copying file") }) } @@ -341,13 +320,13 @@ after_creation_message_html_template <- function( name ) { do_if_unquiet({ - cat_line("") - cat_rule("To use this html file as a template, add the following code in your UI:") - cat_line(darkgrey("htmlTemplate(")) - cat_line(darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) - cat_line(darkgrey(" body = tagList()")) - cat_line(darkgrey(" # add here other template arguments")) - cat_line(darkgrey(")")) + cli_cat_line("") + cli_cat_line("To use this html file as a template, add the following code in your UI:") + cli_cat_line(crayon_darkgrey("htmlTemplate(")) + cli_cat_line(crayon_darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) + cli_cat_line(crayon_darkgrey(" body = tagList()")) + cli_cat_line(crayon_darkgrey(" # add here other template arguments")) + cli_cat_line(crayon_darkgrey(")")) }) } @@ -414,48 +393,6 @@ yesno <- function(...) { menu(c("Yes", "No")) == 1 } - -# Checking that a package is installed -check_is_installed <- function( - pak, - ... -) { - if ( - !requireNamespace(pak, ..., quietly = TRUE) - ) { - stop( - sprintf( - "The {%s} package is required to run this function.\nYou can install it with `install.packages('%s')`.", - pak, - pak - ), - call. = FALSE - ) - } -} - -required_version <- function( - pak, - version -) { - if ( - utils::packageVersion(pak) < version - ) { - stop( - sprintf( - "This function require the version '%s' of the {%s} package.\nYou can update with `install.packages('%s')`.", - version, - pak, - pak - ), - call. = FALSE - ) - } -} - - - - add_sass_code <- function(where, dir, name) { if (fs_file_exists(where)) { if (fs_file_exists("dev/run_dev.R")) { diff --git a/_pkgdown.yml b/_pkgdown.yml index a2345294..4bac04b8 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -25,7 +25,7 @@ reference: - title: Create a Dockerfile desc: | - Build a container containing your Shiny App. `add_dockerfile()` creates a "classical" Dockerfile, + Build a container containing your Shiny App. `add_dockerfile()` creates a "classical" Dockerfile, while `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` creates platform specific Dockerfile. contents: - add_dockerfile @@ -34,7 +34,7 @@ reference: - title: Use files desc: | - `use_external_js_file` and `use_external_css_file`download files from external sources and install + `use_external_js_file` and `use_external_css_file`download files from external sources and install them inside the appropriate directory. `use_utils_ui` copies the golem_utils_ui.R to the R folder and `use_utils_server` copies the golem_utils_server.R to the R folder. contents: @@ -88,7 +88,7 @@ reference: - title: Addins desc: | - `insert_ns()` takes a selected character vector and wrap it in `ns()`. The series of `go_to_*()` + `insert_ns()` takes a selected character vector and wrap it in `ns()`. The series of `go_to_*()` addins help you go to common files used in developing a {golem} application. contents: - insert_ns @@ -102,7 +102,7 @@ reference: - title: JavaScript interaction functions desc: | - `activate_js` is used in your UI to insert directly the JavaScript functions contained in golem. + `activate_js` is used in your UI to insert directly the JavaScript functions contained in golem. These functions can be called from the server with `invoke_js`. `invoke_js` can also be used to launch any JS function created inside a Shiny JavaScript handler. contents: @@ -111,7 +111,7 @@ reference: - title: Options desc: | - Set and get a series of options to be used with `{golem}`. These options are found inside the + Set and get a series of options to be used with `{golem}`. These options are found inside the `golem-config.yml` file, found in most cases inside the inst folder. contents: - set_golem_options @@ -126,9 +126,11 @@ reference: desc: | `use_recommended_deps` adds shiny, DT, attempt, glue, golem, htmltools to dependencies `use_recommended_tests` adds a test folder and copy the golem tests + `install_dev_deps` install all packages needed for golem developpement contents: - use_recommended_deps - use_recommended_tests + - install_dev_deps - title: Misc contents: @@ -157,3 +159,5 @@ reference: - sanity_check - js_handler_template - use_module_test + - get_current_config + - pkg_name diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index bb623ca5..3c8db429 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -92,7 +92,7 @@ new_css <- function(path, package_name, ...) { write_there(" background-color:red;") write_there("}") - cli::cat_bullet("CSS generated") + cli_cat_bullet("CSS generated") } create_golem("ici", project_hook = new_css) @@ -294,4 +294,4 @@ run_app <- function( ) ) } -``` \ No newline at end of file +``` From 5a5ec0b1d0d1c38337e2d28971c06b4b2dedc569 Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Fri, 9 Dec 2022 19:08:29 +0100 Subject: [PATCH 107/190] update maintenance mode (#952) * fix pkgdown * explain maintenance mode --- NEWS.md | 3 +++ R/with_opt.R | 2 +- _pkgdown.yml | 1 + man/maintenance_page.Rd | 3 +++ vignettes/f_extending_golem.Rmd | 17 +++++++++++++---- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index b565d0e9..24ec3fbf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,7 @@ + Add `add_partial_html_template()` to create a partial html template, with "just" a div and a `{{ }}` (@nathansquan #858). + ## New features / user visible changes + The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}`, `{usethis}`, `{cli}`, `{crayon}` @@ -19,6 +20,8 @@ + Functions that print to the console are now quiet if `options("golem.quiet" = TRUE)`, #793 + Small documentation update in dockerfile related functions (#939) + `fill_desc()` now allows to set the version (#877) ++ Setting the Environment variable `GOLEM_MAINTENANCE_ACTIVE` to `TRUE` active the maintenance mode of your app + ## Bug fix + The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) diff --git a/R/with_opt.R b/R/with_opt.R index 3b33a40b..c4cbc777 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -123,7 +123,7 @@ get_golem_options <- function(which = NULL) { #' @importFrom shiny htmlTemplate #' #' @return an html_document -#' +#' @details see the vignette \code{vignette("f_extending_golem", package = "golem")} for details. #' @export maintenance_page <- function() { shiny::htmlTemplate( diff --git a/_pkgdown.yml b/_pkgdown.yml index 4bac04b8..0726a74e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -146,6 +146,7 @@ reference: - fill_desc - make_dev - with_golem_options + - maintenance_page - title: internal contents: diff --git a/man/maintenance_page.Rd b/man/maintenance_page.Rd index 148e3932..42bb5c7f 100644 --- a/man/maintenance_page.Rd +++ b/man/maintenance_page.Rd @@ -12,3 +12,6 @@ an html_document \description{ A default html page for maintenance mode } +\details{ +see the vignette \code{vignette("f_extending_golem", package = "golem")} for details. +} diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index 3c8db429..24cd694c 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -200,7 +200,7 @@ golem::add_css_file(name = "custom", template = my_tmpl) ## Turn on the maintenance mode -## What it is +### What it is From time to time, you need your application to be unavailbel: database update, API changes, etc. In order to keep your app running but make it unavailable, you can use a __maintenance mode__. @@ -208,7 +208,7 @@ When this maintenance mode is turned on, your application will be paused and a s `{golem}` comes with a default maintenance page, and you can replace it with you own page. -## How to set the maintenance mode +### How to set the maintenance mode The maintenance mode will be turned on whenever the R process detects that the `GOLEM_MAINTENANCE_ACTIVE` environment variable is set to TRUE. @@ -224,16 +224,25 @@ withr::with_envvar( ``` +or + +```{r eval = FALSE} +Sys.setenv("GOLEM_MAINTENANCE_ACTIVE" = TRUE) +golem::run_dev() +``` + + + If you're deploying on Posit Connect, you can set this variable in the setup panel. If in command line, you can also do ``` -export GOLEM_MAINTENANCE_ACTIVE=TRUE && Rscript -e "mypkg::run_app()" +export GOLEM_MAINTENANCE_ACTIVE=TRUE && Rscript -e "mygolem::run_app()" ``` -## The maintenance page +### The maintenance page `{golem}` comes with a default maintenance page, but you can override it and use your own custom page. From ebf86804622b2f0b3b147c4a4c2b578e0439c53c Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Sat, 10 Dec 2022 08:53:07 +0100 Subject: [PATCH 108/190] feat: remove rstudioapi hard dep (#955) * feat: remove rstudioapi hard dep * remove rstudioapi from NAMESPACE Co-authored-by: Colin Fay --- DESCRIPTION | 2 +- NAMESPACE | 8 ------ R/add_dockerfiles.R | 19 +++----------- R/addins.R | 18 +++++++------ R/bootstrap_rstudio_api.R | 54 +++++++++++++++++++++++++++++++++++++++ R/bootstrap_usethis.R | 2 +- R/create_golem.R | 6 +++-- R/reload.R | 18 ++++++++++--- R/sanity_check.R | 5 ++-- 9 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 R/bootstrap_rstudio_api.R diff --git a/DESCRIPTION b/DESCRIPTION index 5c5fcc07..cd1c2471 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,7 +30,6 @@ Imports: here, htmltools, rlang (>= 1.0.0), - rstudioapi, shiny (>= 1.5.0), utils, yaml @@ -59,6 +58,7 @@ Suggests: renv, usethis (>= 1.6.0), fs, + rstudioapi, desc VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index de7f62cb..e7919bb6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -94,14 +94,6 @@ importFrom(attempt,stop_if_not) importFrom(attempt,without_warning) importFrom(config,get) importFrom(htmltools,htmlDependency) -importFrom(rstudioapi,documentSaveAll) -importFrom(rstudioapi,getSourceEditorContext) -importFrom(rstudioapi,hasFun) -importFrom(rstudioapi,isAvailable) -importFrom(rstudioapi,modifyRange) -importFrom(rstudioapi,navigateToFile) -importFrom(rstudioapi,openProject) -importFrom(rstudioapi,sourceMarkers) importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) importFrom(shiny,htmlTemplate) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index bb1e915a..4901aef5 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -47,7 +47,6 @@ talk_once <- function(.f, msg = "") { #' @export #' @rdname dockerfiles #' -#' @importFrom rstudioapi navigateToFile isAvailable hasFun #' #' @examples #' \donttest{ @@ -176,11 +175,7 @@ add_dockerfile_ <- talk_once( dock$write(output) if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) - } + rstudioapi_navigateToFile(output) } alert_build( path = path, @@ -274,11 +269,7 @@ add_dockerfile_shinyproxy_ <- talk_once( dock$write(output) if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) - } + rstudioapi_navigateToFile(output) } alert_build( path, @@ -407,11 +398,7 @@ add_dockerfile_heroku_ <- talk_once( sprintf("You can replace %s with another app name.", apps_h) ) if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) - } + rstudioapi_navigateToFile(output) } usethis_use_build_ignore(files = output) return(invisible(dock)) diff --git a/R/addins.R b/R/addins.R index 50502b9d..442c7353 100644 --- a/R/addins.R +++ b/R/addins.R @@ -4,9 +4,7 @@ #' The series of `go_to_*()` addins help you go to #' common files used in developing a `{golem}` application. #' -#' @importFrom rstudioapi getSourceEditorContext modifyRange #' @importFrom attempt stop_if_not -#' @importFrom rstudioapi modifyRange #' #' @aliases addins #' @rdname addins @@ -17,16 +15,16 @@ NULL #' @aliases addins insert_ns <- function() { stop_if_not( - rstudioapi::hasFun("getSourceEditorContext"), + rstudioapi_hasFun("getSourceEditorContext"), msg = "Your version of RStudio does not support `getSourceEditorContext`" ) stop_if_not( - rstudioapi::hasFun("modifyRange"), + rstudioapi_hasFun("modifyRange"), msg = "Your version of RStudio does not support `modifyRange`" ) - curr_editor <- rstudioapi::getSourceEditorContext() + curr_editor <- rstudioapi_getSourceEditorContext() id <- curr_editor$id sel_rng <- curr_editor$selection[[1]]$range @@ -34,7 +32,11 @@ insert_ns <- function() { mod_text <- paste0("ns(", sel_text, ")") - rstudioapi::modifyRange(sel_rng, mod_text, id = id) + rstudioapi_modifyRange( + sel_rng, + mod_text, + id = id + ) } go_to <- function( @@ -50,11 +52,11 @@ go_to <- function( } stop_if_not( - rstudioapi::hasFun("navigateToFile"), + rstudioapi_hasFun("navigateToFile"), msg = "Your version of RStudio does not support `navigateToFile`" ) - rstudioapi::navigateToFile(file) + rstudioapi_navigateToFile(file) } #' @rdname addins diff --git a/R/bootstrap_rstudio_api.R b/R/bootstrap_rstudio_api.R new file mode 100644 index 00000000..e66901dc --- /dev/null +++ b/R/bootstrap_rstudio_api.R @@ -0,0 +1,54 @@ +check_rstudioapi_installed <- function(reason = "to manipulate RStudio files.") { + rlang::check_installed( + "rstudioapi", + reason = reason + ) +} + + +rstudioapi_navigateToFile <- function(output){ + # Don't suggest to install + if (rlang::is_installed("rstudioapi")){ + if ( + rstudioapi::isAvailable() & + rstudioapi::hasFun("navigateToFile") + ) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } + } else { + try(file.edit(output)) + } + +} + +rstudioapi_hasFun <- function( + fun +){ + # Default to FALSE so that it's FALSE + # If package is not installed + hasFun <- FALSE + if (rlang::is_installed("rstudioapi")){ + hasFun <- rstudioapi::hasFun(fun) + } + hasFun +} + +rstudioapi_getSourceEditorContext <- function(){ + check_rstudioapi_installed() + rstudioapi::getSourceEditorContext() +} + +rstudioapi_modifyRange <- function( + location = NULL, + text = NULL, + id = NULL +){ + check_rstudioapi_installed() + rstudioapi::modifyRange( + location = location, + text = text, + id = id + ) +} diff --git a/R/bootstrap_usethis.R b/R/bootstrap_usethis.R index 1e513da5..1e033f2c 100644 --- a/R/bootstrap_usethis.R +++ b/R/bootstrap_usethis.R @@ -38,7 +38,7 @@ usethis_use_package <- function( usethis_create_project <- function( path, - rstudio = rstudioapi::isAvailable(), + rstudio = rstudioapi::isAvailable(), # rstudioap is usethis Imports, so its ok open = rlang::is_interactive() ) { check_usethis_installed( diff --git a/R/create_golem.R b/R/create_golem.R index 76d421e3..2ec236f4 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -55,7 +55,6 @@ replace_package_name <- function( #' to `FALSE`. See https://github.com/ThinkR-open/golem/issues/468 for more background. #' #' @importFrom utils getFromNamespace -#' @importFrom rstudioapi isAvailable openProject hasFun #' @importFrom yaml write_yaml #' #' @export @@ -236,7 +235,10 @@ create_golem <- function( if (isTRUE(open)) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && rstudioapi::hasFun("openProject") + ) { rstudioapi::openProject(path = path) } else { setwd(path) diff --git a/R/reload.R b/R/reload.R index 0eb2e150..c92b5d9a 100644 --- a/R/reload.R +++ b/R/reload.R @@ -88,7 +88,6 @@ check_name_consistency <- function(pkg) { #' @inheritParams pkgload::load_all #' #' @param ... Other arguments passed to `pkgload::load_all()` -#' @importFrom rstudioapi isAvailable hasFun documentSaveAll #' @export #' #' @return Used for side-effects @@ -107,7 +106,11 @@ document_and_reload <- function( check_name_consistency(pkg) rlang::check_installed("pkgload") - if (rstudioapi::isAvailable() & rstudioapi::hasFun("documentSaveAll")) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("documentSaveAll") + ) { rstudioapi::documentSaveAll() } roxed <- try({ @@ -144,8 +147,15 @@ document_and_reload <- function( } } -dialog_if_has <- function(title, message, url = "") { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("showDialog")) { +dialog_if_has <- function( + title, + message, + url = "" +) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && rstudioapi::hasFun("showDialog") + ) { rstudioapi::showDialog(title, message, url) } } diff --git a/R/sanity_check.R b/R/sanity_check.R index 86669ec6..8730e151 100644 --- a/R/sanity_check.R +++ b/R/sanity_check.R @@ -8,7 +8,6 @@ #' @rdname sanity_check #' @export #' -#' @importFrom rstudioapi sourceMarkers hasFun isAvailable #' #' @return A DataFrame if any of the words has been found. sanity_check <- function(pkg = get_golem_wd()) { @@ -41,7 +40,9 @@ sanity_check <- function(pkg = get_golem_wd()) { } if (length(source_markers) > 0) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("sourceMarkers")) { + if ( + rlang::is_installed("rstudioapi") && rstudioapi::isAvailable() &&rstudioapi::hasFun("sourceMarkers" + )) { rstudioapi::sourceMarkers("sanity_check", markers = source_markers) } return(source_markers) From 135560bef9caae2227c4685c552d973424a64c2f Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Mon, 12 Dec 2022 10:35:09 +0100 Subject: [PATCH 109/190] fix #956 improve get_golem_options doc (#957) * fix #956 improve get_golem_options doc * fix doc * add news --- NEWS.md | 1 + R/with_opt.R | 44 +++++++++++++++++++++++++--------------- man/get_golem_options.Rd | 44 +++++++++++++++++++++++++--------------- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/NEWS.md b/NEWS.md index 24ec3fbf..1cc51e33 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,6 +30,7 @@ + The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) + The typo in `install.packages()` in 02_dev.R has been corrected (@asiripanich) + `add_dockerfile_with_renv()` now works well with uppercase in package name ++ improve `get_golem_options()` documentation ## Internal changes diff --git a/R/with_opt.R b/R/with_opt.R index c4cbc777..ce7d71cb 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -77,13 +77,32 @@ with_golem_options <- function( #' #' # Define and use golem_options #' if (interactive()) { -#' # 1. Pass parameters to `run_app` +#' # 1. Pass parameters directly to `run_app` #' -#' # to set default value, edit run_app like this : +#' run_app( title="My Golem App", +#' content = "something" ) +#' +#' # 2. Get the values +#' # 2.1 from the UI side +#' +#' h1(get_golem_options("title")) +#' +#' # 2.2 from the server-side +#' +#' output$param <- renderPrint({ +#' paste("param content = ", get_golem_options("content")) +#' }) +#' +#' output$param_full <- renderPrint({ +#' get_golem_options() # list of all golem options as a list. +#' }) +#' +#' # 3. If needed, to set default value, edit `run_app` like this : #' #' run_app <- function( -#' title = "this", -#' content = "that" +#' title = "this", +#' content = "that", +#' ... #' ) { #' with_golem_options( #' app = shinyApp( @@ -91,21 +110,14 @@ with_golem_options <- function( #' server = app_server #' ), #' golem_opts = list( -#' p1 = p1, -#' p3 = p3 +#' title = title, +#' content = content, +#' ... #' ) #' ) #' } -#' -#' # 2. Get the values from the UI side -#' -#' h1(get_golem_options("title")) -#' -#' # 3. Get the value from the server-side -#' -#' output$param <- renderPrint({ -#' paste("param p2 = ", get_golem_options("p2")) -#' }) +#' +#' #' } #' get_golem_options <- function(which = NULL) { diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index a0e2895f..0eadfabc 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -21,13 +21,32 @@ parameters passed to \code{run_app()}. # Define and use golem_options if (interactive()) { - # 1. Pass parameters to `run_app` + # 1. Pass parameters directly to `run_app` - # to set default value, edit run_app like this : + run_app( title="My Golem App", + content = "something" ) + + # 2. Get the values + # 2.1 from the UI side + + h1(get_golem_options("title")) + + # 2.2 from the server-side + + output$param <- renderPrint({ + paste("param content = ", get_golem_options("content")) + }) + + output$param_full <- renderPrint({ + get_golem_options() # list of all golem options as a list. + }) + + # 3. If needed, to set default value, edit `run_app` like this : run_app <- function( - title = "this", - content = "that" + title = "this", + content = "that", + ... ) { with_golem_options( app = shinyApp( @@ -35,21 +54,14 @@ if (interactive()) { server = app_server ), golem_opts = list( - p1 = p1, - p3 = p3 + title = title, + content = content, + ... ) ) } - - # 2. Get the values from the UI side - - h1(get_golem_options("title")) - - # 3. Get the value from the server-side - - output$param <- renderPrint({ - paste("param p2 = ", get_golem_options("p2")) - }) + + } } From e8527de47d332b9a83d209dfeb1a65cd73302928 Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Mon, 12 Dec 2022 10:36:47 +0100 Subject: [PATCH 110/190] fix 958 save all before run_dev.R (#959) * fix 958 save all before run_dev.R * add news --- NEWS.md | 1 + R/run_dev.R | 15 ++++++++++++++- man/run_dev.Rd | 4 +++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1cc51e33..3066b6ed 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,6 +21,7 @@ + Small documentation update in dockerfile related functions (#939) + `fill_desc()` now allows to set the version (#877) + Setting the Environment variable `GOLEM_MAINTENANCE_ACTIVE` to `TRUE` active the maintenance mode of your app ++ `golem::run_dev()` now save all open documents before sourcing the `dev/run_dev.R` file ## Bug fix diff --git a/R/run_dev.R b/R/run_dev.R index fd5f2e98..6ee8c25f 100644 --- a/R/run_dev.R +++ b/R/run_dev.R @@ -1,6 +1,7 @@ #' Run run_dev.R #' #' @param file File path to `run_dev.R`. Defaults to `R/run_dev.R`. +#' @param save_all boolean. If TRUE, save all open file before sourcing `file` #' @inheritParams add_module #' #' @export @@ -8,9 +9,21 @@ #' @return Used for side-effect run_dev <- function( file = "dev/run_dev.R", - pkg = get_golem_wd() + pkg = get_golem_wd(), + save_all = TRUE ) { +if (save_all){ + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("documentSaveAll") + ) { + rstudioapi::documentSaveAll() + } +} + + # We'll look for the run_dev script in the current dir try_dev <- file.path( pkg, diff --git a/man/run_dev.Rd b/man/run_dev.Rd index 3f12448a..eb25135a 100644 --- a/man/run_dev.Rd +++ b/man/run_dev.Rd @@ -4,12 +4,14 @@ \alias{run_dev} \title{Run run_dev.R} \usage{ -run_dev(file = "dev/run_dev.R", pkg = get_golem_wd()) +run_dev(file = "dev/run_dev.R", pkg = get_golem_wd(), save_all = TRUE) } \arguments{ \item{file}{File path to \code{run_dev.R}. Defaults to \code{R/run_dev.R}.} \item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} + +\item{save_all}{boolean. If TRUE, save all open file before sourcing \code{file}} } \value{ Used for side-effect From 54b38b082228e368297eba84b6bd79d5a17e4693 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 14 Dec 2022 21:38:07 +0100 Subject: [PATCH 111/190] doc: version bump & readme --- DESCRIPTION | 8 ++--- README.Rmd | 5 +-- README.md | 89 ++++++++++++++++++++++++++--------------------------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index cd1c2471..9d73703a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.5.9005 +Version: 0.4.0 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), @@ -22,9 +22,9 @@ Description: An opinionated framework for building a production-ready License: MIT + file LICENSE URL: https://github.com/ThinkR-open/golem BugReports: https://github.com/ThinkR-open/golem/issues -Depends: +Depends: R (>= 3.0) -Imports: +Imports: attempt (>= 0.3.0), config, here, @@ -60,7 +60,7 @@ Suggests: fs, rstudioapi, desc -VignetteBuilder: +VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 diff --git a/README.Rmd b/README.Rmd index 468a9f85..28555092 100644 --- a/README.Rmd +++ b/README.Rmd @@ -29,6 +29,7 @@ knitr::opts_chunk$set( You're reading the doc about version : ``r as.data.frame(read.dcf("DESCRIPTION"))$Version `` + ## Tool series This package is part of a series of tools for Shiny, which includes: @@ -117,9 +118,9 @@ knitr::include_graphics("https://raw.githubusercontent.com/ThinkR-open/golem/mas ## Step by step guide -See full documentation in the {pkgdown} website: +See full documentation in the {pkgdown} website: -[CRAN] +[CRAN] [dev] diff --git a/README.md b/README.md index d90de195..e70d5dda 100644 --- a/README.md +++ b/README.md @@ -19,69 +19,68 @@ shiny applications. ## About -You’re reading the doc about version : `0.3.4.9002` +You’re reading the doc about version : `0.4.0` ## Tool series This package is part of a series of tools for Shiny, which includes: -- `{golem}` - -- `{shinipsum}` - -- `{fakir}` - -- `{shinysnippets}` - +- `{golem}` - +- `{shinipsum}` - +- `{fakir}` - +- `{shinysnippets}` - ## Resources ### The Book : -- -- [paper version of the book “Engineering Production-Grade Shiny - Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) +- +- [paper version of the book “Engineering Production-Grade Shiny + Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) ### Blog posts : *Building Big Shiny Apps* -- Part 1: - -- Part 2: - +- Part 1: + +- Part 2: + [*Make a Fitness App from scratch*](https://towardsdatascience.com/production-grade-r-shiny-with-golem-prototyping-51b03f37c2a9) ### Slide decks -- useR! 2019 : [A Framework for Building Robust & Production Ready - Shiny - Apps](https://github.com/VincentGuyader/user2019/raw/master/golem_Vincent_Guyader_USER!2019.pdf) -- ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with - {golem}](https://speakerdeck.com/colinfay/production-grade-shiny-apps-with-golem) -- rstudio::conf(2020) : [Production-grade Shiny Apps with - golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) -- barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps - with - {golem}](https://www.barcelonar.org/presentations/BarcelonaR_Building_Production_Grade_Shiny_Apps_with_golem.pdf) +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny + Apps](https://github.com/VincentGuyader/user2019/raw/master/golem_Vincent_Guyader_USER!2019.pdf) +- ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with + {golem}](https://speakerdeck.com/colinfay/production-grade-shiny-apps-with-golem) +- rstudio::conf(2020) : [Production-grade Shiny Apps with + golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) +- barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps + with + {golem}](https://www.barcelonar.org/presentations/BarcelonaR_Building_Production_Grade_Shiny_Apps_with_golem.pdf) ### Video -- [{golem} and Effective Shiny Development - Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) -- [Hands-on demonstration of - {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) -- useR! 2019 : [A Framework for Building Robust & Production Ready - Shiny Apps](https://youtu.be/tCAan6smrjs) -- 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) -- rstudio::conf(2020) : [Production-grade Shiny Apps with - golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) -- 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec - {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) -- 🇫🇷 [Déploiement d’une application {shiny} dans docker avec {renv} et - {golem}](https://www.youtube.com/watch?v=diCG4t76k78) +- [{golem} and Effective Shiny Development + Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) +- [Hands-on demonstration of + {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny + Apps](https://youtu.be/tCAan6smrjs) +- 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) +- rstudio::conf(2020) : [Production-grade Shiny Apps with + golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) +- 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec + {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) +- 🇫🇷 [Déploiement d’une application {shiny} dans docker avec {renv} et + {golem}](https://www.youtube.com/watch?v=diCG4t76k78) ### Cheatsheet -- [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) +- [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) ### Examples apps @@ -89,26 +88,26 @@ These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. -- -- -- -- +- +- +- +- You can also find apps at: -- -- +- +- ## Installation -- You can install the stable version from CRAN with: +- You can install the stable version from CRAN with: ``` r install.packages("golem") ``` -- You can install the development version from - [GitHub](https://github.com/Thinkr-open/golem) with: +- You can install the development version from + [GitHub](https://github.com/Thinkr-open/golem) with: ``` r # install.packages("remotes") From 08577f4bd13dbf5d37b677035d57da0afa5147bb Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 08:49:32 +0100 Subject: [PATCH 112/190] style: linter --- R/boostrap_cli.R | 5 --- R/bootstrap_desc.R | 36 ++++++++-------- R/bootstrap_rstudio_api.R | 67 ++++++++++++++---------------- R/bundle_resources.R | 6 +-- R/config.R | 1 - R/create_golem.R | 2 +- R/modules_fn.R | 1 - R/reload.R | 6 +-- R/run_dev.R | 21 +++++----- R/sanity_check.R | 3 +- R/set_golem_options.R | 1 - R/with_opt.R | 29 +++++++------ tests/testthat/test-create_golem.R | 2 - vignettes/c_deploy.Rmd | 2 +- vignettes/e_config.Rmd | 2 +- 15 files changed, 84 insertions(+), 100 deletions(-) diff --git a/R/boostrap_cli.R b/R/boostrap_cli.R index c9f6da44..d26cb117 100644 --- a/R/boostrap_cli.R +++ b/R/boostrap_cli.R @@ -30,9 +30,4 @@ cli_cat_rule <- function(...) { ... ) }) - - } - - - diff --git a/R/bootstrap_desc.R b/R/bootstrap_desc.R index a91196df..c9fa0ce9 100644 --- a/R/bootstrap_desc.R +++ b/R/bootstrap_desc.R @@ -1,40 +1,40 @@ # All the fns here check that {desc} is installed # before doing anything. check_desc_installed <- function() { - rlang::check_installed( - "desc", - reason = "to fill DESCRIPTION." - ) + rlang::check_installed( + "desc", + reason = "to fill DESCRIPTION." + ) } desc_description <- function(file) { - check_desc_installed() - desc::description$new( - file = file - ) + check_desc_installed() + desc::description$new( + file = file + ) } desc_get <- function(keys) { - check_desc_installed() - desc::desc_get(keys) + check_desc_installed() + desc::desc_get(keys) } desc_get_version <- function() { - check_desc_installed() - desc::desc_get_version() + check_desc_installed() + desc::desc_get_version() } desc_get_deps <- function(file = NULL) { - check_desc_installed() - desc::desc_get_deps(file) + check_desc_installed() + desc::desc_get_deps(file) } desc_get_field <- function(key) { - check_desc_installed() - desc::desc_get_field(key) + check_desc_installed() + desc::desc_get_field(key) } desc_get_author <- function() { - check_desc_installed() - desc::desc_get_author() + check_desc_installed() + desc::desc_get_author() } diff --git a/R/bootstrap_rstudio_api.R b/R/bootstrap_rstudio_api.R index e66901dc..7aa21fb2 100644 --- a/R/bootstrap_rstudio_api.R +++ b/R/bootstrap_rstudio_api.R @@ -6,49 +6,46 @@ check_rstudioapi_installed <- function(reason = "to manipulate RStudio files.") } -rstudioapi_navigateToFile <- function(output){ - # Don't suggest to install - if (rlang::is_installed("rstudioapi")){ - if ( - rstudioapi::isAvailable() & - rstudioapi::hasFun("navigateToFile") - ) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) - } +rstudioapi_navigateToFile <- function(output) { + # Don't suggest to install + if (rlang::is_installed("rstudioapi")) { + if ( + rstudioapi::isAvailable() & + rstudioapi::hasFun("navigateToFile") + ) { + rstudioapi::navigateToFile(output) } else { - try(file.edit(output)) + try(file.edit(output)) } - + } else { + try(file.edit(output)) + } } -rstudioapi_hasFun <- function( - fun -){ - # Default to FALSE so that it's FALSE - # If package is not installed - hasFun <- FALSE - if (rlang::is_installed("rstudioapi")){ - hasFun <- rstudioapi::hasFun(fun) - } - hasFun +rstudioapi_hasFun <- function(fun) { + # Default to FALSE so that it's FALSE + # If package is not installed + hasFun <- FALSE + if (rlang::is_installed("rstudioapi")) { + hasFun <- rstudioapi::hasFun(fun) + } + hasFun } -rstudioapi_getSourceEditorContext <- function(){ - check_rstudioapi_installed() - rstudioapi::getSourceEditorContext() +rstudioapi_getSourceEditorContext <- function() { + check_rstudioapi_installed() + rstudioapi::getSourceEditorContext() } rstudioapi_modifyRange <- function( - location = NULL, - text = NULL, - id = NULL -){ + location = NULL, + text = NULL, + id = NULL +) { check_rstudioapi_installed() - rstudioapi::modifyRange( - location = location, - text = text, - id = id - ) + rstudioapi::modifyRange( + location = location, + text = text, + id = id + ) } diff --git a/R/bundle_resources.R b/R/bundle_resources.R index faa2b2d2..8756d708 100644 --- a/R/bundle_resources.R +++ b/R/bundle_resources.R @@ -39,7 +39,7 @@ bundle_resources <- function( ) > 0 ) { res[[ - length(res) + 1 + length(res) + 1 ]] <- htmltools::htmlDependency( name, version, @@ -84,7 +84,7 @@ bundle_resources <- function( for (i in css_nms) { res[[ - length(res) + 1 + length(res) + 1 ]] <- tags$link( href = i, rel = "stylesheet" @@ -95,7 +95,7 @@ bundle_resources <- function( if (with_sparkles) { res[[ - length(res) + 1 + length(res) + 1 ]] <- htmlDependency( "sparkles", version = utils::packageVersion("golem"), diff --git a/R/config.R b/R/config.R index 37df7748..9bd40dc6 100644 --- a/R/config.R +++ b/R/config.R @@ -61,7 +61,6 @@ guess_where_config <- function( #' #' @export get_current_config <- function(path = getwd()) { - # We check wether we can guess where the config file is path_conf <- guess_where_config(path) diff --git a/R/create_golem.R b/R/create_golem.R index 2ec236f4..b450a0eb 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -237,7 +237,7 @@ create_golem <- function( if (isTRUE(open)) { if ( rlang::is_installed("rstudioapi") && - rstudioapi::isAvailable() && rstudioapi::hasFun("openProject") + rstudioapi::isAvailable() && rstudioapi::hasFun("openProject") ) { rstudioapi::openProject(path = path) } else { diff --git a/R/modules_fn.R b/R/modules_fn.R index 9ef1cf34..95a8f8fd 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -237,7 +237,6 @@ use_module_test <- function( pkg = get_golem_wd(), open = TRUE ) { - # Remove the extension if any name <- file_path_sans_ext(name) # Remove the "mod_" if any diff --git a/R/reload.R b/R/reload.R index c92b5d9a..cf547347 100644 --- a/R/reload.R +++ b/R/reload.R @@ -108,8 +108,8 @@ document_and_reload <- function( if ( rlang::is_installed("rstudioapi") && - rstudioapi::isAvailable() && - rstudioapi::hasFun("documentSaveAll") + rstudioapi::isAvailable() && + rstudioapi::hasFun("documentSaveAll") ) { rstudioapi::documentSaveAll() } @@ -154,7 +154,7 @@ dialog_if_has <- function( ) { if ( rlang::is_installed("rstudioapi") && - rstudioapi::isAvailable() && rstudioapi::hasFun("showDialog") + rstudioapi::isAvailable() && rstudioapi::hasFun("showDialog") ) { rstudioapi::showDialog(title, message, url) } diff --git a/R/run_dev.R b/R/run_dev.R index 6ee8c25f..2479e8c6 100644 --- a/R/run_dev.R +++ b/R/run_dev.R @@ -12,18 +12,17 @@ run_dev <- function( pkg = get_golem_wd(), save_all = TRUE ) { - -if (save_all){ - if ( - rlang::is_installed("rstudioapi") && - rstudioapi::isAvailable() && - rstudioapi::hasFun("documentSaveAll") - ) { - rstudioapi::documentSaveAll() + if (save_all) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("documentSaveAll") + ) { + rstudioapi::documentSaveAll() + } } -} - - + + # We'll look for the run_dev script in the current dir try_dev <- file.path( pkg, diff --git a/R/sanity_check.R b/R/sanity_check.R index 8730e151..6d2d5a34 100644 --- a/R/sanity_check.R +++ b/R/sanity_check.R @@ -41,8 +41,7 @@ sanity_check <- function(pkg = get_golem_wd()) { if (length(source_markers) > 0) { if ( - rlang::is_installed("rstudioapi") && rstudioapi::isAvailable() &&rstudioapi::hasFun("sourceMarkers" - )) { + rlang::is_installed("rstudioapi") && rstudioapi::isAvailable() && rstudioapi::hasFun("sourceMarkers")) { rstudioapi::sourceMarkers("sanity_check", markers = source_markers) } return(source_markers) diff --git a/R/set_golem_options.R b/R/set_golem_options.R index fce0c411..e371c731 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -43,7 +43,6 @@ set_golem_options <- function( talkative = TRUE, config_file = golem::get_current_config(golem_wd) ) { - # TODO here we'll run the # golem_install_dev_pkg() function diff --git a/R/with_opt.R b/R/with_opt.R index ce7d71cb..1ff0acfb 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -19,7 +19,6 @@ with_golem_options <- function( maintenance_page = golem::maintenance_page, print = FALSE ) { - # Check if app is in maintenance if (Sys.getenv("GOLEM_MAINTENANCE_ACTIVE", "FALSE") == "TRUE") { app <- shiny::shinyApp( @@ -79,10 +78,12 @@ with_golem_options <- function( #' if (interactive()) { #' # 1. Pass parameters directly to `run_app` #' -#' run_app( title="My Golem App", -#' content = "something" ) +#' run_app( +#' title = "My Golem App", +#' content = "something" +#' ) #' -#' # 2. Get the values +#' # 2. Get the values #' # 2.1 from the UI side #' #' h1(get_golem_options("title")) @@ -92,17 +93,17 @@ with_golem_options <- function( #' output$param <- renderPrint({ #' paste("param content = ", get_golem_options("content")) #' }) -#' +#' #' output$param_full <- renderPrint({ -#' get_golem_options() # list of all golem options as a list. +#' get_golem_options() # list of all golem options as a list. #' }) -#' +#' #' # 3. If needed, to set default value, edit `run_app` like this : #' #' run_app <- function( -#' title = "this", -#' content = "that", -#' ... +#' title = "this", +#' content = "that", +#' ... #' ) { #' with_golem_options( #' app = shinyApp( @@ -110,14 +111,12 @@ with_golem_options <- function( #' server = app_server #' ), #' golem_opts = list( -#' title = title, -#' content = content, -#' ... +#' title = title, +#' content = content, +#' ... #' ) #' ) #' } -#' -#' #' } #' get_golem_options <- function(which = NULL) { diff --git a/tests/testthat/test-create_golem.R b/tests/testthat/test-create_golem.R index 0dd4a130..92992f06 100644 --- a/tests/testthat/test-create_golem.R +++ b/tests/testthat/test-create_golem.R @@ -2,7 +2,6 @@ ### Helpers functions ---------------------------------------------------------- is_properly_populated_golem <- function(path) { - # All files excepts *.Rproj which changes based on the project name expected_files <- c( "DESCRIPTION", @@ -62,7 +61,6 @@ dummy_dir <- tempfile(pattern = "dummy") dir.create(dummy_dir) withr::with_dir(dummy_dir, { - ## Default test_that("golem is created and properly populated", { dummy_golem_path <- file.path(dummy_dir, "koko") diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index b8d60c07..fd9b1cb0 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -160,4 +160,4 @@ deploy/ \-- renv.lock.prod ``` -then follow the README file \ No newline at end of file +then follow the README file diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 4ad83d4d..2ec72636 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -175,4 +175,4 @@ If you've built an app with `{golem}` before the version 0.2.0, this config file ```{r echo = FALSE} setwd(old) -``` \ No newline at end of file +``` From cf76381a5521e77e17a9a9b93b63afc21b0a0259 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 08:49:49 +0100 Subject: [PATCH 113/190] styler: linter bis --- R/add_dockerfiles_renv.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 8e146487..5d6b18a6 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -39,7 +39,6 @@ add_dockerfile_with_renv_ <- function( file.copy(from = lockfile, to = output_dir) socle <- dockerfiler::dock_from_renv( - lockfile = lockfile, distro = distro, FROM = FROM, @@ -180,7 +179,7 @@ docker build -f Dockerfile --progress=plain -t %s . docker run -p %s:%s %s # then go to 127.0.0.1:%s", tolower(paste0(golem::get_golem_name(), "_base")), - tolower( paste0(golem::get_golem_name(), ":latest")), + tolower(paste0(golem::get_golem_name(), ":latest")), port, port, tolower(paste0(golem::get_golem_name(), ":latest")), From 2e394e4ce407ff34023e92910e4023eb8a2f5d7f Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 09:19:43 +0100 Subject: [PATCH 114/190] feat: on create golem, message if dev deps are not installed Issue #915 --- R/create_golem.R | 3 +++ R/install_dev_deps.R | 55 ++++++++++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/R/create_golem.R b/R/create_golem.R index b450a0eb..ad013b04 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -31,6 +31,7 @@ replace_package_name <- function( } + #' Create a package for a Shiny App using `{golem}` #' #' @param path Name of the folder to create the package in. @@ -233,6 +234,8 @@ create_golem <- function( ) ) + check_dev_deps_are_installed() + if (isTRUE(open)) { if ( diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R index 0ab4b48d..1a0926ac 100644 --- a/R/install_dev_deps.R +++ b/R/install_dev_deps.R @@ -60,29 +60,44 @@ install_dev_deps <- function( } for ( - pak in unique( - c( - "attachment", - "cli", - "crayon", - "desc", - "devtools", - "dockerfiler", - "fs", - "here", - "pkgbuild", - "pkgload", - "processx", - "roxygen2", - "rsconnect", - "rstudioapi", - "testthat", - "usethis" - ) - ) + pak in dev_deps ) { if (!rlang::is_installed(pak)) { f(pak, ...) } } } + +dev_deps <- unique( + c( + "attachment", + "cli", + "crayon", + "desc", + "devtools", + "dockerfiler", + "fs", + "here", + "pkgbuild", + "pkgload", + "processx", + "roxygen2", + "rsconnect", + "rstudioapi", + "testthat", + "usethis" + ) +) + +check_dev_deps_are_installed <- function() { + are_installed <- sapply( + dev_deps, + FUN = rlang::is_installed + ) + if (!all(are_installed)) { + message( + "We noticed that some dev dependencies are not installed.", + "You can install them with `install_dev_deps()`." + ) + } +} From 525e97d75ce106449f62623dc6b9922705895c83 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 09:26:47 +0100 Subject: [PATCH 115/190] doc: entry in news --- NEWS.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3066b6ed..1e4de6c7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -> Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. +> Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. # 0.3.5.9000+ (dev version) @@ -22,6 +22,7 @@ + `fill_desc()` now allows to set the version (#877) + Setting the Environment variable `GOLEM_MAINTENANCE_ACTIVE` to `TRUE` active the maintenance mode of your app + `golem::run_dev()` now save all open documents before sourcing the `dev/run_dev.R` file ++ When creating an app, you'll get a message if the dev deps are not all installed (#915) ## Bug fix @@ -78,7 +79,7 @@ Update in the tests for CRAN (skip not installed + examples). + `use_code_of_conduct()` in dev script now has the contact param (#812) + All `with_test` params are now TRUE in the dev script (#801) - + + `test-golem-recommended` now has two new tests for `app_sys` and `get_golem_config` (#751) + `use_utils_ui()` `use_utils_server()` & now come with a `with_test` parameter that adds a test file for theses functions (#625 & #801) @@ -88,7 +89,7 @@ Update in the tests for CRAN (skip not installed + examples). + Every `{rstudioapi}` calls is now conditionned by the availabily of this function (#776) + `use_external_*` functions no longer suggest to "Go to" (#713, @novica) - + + `create_golem()` now comes with `with_git` parameter that can be used to initialize git repository while creating a project template + `use_recommended_tests()` now comes with `testServer` (#720). @@ -103,7 +104,7 @@ Update in the tests for CRAN (skip not installed + examples). + run_dev now has `options(shiny.port = httpuv::randomPort())` to prevent the browser from caching the CSS & JS files (#675) -+ You can now specify the path to R in `expect_running()`. ++ You can now specify the path to R in `expect_running()`. ## Bug fix @@ -119,7 +120,7 @@ Update in the tests for CRAN (skip not installed + examples). + Fixed issue with expect_running & path to R (#700, @waiteb5) -+ `expect_running()` now find R.exe on windows. ++ `expect_running()` now find R.exe on windows. + `use_recommended_tests()` no longer add `{processx}` to the `DESCRIPTION` (#710) @@ -171,7 +172,7 @@ Update in the tests for CRAN (skip not installed + examples). + `run_dev()` performs a check on golem name. -+ `sanity_check()` function has been added to check for any 'browser()' or commented #TODO / #TOFIX / #BUG in the code (#1354 @Swechhya) ++ `sanity_check()` function has been added to check for any 'browser()' or commented #TODO / #TOFIX / #BUG in the code (#1354 @Swechhya) ## New features @@ -213,7 +214,7 @@ Update in the tests for CRAN (skip not installed + examples). ## Breaking changes -+ `add_dockerfile*` function now return the `{dockerfiler}` object instead of the path to it. It allows to modify the Dockerfile object programmatically. (#493) ++ `add_dockerfile*` function now return the `{dockerfiler}` object instead of the path to it. It allows to modify the Dockerfile object programmatically. (#493) + The `get_golem_config` now first look for a `GOLEM_CONFIG_ACTIVE` before looking for `R_CONFIG_ACTIVE` (#563) @@ -222,7 +223,7 @@ Update in the tests for CRAN (skip not installed + examples). + `add_` functions no longer append to file if it already exists (#393) + `config::get()` is no longer exported to prevent namespace conflicts with `base::get()` - + + fixed issue with favicon when package is built (#387) + `use_external_*()` function don't add ext if already there (#405) @@ -300,11 +301,11 @@ Update in the tests for CRAN (skip not installed + examples). + `devtools::test()` in 03_deploy.R is now `devtools::check()` -+ modules bow have a placeholder for content ++ modules bow have a placeholder for content + Dev scripts have been rewritten and rerordered a litte bit -## Breaking changes +## Breaking changes + `invoke_js()` now takes a list of elements to send to JS (through `...`) instead of a vector (#155, @zwycl) @@ -352,13 +353,13 @@ Update in the tests for CRAN (skip not installed + examples). # golem 0.1.0 - CRAN release candidate, v2 -## New Functions +## New Functions + `get_golem_wd` allows to print the current golem working directory, and `set_golem_wd` to change it. -## Breaking changes +## Breaking changes -+ In order to work, the functions creating files need a `golem.wd`. This working directory is set by `set_golem_options` or the first time you create a file. It default to `"."`, the current directory. ++ In order to work, the functions creating files need a `golem.wd`. This working directory is set by `set_golem_options` or the first time you create a file. It default to `"."`, the current directory. + Changes in the name of the args in `set_golem_options`: `pkg_path` is now `golem_wd`, `pkg_name` is now `golem_name`, `pkg_version` is now `golem_version` @@ -372,7 +373,7 @@ Update in the tests for CRAN (skip not installed + examples). ## Changes in the way run_app and deploy files are build -+ There is now a unique framework for run_app, that allows to deploy anywhere and can accept arguments. These arguments can then be retrieved with `get_golem_options()`. ++ There is now a unique framework for run_app, that allows to deploy anywhere and can accept arguments. These arguments can then be retrieved with `get_golem_options()`. > See https://rtask.thinkr.fr/blog/shinyapp-runapp-shinyappdir-difference/ @@ -385,7 +386,7 @@ Update in the tests for CRAN (skip not installed + examples). + There is now `add_shinyserver_file` & `add_shinyappsio_file`, #40 + `add_ui_server_files()` creates an ui & server.R files. -## Small functions updates +## Small functions updates + Functions that create file(s) now automatically create folder if it's not there. Can be prevented with `dir_create = FALSE` + Functions that create file(s) can now be prevented from opening with `open = FALSE`, #75 @@ -394,25 +395,25 @@ Update in the tests for CRAN (skip not installed + examples). # golem 0.0.1.6000+ -## Changes +## Changes * `create_golem()` now switch to the newly created project * `use_git()` is not listed in `dev/01_start.R` -## Breaking changes +## Breaking changes * Renamed `add_rconnect_file()` to `add_rstudioconnect_file()` * Renamed `create_shiny_template()` to `create_golem()` * Renamed `js()` to `activate_js()` * Renamed `use_recommended_dep()` to `use_recommended_deps()` -## New functions +## New functions * `invoke_js()` allows to call JS functions from the server side. #52 # golem 0.0.1.5000 -## Changes +## Changes * The dev files are now split in three - start / dev / deploy @@ -444,7 +445,7 @@ Update in the tests for CRAN (skip not installed + examples). * `use_utils_prod` is now included in golem so you don't have to explicitly include the functions. -## Docs +## Docs * Golem now has four vignettes From 2c869d35d63985d7441b6fd0bfe68dc901425ebb Mon Sep 17 00:00:00 2001 From: Stephen Holsenbeck Date: Mon, 19 Dec 2022 17:47:15 -0500 Subject: [PATCH 116/190] Add a demo shinyapps.io snippet for the _03_deploy_ file --- inst/shinyexample/dev/03_deploy.R | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index 2f9595ef..a8e3701e 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -38,3 +38,23 @@ golem::add_dockerfile_with_renv() ## If you want to deploy to ShinyProxy golem::add_dockerfile_with_renv_shinyproxy() +## ShinyApps.io Demo Deployment Snippet +# If you want to deploy to ShinyApps.io without using the built in RStudio workflow +rsconnect::deployApp( + appName = desc::desc_get_field("Package"), + appTitle = desc::desc_get_field("Package"), + appFiles = c( + # Add any additional files unique to your app here. + "R/", + "inst/", + "data/", + "NAMESPACE", + "DESCRIPTION", + "app.R", + ".Rprofile", + ".Renviron" + ), + appId = rsconnect::deployments(".")$appID, + lint = FALSE, + forceUpdate = TRUE +) From 22f346768bbc1928bb6b575fafe1afe889136012 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 09:49:24 +0100 Subject: [PATCH 117/190] fix: removed the dot file from deployment Issue #923 --- inst/shinyexample/dev/03_deploy.R | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index a8e3701e..1f886875 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -38,8 +38,9 @@ golem::add_dockerfile_with_renv() ## If you want to deploy to ShinyProxy golem::add_dockerfile_with_renv_shinyproxy() -## ShinyApps.io Demo Deployment Snippet -# If you want to deploy to ShinyApps.io without using the built in RStudio workflow + +# Deploy to Posit Connect or ShinyApps.io +# In command line. rsconnect::deployApp( appName = desc::desc_get_field("Package"), appTitle = desc::desc_get_field("Package"), @@ -50,9 +51,7 @@ rsconnect::deployApp( "data/", "NAMESPACE", "DESCRIPTION", - "app.R", - ".Rprofile", - ".Renviron" + "app.R" ), appId = rsconnect::deployments(".")$appID, lint = FALSE, From 9ce91506e55fac4221a5cf2dd6d067001a72b5aa Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 09:53:47 +0100 Subject: [PATCH 118/190] doc: news update --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 1e4de6c7..7e9765e3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,7 @@ + Setting the Environment variable `GOLEM_MAINTENANCE_ACTIVE` to `TRUE` active the maintenance mode of your app + `golem::run_dev()` now save all open documents before sourcing the `dev/run_dev.R` file + When creating an app, you'll get a message if the dev deps are not all installed (#915) ++ 03_deploy now contains an example of sending the app to PositConnect or Shinyapps.io (#923) ## Bug fix From 85a821132aad08c21da3cdde30f86ea00ad304c1 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 10:23:26 +0100 Subject: [PATCH 119/190] hotfix: check that all boostrap fns are there #961 --- R/add_dockerfiles_renv.R | 2 +- R/boostrap_cli.R | 4 +++- R/create_golem.R | 3 ++- R/sanity_check.R | 9 +++++++-- R/utils.R | 6 ++---- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 5d6b18a6..a784db2e 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -38,7 +38,7 @@ add_dockerfile_with_renv_ <- function( } file.copy(from = lockfile, to = output_dir) - socle <- dockerfiler::dock_from_renv( + socle <- dockerfiler_dock_from_renv( lockfile = lockfile, distro = distro, FROM = FROM, diff --git a/R/boostrap_cli.R b/R/boostrap_cli.R index d26cb117..aff16776 100644 --- a/R/boostrap_cli.R +++ b/R/boostrap_cli.R @@ -10,7 +10,9 @@ check_cli_installed <- function(reason = "to have attractive command line interf cli_cat_bullet <- function(...) { check_cli_installed() - cli::cat_bullet(...) + do_if_unquiet({ + cli::cat_bullet(...) + )) } diff --git a/R/create_golem.R b/R/create_golem.R index ad013b04..4add58e5 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -240,7 +240,8 @@ create_golem <- function( if (isTRUE(open)) { if ( rlang::is_installed("rstudioapi") && - rstudioapi::isAvailable() && rstudioapi::hasFun("openProject") + rstudioapi::isAvailable() && + rstudioapi::hasFun("openProject") ) { rstudioapi::openProject(path = path) } else { diff --git a/R/sanity_check.R b/R/sanity_check.R index 6d2d5a34..3acc72fb 100644 --- a/R/sanity_check.R +++ b/R/sanity_check.R @@ -41,8 +41,13 @@ sanity_check <- function(pkg = get_golem_wd()) { if (length(source_markers) > 0) { if ( - rlang::is_installed("rstudioapi") && rstudioapi::isAvailable() && rstudioapi::hasFun("sourceMarkers")) { - rstudioapi::sourceMarkers("sanity_check", markers = source_markers) + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("sourceMarkers")) { + rstudioapi::sourceMarkers( + "sanity_check", + markers = source_markers + ) } return(source_markers) } else { diff --git a/R/utils.R b/R/utils.R index 497a4551..ef475bc1 100644 --- a/R/utils.R +++ b/R/utils.R @@ -226,11 +226,9 @@ open_or_go_to <- function( open_file ) { if ( - rstudioapi::isAvailable() && - open_file && - rstudioapi::hasFun("navigateToFile") + open_file ) { - rstudioapi::navigateToFile(where) + rstudioapi_navigateToFile(where) } else { cat_red_bullet( sprintf( From 942c998745b54457ab88185a77764ef1bda9c1b5 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 10:39:47 +0100 Subject: [PATCH 120/190] fix: hotix typo --- R/boostrap_cli.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/boostrap_cli.R b/R/boostrap_cli.R index aff16776..8f9a9eda 100644 --- a/R/boostrap_cli.R +++ b/R/boostrap_cli.R @@ -12,7 +12,7 @@ cli_cat_bullet <- function(...) { check_cli_installed() do_if_unquiet({ cli::cat_bullet(...) - )) + }) } From bf03e50fa52054de84b6d30a4929088c7538f47d Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 23 Dec 2022 11:13:57 +0100 Subject: [PATCH 121/190] typo: hotfix typo message check_dev_deps_are_installed --- R/install_dev_deps.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R index 1a0926ac..d86dc2e8 100644 --- a/R/install_dev_deps.R +++ b/R/install_dev_deps.R @@ -96,7 +96,7 @@ check_dev_deps_are_installed <- function() { ) if (!all(are_installed)) { message( - "We noticed that some dev dependencies are not installed.", + "We noticed that some dev dependencies are not installed.\n", "You can install them with `install_dev_deps()`." ) } From 5eb61fb3b51fae9424eb0a65d7c142dd8fd48b9b Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Fri, 20 Jan 2023 22:01:27 +0100 Subject: [PATCH 122/190] Update add_dockerfiles_renv.R (#976) fix : allow using `add_dockerfile_with_renv without` attachment and/or detect if package is missing --- R/add_dockerfiles_renv.R | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index a784db2e..5a13be88 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -12,11 +12,21 @@ add_dockerfile_with_renv_ <- function( update_tar_gz = TRUE # build_golem_from_source = TRUE, ) { + + if (is.null(lockfile)) { + rlang::check_installed( + "attachment", + reason = "to build a Dockerfile with automatic renv.lock creation. Use the `lockfile` parameter to pass your own `renv.lock` file." + ) + + } + + + rlang::check_installed( - "renv", + "renv", reason = "to build a Dockerfile." ) - # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on x <- suppressWarnings({ @@ -31,6 +41,7 @@ add_dockerfile_with_renv_ <- function( } if (is.null(lockfile)) { + lockfile <- attachment_create_renv_for_prod( path = source_folder, output = file.path(output_dir, "renv.lock.prod") From 3e11e48b3608a47713956d9c630c5a2d65b14237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rochette?= Date: Sat, 28 Jan 2023 18:43:11 +0100 Subject: [PATCH 123/190] fix: allow to build readme in 01_start tags: fix, doc Why? - I need to be able to commit right after running the complete "dev/01_dev.R" How? - To commit, I need the Readme.md file to be created, after I created the Readme.Rmd file. - devtools::build_readme() allows to install the package temporarily with its last version to be able to knit the Readme.Rmd in the last good conditions --- inst/shinyexample/dev/01_start.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 24eee49e..7a963694 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -39,6 +39,7 @@ golem::install_dev_deps() ## See ?usethis for more information usethis::use_mit_license("Golem User") # You can set another license here usethis::use_readme_rmd(open = FALSE) +devtools::build_readme() # Note that `contact` is required since usethis version 2.1.5 # If your {usethis} version is older, you can remove that param usethis::use_code_of_conduct(contact = "Golem User") @@ -64,4 +65,4 @@ golem::use_utils_server(with_test = TRUE) # You're now set! ---- # go to dev/02_dev.R -rstudioapi::navigateToFile("dev/02_dev.R") \ No newline at end of file +rstudioapi::navigateToFile("dev/02_dev.R") From e1142e454a6f140e93c062304c0af8573be24740 Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Sun, 29 Jan 2023 09:51:14 +0100 Subject: [PATCH 124/190] draft : Temp (#977) * Pass `require_suggets` to `attachment_create_renv_for_prod` * feat : force check_if_suggests_is_installed = FALSE in renv creation * feat : add test to check suggested package are not in renv.lock * fix : explictly use library(appname) before launching an app un Dockerfile issue : #978 * fix : update add_dockerfile_with_renv * fix : check_installed is more cleaver * fix : add {renv} to dev_deps (its already in {dockerfiler}'s deps, but it seems safer to do this. --------- Co-authored-by: Stephen Holsenbeck --- DESCRIPTION | 2 +- R/add_dockerfiles.R | 6 +-- R/add_dockerfiles_renv.R | 79 ++++++++++++++++++++++---------- R/bootstrap_attachment.R | 6 ++- R/install_dev_deps.R | 3 +- man/dockerfiles.Rd | 18 ++++++-- man/get_golem_options.Rd | 28 +++++------ tests/testthat/helper-config.R | 15 ++++++ tests/testthat/test-renv_stuff.R | 34 +++++++++----- 9 files changed, 131 insertions(+), 60 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 9d73703a..ef1e936a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -66,4 +66,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.2 +RoxygenNote: 7.2.3 diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 4901aef5..6565c7a1 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -165,7 +165,7 @@ add_dockerfile_ <- talk_once( dock$CMD( sprintf( - "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + "R -e \"options('shiny.port'=%s,shiny.host='%s');library(%3$s);%3$s::run_app()\"", port, host, read.dcf(path)[1] @@ -263,7 +263,7 @@ add_dockerfile_shinyproxy_ <- talk_once( dock$EXPOSE(3838) dock$CMD(sprintf( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"]", read.dcf(path)[1] )) dock$write(output) @@ -357,7 +357,7 @@ add_dockerfile_heroku_ <- talk_once( dock$CMD( sprintf( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"", read.dcf(path)[1] ) ) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 5a13be88..9c38a977 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -9,24 +9,21 @@ add_dockerfile_with_renv_ <- function( repos = c(CRAN = "https://cran.rstudio.com/"), expand = FALSE, extra_sysreqs = NULL, - update_tar_gz = TRUE + update_tar_gz = TRUE, + document = FALSE, + ... # build_golem_from_source = TRUE, ) { - + if (is.null(lockfile)) { rlang::check_installed( - "attachment", + c("renv","attachment"), reason = "to build a Dockerfile with automatic renv.lock creation. Use the `lockfile` parameter to pass your own `renv.lock` file." ) - + } - - - - rlang::check_installed( - "renv", - reason = "to build a Dockerfile." - ) + + # Small hack to prevent warning from rlang::lang() in tests # This should be managed in {attempt} later on x <- suppressWarnings({ @@ -41,10 +38,28 @@ add_dockerfile_with_renv_ <- function( } if (is.null(lockfile)) { - + + + if ( isTRUE(document) ){ + + cli_cat_line("You set `document = TRUE` and you did not pass your own renv.lock file,") + cli_cat_line("as a consequence {golem} will use `attachment::att_amend_desc()` to update your ") + cli_cat_line("DESCRIPTION file before creating the renv.lock file") + cli_cat_line("") + cli_cat_line("you can set `document = FALSE` to use your actual DESCRIPTION file,") + cli_cat_line("or pass you own renv.lock to use, using the `lockfile` parameter") + cli_cat_line("") + cli_cat_line("In any case be sure to have no Error or Warning at `devtools::check()`") + } + + + + lockfile <- attachment_create_renv_for_prod( path = source_folder, - output = file.path(output_dir, "renv.lock.prod") + check_if_suggests_is_installed = FALSE, document = document, + output = file.path(output_dir, "renv.lock.prod"), + ... ) } @@ -132,8 +147,10 @@ add_dockerfile_with_renv_ <- function( #' @param output_dir folder to export everything deployment related. #' @param distro One of "focal", "bionic", "xenial", "centos7", or "centos8". #' See available distributions at https://hub.docker.com/r/rstudio/r-base/. +#' @param document boolean. If TRUE (by default), DESCRIPTION file is updated using [attachment::att_amend_desc()] before creating the renv.lock file #' @param dockerfile_cmd What is the CMD to add to the Dockerfile. If NULL, the default, -#' the CMD will be `R -e "options('shiny.port'={port},shiny.host='{host}');{appname}::run_app()\` +#' the CMD will be `R -e "options('shiny.port'={port},shiny.host='{host}');library({appname});{appname}::run_app()\` +#' @param ... Other arguments to pass to [renv::snapshot()] #' @inheritParams add_dockerfile #' @rdname dockerfiles #' @export @@ -150,9 +167,11 @@ add_dockerfile_with_renv <- function( repos = c(CRAN = "https://cran.rstudio.com/"), expand = FALSE, open = TRUE, + document = TRUE, extra_sysreqs = NULL, update_tar_gz = TRUE, - dockerfile_cmd = NULL + dockerfile_cmd = NULL, + ... ) { base_dock <- add_dockerfile_with_renv_( source_folder = source_folder, @@ -165,14 +184,16 @@ add_dockerfile_with_renv <- function( repos = repos, expand = expand, extra_sysreqs = extra_sysreqs, - update_tar_gz = update_tar_gz + update_tar_gz = update_tar_gz, + document = document, + ... ) if (!is.null(port)) { base_dock$EXPOSE(port) } if (is.null(dockerfile_cmd)) { dockerfile_cmd <- sprintf( - "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + "R -e \"options('shiny.port'=%s,shiny.host='%s');library(%3$s);%3$s::run_app()\"", port, host, golem::get_golem_name() @@ -205,7 +226,7 @@ docker run -p %s:%s %s ) } -#' @inheritParams add_dockerfile +#' @inheritParams add_dockerfile_with_renv #' @rdname dockerfiles #' @export #' @export @@ -221,7 +242,9 @@ add_dockerfile_with_renv_shinyproxy <- function( expand = FALSE, extra_sysreqs = NULL, open = TRUE, - update_tar_gz = TRUE + document = TRUE, + update_tar_gz = TRUE, + ... ) { add_dockerfile_with_renv( source_folder = source_folder, @@ -238,14 +261,16 @@ add_dockerfile_with_renv_shinyproxy <- function( extra_sysreqs = extra_sysreqs, update_tar_gz = update_tar_gz, open = open, + document = document, dockerfile_cmd = sprintf( - "R -e \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"", + "R -e \"options('shiny.port'=3838,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"", golem::get_golem_name() - ) + ), + ... ) } -#' @inheritParams add_dockerfile +#' @inheritParams add_dockerfile_with_renv #' @rdname dockerfiles #' @export #' @export @@ -261,7 +286,9 @@ add_dockerfile_with_renv_heroku <- function( expand = FALSE, extra_sysreqs = NULL, open = TRUE, - update_tar_gz = TRUE + document = TRUE, + update_tar_gz = TRUE, + ... ) { add_dockerfile_with_renv( source_folder = source_folder, @@ -278,10 +305,12 @@ add_dockerfile_with_renv_heroku <- function( extra_sysreqs = extra_sysreqs, update_tar_gz = update_tar_gz, open = FALSE, + document = document, dockerfile_cmd = sprintf( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"", golem::get_golem_name() - ) + ), + ... ) apps_h <- gsub( diff --git a/R/bootstrap_attachment.R b/R/bootstrap_attachment.R index 887b6939..0f0e831d 100644 --- a/R/bootstrap_attachment.R +++ b/R/bootstrap_attachment.R @@ -3,7 +3,7 @@ check_attachment_installed <- function() { rlang::check_installed( "attachment", - version = "0.2.5", + version = "0.3.1", reason = "to build a Dockerfile." ) } @@ -12,12 +12,16 @@ attachment_create_renv_for_prod <- function( path = ".", output = "renv.lock.prod", dev_pkg = "remotes", + check_if_suggests_is_installed = FALSE, + document = FALSE, ... ) { attachment::create_renv_for_prod( path = path, output = output, dev_pkg = dev_pkg, + document = document, + check_if_suggests_is_installed = check_if_suggests_is_installed, ... ) } diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R index d86dc2e8..cffab779 100644 --- a/R/install_dev_deps.R +++ b/R/install_dev_deps.R @@ -82,6 +82,7 @@ dev_deps <- unique( "pkgload", "processx", "roxygen2", + "renv", "rsconnect", "rstudioapi", "testthat", @@ -97,7 +98,7 @@ check_dev_deps_are_installed <- function() { if (!all(are_installed)) { message( "We noticed that some dev dependencies are not installed.\n", - "You can install them with `install_dev_deps()`." + "You can install them with `golem::install_dev_deps()`." ) } } diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 6a54e1a0..04536333 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -69,9 +69,11 @@ add_dockerfile_with_renv( repos = c(CRAN = "https://cran.rstudio.com/"), expand = FALSE, open = TRUE, + document = TRUE, extra_sysreqs = NULL, update_tar_gz = TRUE, - dockerfile_cmd = NULL + dockerfile_cmd = NULL, + ... ) add_dockerfile_with_renv_shinyproxy( @@ -86,7 +88,9 @@ add_dockerfile_with_renv_shinyproxy( expand = FALSE, extra_sysreqs = NULL, open = TRUE, - update_tar_gz = TRUE + document = TRUE, + update_tar_gz = TRUE, + ... ) add_dockerfile_with_renv_heroku( @@ -101,7 +105,9 @@ add_dockerfile_with_renv_heroku( expand = FALSE, extra_sysreqs = NULL, open = TRUE, - update_tar_gz = TRUE + document = TRUE, + update_tar_gz = TRUE, + ... ) } \arguments{ @@ -154,8 +160,12 @@ default is current folder '.'} \item{distro}{One of "focal", "bionic", "xenial", "centos7", or "centos8". See available distributions at https://hub.docker.com/r/rstudio/r-base/.} +\item{document}{boolean. If TRUE (by default), DESCRIPTION file is updated using \code{\link[attachment:att_amend_desc]{attachment::att_amend_desc()}} before creating the renv.lock file} + \item{dockerfile_cmd}{What is the CMD to add to the Dockerfile. If NULL, the default, -the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}');\{appname\}::run_app()\\}} +the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}');library(\{appname\});\{appname\}::run_app()\\}} + +\item{...}{Other arguments to pass to \code{\link[renv:snapshot]{renv::snapshot()}}} } \value{ The \code{{dockerfiler}} object, invisibly. diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index 0eadfabc..6f8a2a6c 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -23,10 +23,12 @@ parameters passed to \code{run_app()}. if (interactive()) { # 1. Pass parameters directly to `run_app` - run_app( title="My Golem App", - content = "something" ) + run_app( + title = "My Golem App", + content = "something" + ) - # 2. Get the values + # 2. Get the values # 2.1 from the UI side h1(get_golem_options("title")) @@ -36,17 +38,17 @@ if (interactive()) { output$param <- renderPrint({ paste("param content = ", get_golem_options("content")) }) - + output$param_full <- renderPrint({ - get_golem_options() # list of all golem options as a list. + get_golem_options() # list of all golem options as a list. }) - + # 3. If needed, to set default value, edit `run_app` like this : run_app <- function( - title = "this", - content = "that", - ... + title = "this", + content = "that", + ... ) { with_golem_options( app = shinyApp( @@ -54,14 +56,12 @@ if (interactive()) { server = app_server ), golem_opts = list( - title = title, - content = content, - ... + title = title, + content = content, + ... ) ) } - - } } diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 5105773b..81554197 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -93,3 +93,18 @@ withr::with_dir(pkg, { ) usethis::use_mit_license("Golem") }) + + +create_deploy_folder <- function(){ +file.path( + tempdir(), + make.names( + paste0( + "deploy", + round( + runif(1, min = 0, max = 99999) + ) + ) + ) + ) +} diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R index f63449e4..07853af1 100644 --- a/tests/testthat/test-renv_stuff.R +++ b/tests/testthat/test-renv_stuff.R @@ -8,17 +8,7 @@ test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all outp add_dockerfile_with_renv_shinyproxy, add_dockerfile_with_renv_heroku )) { - deploy_folder <- file.path( - tempdir(), - make.names( - paste0( - "deploy", - round( - runif(1, min = 0, max = 99999) - ) - ) - ) - ) + deploy_folder <- create_deploy_folder() fun(output_dir = deploy_folder, open = FALSE) @@ -32,3 +22,25 @@ test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all outp } }) }) +test_that("suggested package ore not in renv prod", { + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.3.1") + with_dir(pkg, { + desc_file <- file.path("DESCRIPTION") + desc_lines <- readLines(desc_file) + # desc_lines <- c(desc_lines,"Suggests: \n idontexist") + desc_lines[desc_lines == "Suggests: "] <- "Suggests: \n idontexist," + writeLines(desc_lines,desc_file) + deploy_folder <- create_deploy_folder() + + add_dockerfile_with_renv(output_dir = deploy_folder, open = FALSE) + + base <- paste(readLines(file.path(deploy_folder,"renv.lock.prod")),collapse = " ") + expect_false(grepl(pattern = "idontexist",x = base)) + expect_true(grepl(pattern = "shiny",x = base)) + + unlink(deploy_folder, force = TRUE, recursive = TRUE) + } + ) +}) From f576d0f7a6e23331ce971f579d917939c872c090 Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Sun, 29 Jan 2023 10:20:26 +0100 Subject: [PATCH 125/190] fix : URLs are now OK # 980 (#981) --- vignettes/a_start.Rmd | 4 ++-- vignettes/b_dev.Rmd | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 0dd31ef2..6e17673c 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -124,7 +124,7 @@ golem::fill_desc( ) ``` -About [the DESCRIPTION file](https://r-pkgs.org/Metadata.html#sec-description). +About [the DESCRIPTION file](https://r-pkgs.org/description.html). ### Add `{golem}` options @@ -157,7 +157,7 @@ Create a template for tests: golem::use_recommended_tests() ``` -About [tests in a package](https://r-pkgs.org/tests.html). +About [tests in a package](https://r-pkgs.org/testing-basics.html). ### Use Recommended Packages diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index c0b41100..367484af 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -45,7 +45,7 @@ Note that the `{attachment}` package should be installed on your machine. attachment::att_amend_desc() ``` -About [package dependencies](https://r-pkgs.org/Metadata.html#sec-namespace). +About [package dependencies](https://r-pkgs.org/dependencies-mindset-background.html). ### Add modules From da7cbe73b57516466b2e37b298e502091cdf5a87 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 22 Feb 2023 15:19:41 +0100 Subject: [PATCH 126/190] Fix #993 --- inst/shinyexample/REMOVEME.Rbuildignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shinyexample/REMOVEME.Rbuildignore b/inst/shinyexample/REMOVEME.Rbuildignore index f3162bb5..9b9a10f3 100644 --- a/inst/shinyexample/REMOVEME.Rbuildignore +++ b/inst/shinyexample/REMOVEME.Rbuildignore @@ -3,4 +3,5 @@ ^data-raw$ dev_history.R ^dev$ -$run_dev.* \ No newline at end of file +$run_dev.* +^.here$ From 9c09c01bcd70315f404006728d5f5b59515e260b Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 6 Mar 2023 15:23:44 +0100 Subject: [PATCH 127/190] test: new test and deploy script --- inst/mantests/build.R | 693 ++++++++++++++++++++---------------------- 1 file changed, 332 insertions(+), 361 deletions(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 3e761bf1..668de34f 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -1,5 +1,40 @@ +# To test locally, run this in a docker container: +# docker run -d --rm -v $(pwd):/home/golem rocker/shiny:latest tail -f /dev/null +# Attach VSCode to the container +# Then run this script in the terminal + +# This script is launched internally by ThinkR every day by our GitLab CI +temp_app <- commandArgs(TRUE)[1] +if (is.na(temp_app)) { + temp_app <- "/golemmetrics" +} +cli::cat_bullet(temp_app) +if (dir.exists(temp_app)) { + unlink(temp_app, TRUE, TRUE) +} +install.packages(c("pak")) + +if (!rlang::is_installed("cli")) { + pak::pak("cli") +} + +if (!rlang::is_installed("testthat")) { + pak::pak("testthat") +} + +if (!rlang::is_installed("desc")) { + pak::pak("desc") +} + +if (!rlang::is_installed("devtools")) { + pak::pak("devtools") +} + +library(desc) +library(testthat) cat("\n") + # rstudioapi::jobRunScript(here::here("inst/mantests/build.R"), workingDir = here::here()) cat_ok <- function() cli::cat_bullet("Passed", bullet = "tick", bullet_col = "green") @@ -16,416 +51,352 @@ fakename <- sprintf( gsub("[ :-]", "", Sys.time()) ) -# Just so that I can use this script locally too, -# I set a temporary lib -cli::cat_rule("Set up for lib") +cli::cat_rule("Installing the dev version of golem") +pak::pak("thinkr-open/golem@dev") -temp_app <- file.path(tempdir(), "golemmetrics") - -if (Sys.getenv("CI", "local") == "local") { - # If I'm on the CI, we don't change the lib - temp_lib <- .libPaths() -} else { - temp_lib <- file.path(tempdir(), "temp_lib") - .libPaths(c(temp_lib, .libPaths())) -} +cli::cat_rule("Install crystalmountains") +pak::pak( + "thinkr-open/crystalmountains" +) -cli::cat_bullet(temp_app) -# This will be our golem app +cli::cat_rule("Creating a golem based app") +# Going to the temp dir and create a new golem +cli::cat_rule("Creating a golem based app") +library(golem) -if (dir.exists(temp_app)) { - unlink(temp_app, TRUE, TRUE) -} +zz <- file("all.Rout", open = "wt") +sink(zz) +sink(zz, type = "message") -dir.create(temp_lib, recursive = TRUE) +# This is our temp app -install.packages( - c("remotes", "desc", "testthat", "cli", "fs", "cranlogs", "pak"), - lib = temp_lib, - repo = "https://cran.rstudio.com/" +create_golem( + temp_app, + open = FALSE, + project_hook = crystalmountains::golem_hook ) +sink(type = "message") +sink() -cli::cat_rule("Install golem") - -library(remotes, lib.loc = temp_lib) -library(desc, lib.loc = temp_lib) -library(testthat, lib.loc = temp_lib) -library(cli, lib.loc = temp_lib) -library(fs, lib.loc = temp_lib) - -# We'll need to install golem from the current branch because -# otherwise the dependency tree breaks -# install_github( -# "ThinkR-open/golem", -# ref = Sys.getenv("GITHUB_BASE_REF", "dev"), -# force = TRUE, -# lib.loc = temp_lib -# ) - -# Installing the current version of golem -install_local( - lib.loc = temp_lib +expect_true( + dir.exists(temp_app) ) -golem::install_dev_deps( - force_install = TRUE, - lib = temp_lib +expect_true( + any(grepl( + "golem::install_dev_deps()", + readLines("all.Rout") + )) ) +unlink("all.Rout", TRUE, TRUE) -withr::with_tempdir({ - cli::cat_rule("Install crystalmountains") - - remotes::install_github( - "thinkr-open/crystalmountains", - lib.loc = temp_lib, - update = "never" - ) - - here::set_here(getwd()) - # Going to the temp dir and create a new golem - cli::cat_rule("Creating a golem based app") - library(golem) - - create_golem( - temp_app, - open = FALSE, - project_hook = crystalmountains::golem_hook - ) +golem::install_dev_deps(force = TRUE) +for (pak in golem:::dev_deps) { expect_true( - dir.exists(temp_app) + rlang::is_installed(pak) ) +} - old <- setwd(temp_app) +old <- setwd(temp_app) - here::set_here(temp_app) +usethis::use_build_ignore(".here") - usethis::use_build_ignore(".here") +cat( + readLines("DESCRIPTION"), + sep = "\n" +) - if (Sys.getenv("GITHUB_BASE_REF") == "") { - usethis::use_dev_package( - "golem", - remote = "github::ThinkR-open/golem@dev" - ) - } else { - usethis::use_dev_package( - "golem", - remote = sprintf( - "github::ThinkR-open/golem@dev", - Sys.getenv("GITHUB_BASE_REF") - ) - ) - } +usethis::use_build_ignore(".here") +usethis::use_dev_package("golem") +cat( + readLines("DESCRIPTION"), + sep = "\n" +) - cat( - readLines("DESCRIPTION"), - sep = "\n" - ) +cat_ok() - here::set_here(temp_app) +cli::cat_rule("Checking the hook has set the MIT licence") +expect_true( + file.exists("LICENSE") +) +expect_true( + desc_get("License") == "MIT + file LICENSE" +) +cat_ok() - usethis::use_build_ignore(".here") +cli::cat_rule("Checking the DESCRIPTION is correct") +expect_true( + desc_get("Package") == "golemmetrics" +) +expect_true( + desc_get("Title") == "An Amazing Shiny App" +) +expect_true( + all(desc_get_deps()$package %in% c("config", "golem", "shiny")) +) +cat_ok() + +cli::cat_rule("Checking all files are here") + +expected_files <- c( + "DESCRIPTION", + "NAMESPACE", + "R", + "R/app_config.R", + "R/app_server.R", + "R/app_ui.R", + "R/run_app.R", + "dev", + "dev/01_start.R", + "dev/02_dev.R", + "dev/03_deploy.R", + "dev/run_dev.R", + "inst", + "inst/app", + "inst/app/www", + "inst/app/www/favicon.ico", + "inst/golem-config.yml", + "man", + "man/run_app.Rd" +) +actual_files <- fs::dir_ls(recurse = TRUE) - usethis::use_dev_package("golem") +for (i in expected_files) { + expect_true(i %in% actual_files) +} +cat_ok() + +# Going through 01_start.R ---- +# +cli::cat_rule("Going through 01_start.R") +cli::cat_line() + +golem::fill_desc( + pkg = temp_app, + pkg_name = "golemmetrics", # The Name of the package containing the App + pkg_title = "A App with Metrics about 'Golem'", # The Title of the package containing the App + pkg_description = "Read metrics about {golem}.", # The Description of the package containing the App + author_first_name = "Colin", # Your First Name + author_last_name = "Fay", # Your Last Name + author_email = "colin@thinkr.fr", # Your Email + repo_url = NULL, # The URL of the GitHub Repo (optional) + pkg_version = "0.0.0.9000" # The Version of the package containing the App +) - cat( - readLines("DESCRIPTION"), - sep = "\n" - ) +cli::cat_rule("checking package name") +expect_equal( + desc_get_field("Package"), + "golemmetrics" +) +cat_ok() +cli::cat_rule("checking pkg_title name") +expect_equal( + desc_get_field("Title"), + "A App with Metrics about 'Golem'" +) +cat_ok() +cli::cat_rule("checking package name") +expect_equal( + desc_get_field("Description"), + "Read metrics about {golem}." +) +cat_ok() +cli::cat_rule("checking package name") +expect_equal( + as.character(desc_get_author()), + "Colin Fay [cre, aut]" +) +cat_ok() +cli::cat_rule("checking package version") +expect_equal( + as.character(desc_get_version()), + "0.0.0.9000" +) +cat_ok() - cat_ok() +cli::cat_rule("set_golem_options") +golem::set_golem_options() +expect_equal( + golem::get_golem_wd(), + golem::pkg_path() +) +expect_equal( + golem::get_golem_name(), + "golemmetrics" +) +expect_equal( + golem::get_golem_version(), + "0.0.0.9000" +) +expect_false( + golem::app_prod() +) - cli::cat_rule("Checking the hook has set the MIT licence") - expect_true( - file.exists("LICENSE") - ) - expect_true( - desc_get("License") == "MIT + file LICENSE" - ) - cat_ok() +cat_ok() - cli::cat_rule("Checking the DESCRIPTION is correct") - expect_true( - desc_get("Package") == "golemmetrics" - ) - expect_true( - desc_get("Title") == "An Amazing Shiny App" - ) - expect_true( - all(desc_get_deps()$package %in% c("config", "golem", "shiny")) - ) - cat_ok() - - cli::cat_rule("Checking all files are here") - - expected_files <- c( - "DESCRIPTION", - "NAMESPACE", - "R", - "R/app_config.R", - "R/app_server.R", - "R/app_ui.R", - "R/run_app.R", - "dev", - "dev/01_start.R", - "dev/02_dev.R", - "dev/03_deploy.R", - "dev/run_dev.R", - "inst", - "inst/app", - "inst/app/www", - "inst/app/www/favicon.ico", - "inst/golem-config.yml", - "man", - "man/run_app.Rd" - ) - actual_files <- fs::dir_ls(recurse = TRUE) - - for (i in expected_files) { - expect_true(i %in% actual_files) - } - cat_ok() - - # Going through 01_start.R ---- - # - cli::cat_rule("Going through 01_start.R") - cli::cat_line() - - golem::fill_desc( - pkg = temp_app, - pkg_name = "golemmetrics", # The Name of the package containing the App - pkg_title = "A App with Metrics about 'Golem'", # The Title of the package containing the App - pkg_description = "Read metrics about {golem}.", # The Description of the package containing the App - author_first_name = "Colin", # Your First Name - author_last_name = "Fay", # Your Last Name - author_email = "colin@thinkr.fr", # Your Email - repo_url = NULL, # The URL of the GitHub Repo (optional) - pkg_version = "0.0.0.9000" # The Version of the package containing the App - ) +cli::cat_rule("Create Common Files") - cli::cat_rule("checking package name") - expect_equal( - desc_get_field("Package"), - "golemmetrics" - ) - cat_ok() - cli::cat_rule("checking pkg_title name") - expect_equal( - desc_get_field("Title"), - "A App with Metrics about 'Golem'" - ) - cat_ok() - cli::cat_rule("checking package name") - expect_equal( - desc_get_field("Description"), - "Read metrics about {golem}." - ) - cat_ok() - cli::cat_rule("checking package name") - expect_equal( - as.character(desc_get_author()), - "Colin Fay [cre, aut]" - ) - cat_ok() - cli::cat_rule("checking package version") - expect_equal( - as.character(desc_get_version()), - "0.0.0.9000" - ) - cat_ok() - - cli::cat_rule("set_golem_options") - - golem::set_golem_options() - expect_equal( - golem::get_golem_wd(), - golem::pkg_path() - ) - expect_equal( - golem::get_golem_name(), - "golemmetrics" - ) - expect_equal( - golem::get_golem_version(), - "0.0.0.9000" - ) - expect_false( - golem::app_prod() - ) +# usethis::use_mit_license( "Golem User" ) - cat_ok() +expect_equal( + desc_get_field("License"), + "MIT + file LICENSE" +) +expect_true( + file.exists("LICENSE") +) +usethis::use_readme_rmd(open = FALSE) +expect_true( + file.exists("README.Rmd") +) - cli::cat_rule("Create Common Files") +usethis::use_code_of_conduct("Golem user") +expect_true( + file.exists("CODE_OF_CONDUCT.md") +) - # usethis::use_mit_license( "Golem User" ) +usethis::use_news_md(open = FALSE) +expect_true( + file.exists("NEWS.md") +) - expect_equal( - desc_get_field("License"), - "MIT + file LICENSE" - ) - expect_true( - file.exists("LICENSE") - ) - usethis::use_readme_rmd(open = FALSE) - expect_true( - file.exists("README.Rmd") - ) +cat_ok() - usethis::use_code_of_conduct("Golem user") - expect_true( - file.exists("CODE_OF_CONDUCT.md") - ) +cli::cat_rule("use_recommended") - usethis::use_news_md(open = FALSE) - expect_true( - file.exists("NEWS.md") - ) +golem::use_recommended_tests(spellcheck = FALSE) +expect_true( + dir.exists("tests") +) - cat_ok() +# golem::use_recommended_deps() - cli::cat_rule("use_recommended") +golem::use_utils_ui(with_test = TRUE) +expect_true( + file.exists("R/golem_utils_ui.R") +) +expect_true( + file.exists("tests/testthat/test-golem_utils_ui.R") +) - golem::use_recommended_tests(spellcheck = FALSE) - expect_true( - dir.exists("tests") - ) +golem::use_utils_server() +expect_true( + file.exists("R/golem_utils_server.R") +) - # golem::use_recommended_deps() +cat_ok() - golem::use_utils_ui(with_test = TRUE) - expect_true( - file.exists("R/golem_utils_ui.R") - ) - expect_true( - file.exists("tests/testthat/test-golem_utils_ui.R") - ) +# Going through 02_dev ---- +cli::cat_rule("Going through 02_dev.R") - golem::use_utils_server() - expect_true( - file.exists("R/golem_utils_server.R") - ) +cli::cat_rule("Testing usepackage") +if (!requireNamespace("cranlogs")) { + pak::pak("cranlogs") +} +usethis::use_package("cranlogs") +expect_true( + "cranlogs" %in% desc_get_deps()$package +) +cat_ok() + +cli::cat_rule("Testing modules") +golem::add_module( + name = "main", + open = FALSE, + module_template = crystalmountains::module_template, + fct = "golem_logs", + js = "golem_stars", + utils = "pretty_num" +) - cat_ok() +expect_true( + file.exists("R/mod_main.R") +) - # Going through 02_dev ---- - cli::cat_rule("Going through 02_dev.R") +expect_true( + file.exists("R/mod_main_fct_golem_logs.R") +) +write( + readLines( + system.file( + "golemlogs", + package = "crystalmountains" + ) + ), + "R/mod_main_fct_golem_logs.R", + append = TRUE +) +expect_true( + file.exists("R/mod_main_utils_pretty_num.R") +) +write( + readLines( + system.file( + "prettynum", + package = "crystalmountains" + ) + ), + "R/mod_main_utils_pretty_num.R", + append = TRUE +) +expect_true( + file.exists("inst/app/www/golem_stars.js") +) +unlink("inst/app/www/golem_stars.js", TRUE, TRUE) - cli::cat_rule("Testing usepackage") - if (!requireNamespace("cranlogs")) { - install.packages("cranlogs") - } - usethis::use_package("cranlogs") - expect_true( - "cranlogs" %in% desc_get_deps()$package - ) - cat_ok() - - cli::cat_rule("Testing modules") - golem::add_module( - name = "main", - open = FALSE, - module_template = crystalmountains::module_template, - fct = "golem_logs", - js = "golem_stars", - utils = "pretty_num" - ) +golem::document_and_reload() - expect_true( - file.exists("R/mod_main.R") - ) +cat_ok() - expect_true( - file.exists("R/mod_main_fct_golem_logs.R") - ) - write( - readLines( - system.file( - "golemlogs", - package = "crystalmountains" - ) - ), - "R/mod_main_fct_golem_logs.R", - append = TRUE - ) - expect_true( - file.exists("R/mod_main_utils_pretty_num.R") - ) - write( - readLines( - system.file( - "prettynum", - package = "crystalmountains" - ) - ), - "R/mod_main_utils_pretty_num.R", - append = TRUE - ) - expect_true( - file.exists("inst/app/www/golem_stars.js") - ) - unlink("inst/app/www/golem_stars.js", TRUE, TRUE) +golem::add_fct("helpers", open = FALSE) +expect_true( + file.exists("R/fct_helpers.R") +) +unlink("R/fct_helpers.R", TRUE, TRUE) +golem::add_utils("helpers", open = FALSE) +expect_true( + file.exists("R/utils_helpers.R") +) +unlink("R/utils_helpers.R", TRUE, TRUE) + +golem::add_js_file("script", template = crystalmountains::js_file, open = FALSE) +golem::add_js_handler("handlers", template = crystalmountains::js_handler, open = FALSE) +golem::add_css_file("custom", template = crystalmountains::css_file, open = FALSE) + +cli::cat_rule("Testing and installing package") +golem::document_and_reload() +devtools::test() +cat_ok() + +cli::cat_rule("Testing 03_dev") +devtools::check() +remotes::install_local(force = TRUE, upgrade = FALSE) +targz <- devtools::build() +remotes::install_local(targz, force = TRUE, upgrade = FALSE) + +cat_ok() + +golem::add_rstudioconnect_file() + +golem::add_dockerfile_with_renv( + repos = "https://packagemanager.rstudio.com/all/__linux__/focal/latest", + from = "rocker/shiny-verse:4.0.4", + extra_sysreqs = c("libxml2-dev"), + open = FALSE, + output_dir = "/golemmetrics/deploy", + document = FALSE +) - golem::document_and_reload() +# Restore old wd - cat_ok() +# unlink(temp_app, TRUE, TRUE) +# unlink(temp_golem, TRUE, TRUE) +# unlink(temp_lib, TRUE, TRUE) - golem::add_fct("helpers", open = FALSE) - expect_true( - file.exists("R/fct_helpers.R") - ) - unlink("R/fct_helpers.R", TRUE, TRUE) - golem::add_utils("helpers", open = FALSE) - expect_true( - file.exists("R/utils_helpers.R") - ) - unlink("R/utils_helpers.R", TRUE, TRUE) - - golem::add_js_file("script", template = crystalmountains::js_file) - golem::add_js_handler("handlers", template = crystalmountains::js_handler) - golem::add_css_file("custom", template = crystalmountains::css_file) - - cli::cat_rule("Testing and installing package") - golem::document_and_reload() - devtools::test() - cat_ok() - - cli::cat_rule("Testing 03_dev") - devtools::check() - remotes::install_local(force = TRUE) - targz <- devtools::build() - remotes::install_local(targz) - - cli::cat_rule("Testing 03_dev") - - cat_ok() - - if (Sys.info()["sysname"] == "Linux") { - golem::add_rstudioconnect_file() - golem::add_dockerfile( - repos = "https://packagemanager.rstudio.com/all/__linux__/focal/latest", - from = "rocker/shiny-verse:4.0.4", - extra_sysreqs = c("libxml2-dev"), - open = FALSE - ) - usethis::use_git() - dir.create(".git/hooks", recursive = TRUE) - file.create(".git/hooks/pre-commit") - install.packages("rsconnect") - rsconnect::writeManifest() - install.packages("knitr") - knitr::knit("README.Rmd") - } - - # Restore old wd - - # unlink(temp_app, TRUE, TRUE) - # unlink(temp_golem, TRUE, TRUE) - # unlink(temp_lib, TRUE, TRUE) - - cli::cat_rule("Completed") -}) +cli::cat_rule("Completed") From bbcc392234d7f485524f1e31085a4b2ba4d7d3ee Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 6 Mar 2023 15:44:26 +0100 Subject: [PATCH 128/190] ci: forcing the repos --- inst/mantests/build.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 668de34f..22dfb032 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -13,6 +13,8 @@ if (dir.exists(temp_app)) { unlink(temp_app, TRUE, TRUE) } +options("repos" = "https://packagemanager.posit.co/cran/__linux__/jammy/latest") + install.packages(c("pak")) if (!rlang::is_installed("cli")) { From a926b4c44619f687a0a00adf7f860b964860528b Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 7 Mar 2023 09:04:54 +0100 Subject: [PATCH 129/190] ci: updated GA --- .github/workflows/R-CMD-check.yaml | 49 +++++++++++ .github/workflows/check-standard.yaml | 112 -------------------------- 2 files changed, 49 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/R-CMD-check.yaml delete mode 100644 .github/workflows/check-standard.yaml diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 00000000..a3ac6182 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -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: + branches: [main, master] + +name: R-CMD-check + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + 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 diff --git a/.github/workflows/check-standard.yaml b/.github/workflows/check-standard.yaml deleted file mode 100644 index 0b0fa965..00000000 --- a/.github/workflows/check-standard.yaml +++ /dev/null @@ -1,112 +0,0 @@ -# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. -# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions -on: - push: - branches: - - main - - master - - dev - pull_request: - branches: - - main - - master - - dev - -name: R-CMD-check - -jobs: - R-CMD-check: - runs-on: ${{ matrix.config.os }} - - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - - strategy: - fail-fast: false - matrix: - config: - - { os: windows-latest, r: "release" } - - { os: macOS-latest, r: "release" } - - { - os: ubuntu-20.04, - r: "release", - rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest", - } - - env: - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - - steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@v1 - with: - r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - run: | - while read -r cmd - do - eval sudo $cmd - done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: | - options(crayon.enabled = TRUE) - rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} - - - name: Manual Test - if: runner.os != 'Windows' - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - run: | - source("inst/mantests/build.R") - shell: Rscript {0} - - # - name: Pushes to another repository - # uses: cpina/github-action-push-to-another-repository@main - # if: runner.os == 'Linux' - # env: - # API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} - # with: - # source-directory: "inst/golemmetrics" - # destination-github-username: "colinfay" - # destination-repository-username: "thinkr-open" - # destination-repository-name: "golemmetrics" - # user-email: contact@colinfay.me - # target-branch: main - - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main - with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check From a9051e67ca129d558055d14c7c6ae49d8917df40 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 7 Mar 2023 09:11:00 +0100 Subject: [PATCH 130/190] ci: use_dev_package in build.R --- inst/mantests/build.R | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 22dfb032..98069b56 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -373,6 +373,12 @@ golem::add_css_file("custom", template = crystalmountains::css_file, open = FALS cli::cat_rule("Testing and installing package") golem::document_and_reload() + +usethis::use_dev_package( + "golem", + remotes = "https://github.com/ThinkR-open/golem" +) + devtools::test() cat_ok() From 585cc03a782b9b14c043fc7b39e58fce5380bdea Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 10 Mar 2023 10:27:45 +0100 Subject: [PATCH 131/190] doc: only one return tag --- R/is_running.R | 1 - R/make_dev.R | 1 - man/is_running.Rd | 2 -- man/prod.Rd | 2 -- 4 files changed, 6 deletions(-) diff --git a/R/is_running.R b/R/is_running.R index 4c0c50c2..69d0e760 100644 --- a/R/is_running.R +++ b/R/is_running.R @@ -7,7 +7,6 @@ #' FALSE otherwise. #' @export #' -#' @return A boolean. #' @examples #' is_running() is_running <- function() { diff --git a/R/make_dev.R b/R/make_dev.R index ce5ff1ea..24489ad4 100644 --- a/R/make_dev.R +++ b/R/make_dev.R @@ -26,7 +26,6 @@ make_dev <- function(fun) { #' @export #' #' @rdname prod -#' @return A boolean. app_prod <- function() { getOption("golem.app.prod") %||% FALSE } diff --git a/man/is_running.Rd b/man/is_running.Rd index b7605526..adb6cee7 100644 --- a/man/is_running.Rd +++ b/man/is_running.Rd @@ -9,8 +9,6 @@ is_running() \value{ TRUE if the running app is a \code{{golem}} based app, FALSE otherwise. - -A boolean. } \description{ Note that this will return \code{TRUE} only if the application diff --git a/man/prod.Rd b/man/prod.Rd index bdabc03a..9255c15b 100644 --- a/man/prod.Rd +++ b/man/prod.Rd @@ -11,8 +11,6 @@ app_dev() } \value{ \code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} - -A boolean. } \description{ Is the app in dev mode or prod mode? From 1b4018a70e0bef7541f9f19b6bd6a9cc1f4b06bc Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 10 Mar 2023 11:31:48 +0100 Subject: [PATCH 132/190] doc: doc cleaning before CRAN release --- .Rbuildignore | 1 + R/add_dockerfiles.R | 225 ++++++++++++++++---------------- R/install_dev_deps.R | 7 +- R/pkg_tools.R | 7 +- README.Rmd | 28 +++- README.md | 77 ++++++++++- cran-comments.md | 27 ++-- man/install_dev_deps.Rd | 4 + man/pkg_tools.Rd | 3 + vignettes/f_extending_golem.Rmd | 84 ++++++------ 10 files changed, 282 insertions(+), 181 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index c460e46a..e7cb3cb1 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -22,3 +22,4 @@ ^Meta$ ^\.github$ ^revdep$ +README.html \ No newline at end of file diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index 6565c7a1..5d1c54d5 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -83,26 +83,25 @@ talk_once <- function(.f, msg = "") { #' } #' @return The `{dockerfiler}` object, invisibly. add_dockerfile <- function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/verse:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - port = 80, - host = "0.0.0.0", - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL -) { + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { add_dockerfile_( path = path, output = output, @@ -123,26 +122,25 @@ add_dockerfile <- function( add_dockerfile_ <- talk_once( function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/verse:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - port = 80, - host = "0.0.0.0", - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL - ) { + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { where <- fs_path(pkg, output) usethis_use_build_ignore( @@ -191,24 +189,23 @@ add_dockerfile_ <- talk_once( #' @export #' @rdname dockerfiles add_dockerfile_shinyproxy <- function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/verse:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL -) { + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { add_dockerfile_shinyproxy_( path = path, output = output, @@ -227,24 +224,23 @@ add_dockerfile_shinyproxy <- function( add_dockerfile_shinyproxy_ <- talk_once( function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/verse:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL - ) { + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { where <- fs_path(pkg, output) usethis_use_build_ignore(output) @@ -285,24 +281,23 @@ add_dockerfile_shinyproxy_ <- talk_once( #' @export #' @rdname dockerfiles add_dockerfile_heroku <- function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/verse:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL -) { + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { add_dockerfile_heroku_( path = path, output = output, @@ -321,24 +316,23 @@ add_dockerfile_heroku <- function( add_dockerfile_heroku_ <- talk_once( function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/verse:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL - ) { + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { where <- fs_path(pkg, output) usethis_use_build_ignore(output) @@ -409,10 +403,9 @@ golem::add_dockerfile_heroku() is not recommended anymore.\nPlease use golem::ad ) alert_build <- function( - path, - output, - build_golem_from_source -) { + path, + output, + build_golem_from_source) { cat_created(output, "Dockerfile") if (!build_golem_from_source) { cat_red_bullet( diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R index cffab779..25595856 100644 --- a/R/install_dev_deps.R +++ b/R/install_dev_deps.R @@ -28,10 +28,11 @@ #' if (interactive()) { #' install_dev_deps() #' } +#' +#' @return Used for side-effects install_dev_deps <- function( - force_install = FALSE, - ... -) { + force_install = FALSE, + ...) { if (!force_install) { if (!interactive()) { # In non interactive mode with force_install turned to FALSE, diff --git a/R/pkg_tools.R b/R/pkg_tools.R index ab3aa4b4..adf26591 100644 --- a/R/pkg_tools.R +++ b/R/pkg_tools.R @@ -1,8 +1,7 @@ # Getting the DESCRIPTION file in a data.frame daf_desc <- function( - path = ".", - entry -) { + path = ".", + entry) { as.character( unlist( unname( @@ -27,6 +26,8 @@ daf_desc <- function( #' #' @export #' @rdname pkg_tools +#' +#' @return The value of the entry in the DESCRIPTION file pkg_name <- function(path = ".") { daf_desc(path, "Package") } diff --git a/README.Rmd b/README.Rmd index 28555092..6fcea973 100644 --- a/README.Rmd +++ b/README.Rmd @@ -7,7 +7,7 @@ output: github_document ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - eval = FALSE, + eval = TRUE, comment = "#>", fig.path = "man/figures/README-", out.width = "100%", @@ -27,8 +27,27 @@ knitr::opts_chunk$set( ## About -You're reading the doc about version : ``r as.data.frame(read.dcf("DESCRIPTION"))$Version `` +You're reading the doc about version : `r pkgload::pkg_version()` +This README has been compiled on the + +```{r} +Sys.time() +``` + +Here are the test & coverage results : + +```{r} +devtools::check(quiet = TRUE) +``` + +```{r echo = FALSE} +unloadNamespace("golem") +``` + +```{r} +covr::package_coverage() +``` ## Tool series @@ -37,13 +56,14 @@ This package is part of a series of tools for Shiny, which includes: - `{golem}` - - `{shinipsum}` - - `{fakir}` - -- `{shinysnippets}` - +- `{gemstones}` - ## Resources ### The Book : - + - [paper version of the book “Engineering Production-Grade Shiny Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) ### Blog posts : @@ -69,7 +89,7 @@ This package is part of a series of tools for Shiny, which includes: - [Hands-on demonstration of {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) - useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://youtu.be/tCAan6smrjs) - `r emo::flag("France")` [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) -- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://posit.co/resources/videos/production-grade-shiny-apps-with-golem/) - `r emo::flag("France")` Rencontres R 2021 : [ Conception d'applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) - `r emo::flag("France")` [ Déploiement d'une application {shiny} dans docker avec {renv} et {golem}](https://www.youtube.com/watch?v=diCG4t76k78) diff --git a/README.md b/README.md index e70d5dda..a5feac9d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,77 @@ shiny applications. ## About -You’re reading the doc about version : `0.4.0` +You’re reading the doc about version : 0.4.0 + +This README has been compiled on the + +``` r +Sys.time() +#> [1] "2023-03-10 09:53:27 CET" +``` + +Here are the test & coverage results : + +``` r +devtools::check(quiet = TRUE) +#> ℹ Loading golem +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.0 ──── +#> Duration: 1m 23.6s +#> +#> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ +``` + +``` r +covr::package_coverage() +#> golem Coverage: 69.16% +#> R/addins.R: 0.00% +#> R/bootstrap_rstudio_api.R: 0.00% +#> R/enable_roxygenize.R: 0.00% +#> R/get_sysreqs.R: 0.00% +#> R/gobals.R: 0.00% +#> R/run_dev.R: 0.00% +#> R/sanity_check.R: 0.00% +#> R/use_files.R: 0.00% +#> R/with_opt.R: 22.58% +#> R/config.R: 28.21% +#> R/test_helpers.R: 30.26% +#> R/js.R: 43.75% +#> R/reload.R: 45.36% +#> R/use_recommended.R: 54.55% +#> R/bootstrap_desc.R: 55.56% +#> R/install_dev_deps.R: 57.14% +#> R/utils.R: 58.30% +#> R/bootstrap_attachment.R: 61.54% +#> R/add_dockerfiles.R: 74.19% +#> R/bootstrap_usethis.R: 76.56% +#> R/boostrap_fs.R: 77.78% +#> R/modules_fn.R: 80.00% +#> R/use_utils.R: 83.33% +#> R/use_favicon.R: 85.56% +#> R/desc.R: 86.25% +#> R/add_resource_path.R: 88.89% +#> R/create_golem.R: 89.47% +#> R/make_dev.R: 90.00% +#> R/add_r_files.R: 91.67% +#> R/add_files.R: 92.31% +#> R/add_rstudio_files.R: 93.10% +#> R/golem-yaml-get.R: 93.18% +#> R/bootstrap_dockerfiler.R: 93.33% +#> R/add_dockerfiles_renv.R: 93.75% +#> R/boostrap_cli.R: 100.00% +#> R/boostrap_crayon.R: 100.00% +#> R/bootstrap_pkgload.R: 100.00% +#> R/bootstrap_roxygen2.R: 100.00% +#> R/browser_button.R: 100.00% +#> R/bundle_resources.R: 100.00% +#> R/disable_autoload.R: 100.00% +#> R/golem-yaml-set.R: 100.00% +#> R/golem-yaml-utils.R: 100.00% +#> R/is_running.R: 100.00% +#> R/pkg_tools.R: 100.00% +#> R/set_golem_options.R: 100.00% +#> R/templates.R: 100.00% +``` ## Tool series @@ -28,13 +98,14 @@ This package is part of a series of tools for Shiny, which includes: - `{golem}` - - `{shinipsum}` - - `{fakir}` - -- `{shinysnippets}` - +- `{gemstones}` - ## Resources ### The Book : - + - [paper version of the book “Engineering Production-Grade Shiny Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) @@ -72,7 +143,7 @@ scratch*](https://towardsdatascience.com/production-grade-r-shiny-with-golem-pro Apps](https://youtu.be/tCAan6smrjs) - 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) - rstudio::conf(2020) : [Production-grade Shiny Apps with - golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) + golem](https://posit.co/resources/videos/production-grade-shiny-apps-with-golem/) - 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) - 🇫🇷 [Déploiement d’une application {shiny} dans docker avec {renv} et diff --git a/cran-comments.md b/cran-comments.md index 35b82c24..30c1d7ba 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,15 +1,24 @@ -## R CMD check results +# R CMD check results -0 errors | 0 warnings | 0 note +0 errors ✔ | 0 warnings ✔ | 0 notes ✔ -* This is a submission that fixes the issue from the CRAN check page. +# Revdeps +## Failed to check (7) -=> The new version of testthat fails when unloaded during the tests. This new version of golem removes the test that unloads testthat. +|package |version |error |warning |note | +|:---------------|:-------|:-----|:-------|:----| +|discoveR |3.1.2 |1 | | | +|loadeR |1.1.3 |1 | | | +|multiSight |? | | | | +|OlympicRshiny |1.0.0 |1 | | | +|shinyTempSignal |0.0.3 |1 | | | +|spatialLIBD |1.10.1 |1 | |2 | +|tripr |1.4.0 |1 | | | -## revdepcheck results +## New problems (1) -We checked 31 reverse dependencies (17 from CRAN + 14 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package. - - * We saw 0 new problems - * We failed to check 0 packages +|package |version |error |warning |note | +|:--------------------|:-------|:-----|:-------|:----| +|MainExistingDatasets|1.0.1 | |__+1__ |1 | +> This has been addressed by a PR on the git repo: https://github.com/baptisteCD/MainExistingDatasets/pull/1 \ No newline at end of file diff --git a/man/install_dev_deps.Rd b/man/install_dev_deps.Rd index 34835239..22f7fce8 100644 --- a/man/install_dev_deps.Rd +++ b/man/install_dev_deps.Rd @@ -13,6 +13,9 @@ to install them.} \item{...}{further arguments passed to the install function.} } +\value{ +Used for side-effects +} \description{ This function will run rlang::check_installed() on: \itemize{ @@ -37,4 +40,5 @@ This function will run rlang::check_installed() on: if (interactive()) { install_dev_deps() } + } diff --git a/man/pkg_tools.Rd b/man/pkg_tools.Rd index a2c40e54..d9a5869a 100644 --- a/man/pkg_tools.Rd +++ b/man/pkg_tools.Rd @@ -15,6 +15,9 @@ pkg_path() \arguments{ \item{path}{Path to use to read the DESCRIPTION} } +\value{ +The value of the entry in the DESCRIPTION file +} \description{ These are functions to help you navigate inside your project while developing diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index 24cd694c..4fcae1c6 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -15,18 +15,18 @@ knitr::opts_chunk$set( ) ``` -This Vignette discusses how you can extend `{golem}`. +This Vignette discusses how you can extend `{golem}`. ## Project Hooks ### What it is -The `create_golem()` function comes with a `project_hook` parameter, a function run just after the `{golem}` project creation. -It can be used to modify the project structure automatically just after its creation. +The `create_golem()` function comes with a `project_hook` parameter, a function run just after the `{golem}` project creation. +It can be used to modify the project structure automatically just after its creation. This allows you to define custom behavior when creating `{golem}` based app, that can be used for: -+ Adding a different front-end template in `R/app_ui.R` ++ Adding a different front-end template in `R/app_ui.R` + Changing configuration options in `inst/golem-config.yml` @@ -36,24 +36,24 @@ This allows you to define custom behavior when creating `{golem}` based app, tha + etc. -### How it works +### How it works -The function is called __after__ the default project has been created, and is executed __in the directory of the created package__. +The function is called __after__ the default project has been created, and is executed __in the directory of the created package__. Here is a rough step by step of what happens when a project is created with `{golem}`: 1. The package name is generated -1. The directory that will receive the package is created +1. The directory that will receive the package is created 1. Default `{golem}` template is copied and pasted 1. R moves to the directory of the newly created project, and runs the `project_hook` function 1. R moves back to the previous directory, and removes comments if needed -1. Project is open +1. Project is open ### Defining your own `project_hook` -The best way to extend `{golem}` project hook functionality is by defining this `project_hook` function in an external package. -This will allow this function to be used inside the `{golem}` creation RStudio project creation widget: +The best way to extend `{golem}` project hook functionality is by defining this `project_hook` function in an external package. +This will allow this function to be used inside the `{golem}` creation RStudio project creation widget: -![](rstudioprojecthook.png) +![](rstudioprojecthook.png) > Note that inside this widget, the function should be explicitely namespaced (pkg::fun) @@ -76,7 +76,7 @@ no_dev <- function(path, package_name, ...) { create_golem("ici", project_hook = no_dev) ``` -This one will create a CSS: +This one will create a CSS: ```{r} new_css <- function(path, package_name, ...) { @@ -103,12 +103,12 @@ create_golem("ici", project_hook = new_css) ### What it is Module templates are a way to define your own content for creating the module script in `R/`. -It allows to extend `{golem}` module template functionality by creating your own content inside the module file. +It allows to extend `{golem}` module template functionality by creating your own content inside the module file. -### How it works +### How it works -The function is called after the file(s) creation. -Here is a step by step of what happens when the `add_module` function is called: +The function is called after the file(s) creation. +Here is a step by step of what happens when the `add_module` function is called: 1. Name is created, and so is the `R/` directory if needed. 1. The fct_ and utils_ files are created if necessary @@ -121,9 +121,9 @@ Here is a step by step of what happens when the `add_module` function is called: ### Defining your own `module_template` -You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. +You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. -Module template functions will receive, by default, the following parameters from `add_modules()`. +Module template functions will receive, by default, the following parameters from `add_modules()`. + `name`: the name of the module + `path`: the path to the file in R/ @@ -157,10 +157,10 @@ golem::add_module(name = "custom", module_template = my_other_tmpl) JavaScript, CSS and Sass template allow to use your own functions to add code when creating JavaScript files, JavaScript handlers and CSS or Sass files. These templates work inside `add_js_file()`, `add_js_handler()`, `add_css_file()` and `add_sass_file()`. -### How it works +### How it works -The function is called after the file creation. -Here is a step by step of what happens when these functions are called: +The function is called after the file creation. +Here is a step by step of what happens when these functions are called: 1. Name is created 1. The path is generated @@ -172,11 +172,11 @@ Here is a step by step of what happens when these functions are called: ### Defining your own `template` -You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. +You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. -File template functions will receive, by default, the following parameters from the `add_*()` function. +File template functions will receive, by default, the following parameters from the `add_*()` function. -+ `path`: the path to the file ++ `path`: the path to the file + `...` further arguments These parameters might not be used inside your own function, but __they need to be set in the function skeleton__, for compatibility reasons. @@ -202,7 +202,7 @@ golem::add_css_file(name = "custom", template = my_tmpl) ### What it is -From time to time, you need your application to be unavailbel: database update, API changes, etc. +From time to time, you need your application to be unavailble: database update, API changes, etc. In order to keep your app running but make it unavailable, you can use a __maintenance mode__. When this maintenance mode is turned on, your application will be paused and a specific page will be displayed to your users. @@ -210,9 +210,9 @@ When this maintenance mode is turned on, your application will be paused and a s ### How to set the maintenance mode -The maintenance mode will be turned on whenever the R process detects that the `GOLEM_MAINTENANCE_ACTIVE` environment variable is set to TRUE. +The maintenance mode will be turned on whenever the R process detects that the `GOLEM_MAINTENANCE_ACTIVE` environment variable is set to TRUE. -To visualise the maintenance page locally, you can run the following: +To visualize the maintenance page locally, you can run the following: ```{r eval = FALSE} withr::with_envvar( @@ -233,9 +233,9 @@ golem::run_dev() -If you're deploying on Posit Connect, you can set this variable in the setup panel. +If you're deploying on Posit Connect, you can set this variable in the setup panel. -If in command line, you can also do +If in command line, you can also do ``` export GOLEM_MAINTENANCE_ACTIVE=TRUE && Rscript -e "mygolem::run_app()" @@ -244,18 +244,17 @@ export GOLEM_MAINTENANCE_ACTIVE=TRUE && Rscript -e "mygolem::run_app()" ### The maintenance page -`{golem}` comes with a default maintenance page, but you can override it and use your own custom page. +`{golem}` comes with a default maintenance page, but you can override it and use your own custom page. In order to use your own page, you need to pass either an `html_document` or a `tagList` to the `with_golem_options` function in `run_app.R`: ```{r} run_app <- function( - onStart = NULL, - options = list(), - enableBookmarking = NULL, - uiPattern = "/", - ... -) { + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ...) { with_golem_options( app = shinyApp( ui = app_ui, @@ -276,16 +275,15 @@ run_app <- function( } ``` -or: +or: ```{r} run_app <- function( - onStart = NULL, - options = list(), - enableBookmarking = NULL, - uiPattern = "/", - ... -) { + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ...) { with_golem_options( app = shinyApp( ui = app_ui, From 08f050f65074102057df39c1228810e2ad95b3e6 Mon Sep 17 00:00:00 2001 From: vincent guyader Date: Wed, 7 Dec 2022 13:19:03 +0100 Subject: [PATCH 133/190] Fix 944 and correct CI * fix #944 force lowercase for package name in Dockerfile * check_is_installed is back again instead of fs_file_copy for dockerfile creation * set version to 0.3.5.9001 --- NEWS.md | 372 +++++++++++++++++++-------------------- R/add_dockerfiles_renv.R | 160 ++++++++--------- R/utils.R | 42 +++++ 3 files changed, 303 insertions(+), 271 deletions(-) diff --git a/NEWS.md b/NEWS.md index 7e9765e3..053a765b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,42 +1,38 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. -# 0.3.5.9000+ (dev version) - -## Soft deprecated - -## Hard deprecated +# 0.4.0 ## New functions -+ Add `add_partial_html_template()` to create a partial html template, with "just" a div and a `{{ }}` (@nathansquan #858). - +- Add `add_partial_html_template()` to create a partial html template, with only a div and a `{{ }}` (@nathansquan #858). ## New features / user visible changes -+ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}`, `{fs}`, `{usethis}`, `{cli}`, `{crayon}` -+ Soft dependency check is now done via `rlang::check_installed()` (#835) -+ `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. -+ `{golem}` now depends on `{rlang}` version >= 1.0.0 -+ Functions that print to the console are now quiet if `options("golem.quiet" = TRUE)`, #793 -+ Small documentation update in dockerfile related functions (#939) -+ `fill_desc()` now allows to set the version (#877) -+ Setting the Environment variable `GOLEM_MAINTENANCE_ACTIVE` to `TRUE` active the maintenance mode of your app -+ `golem::run_dev()` now save all open documents before sourcing the `dev/run_dev.R` file -+ When creating an app, you'll get a message if the dev deps are not all installed (#915) -+ 03_deploy now contains an example of sending the app to PositConnect or Shinyapps.io (#923) +- Dev hard dependencies have been moved to soft dependencies. You can see the list with `golem:::dev_deps`. They can be installed via `golem::install_dev_deps()`. +- Soft dependency check is now done via `rlang::check_installed()` (#835) +- `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. +- `{golem}` now depends on `{rlang}` version >= 1.0.0 +- Functions that print to the console are now quiet if `options("golem.quiet" = TRUE)`, #793 +- Small documentation update in dockerfile related functions (#939) +- `fill_desc()` now allows to set the version (#877) +- Setting the Environment variable `GOLEM_MAINTENANCE_ACTIVE` to `TRUE` active the maintenance mode of your app +- `golem::run_dev()` now save all open documents before sourcing the `dev/run_dev.R` file +- When creating an app, you'll get a message if the dev deps are not all installed (#915) +- 03_deploy now contains an example of sending the app to PositConnect or Shinyapps.io (#923) ## Bug fix -+ The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) -+ The Deprecation message for `use_recommended_deps` no longer suggest to user `use_recommended_deps` (#900) -+ The setting of the config file has been unified so that we are sure to keep the !expr in `golem_set_wd()`, and the codebase has been simplified for this (#709). -+ The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) -+ The typo in `install.packages()` in 02_dev.R has been corrected (@asiripanich) -+ `add_dockerfile_with_renv()` now works well with uppercase in package name -+ improve `get_golem_options()` documentation -## Internal changes +- The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) +- The Deprecation message for `use_recommended_deps` no longer suggest to user `use_recommended_deps` (#900) +- The setting of the config file has been unified so that we are sure to keep the !expr in `golem_set_wd()`, and the codebase has been simplified for this (#709). +- The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) +- The typo in `install.packages()` in 02_dev.R has been corrected (@asiripanich) +- `add_dockerfile_with_renv()` now works well with uppercase in package name +- improve `get_golem_options()` documentation +## Internal changes +add_dockerfile_with_renv now works well with uppercase in package name # golem 0.3.5 @@ -50,88 +46,87 @@ Update in the tests for CRAN (skip not installed + examples). ## New functions -+ `add_dockerfile_with_renv()`, `add_dockerfile_with_renv_heroku()` and `add_dockerfile_with_renv_shinyproxy()` build Dockerfiles that rely on `{renv}` +- `add_dockerfile_with_renv()`, `add_dockerfile_with_renv_heroku()` and `add_dockerfile_with_renv_shinyproxy()` build Dockerfiles that rely on `{renv}` ### Soft deprecated -+ `add_dockerfile`, `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` now recommend to switch to their `_with_renv_` counterpart - +- `add_dockerfile`, `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` now recommend to switch to their `_with_renv_` counterpart # golem 0.3.2 (CRAN VERSION) ### Soft deprecated -+ `use_recommended_deps()` is now soft deprecated (#786) +- `use_recommended_deps()` is now soft deprecated (#786) ### Hard deprecated -+ The `html` parameter in `expect_html_equal()` is no longer in use (#55). +- The `html` parameter in `expect_html_equal()` is no longer in use (#55). ## New functions -+ `add_sass_file()` creates a .sass file in inst/app/www (#768) +- `add_sass_file()` creates a .sass file in inst/app/www (#768) -+ `use_module_test()` creates a test skeleton for a module (#725) +- `use_module_test()` creates a test skeleton for a module (#725) ## New features -+ The `02_dev.R` file now suggests using `attachment::att_amend_desc()` (#787) +- The `02_dev.R` file now suggests using `attachment::att_amend_desc()` (#787) -+ `use_code_of_conduct()` in dev script now has the contact param (#812) +- `use_code_of_conduct()` in dev script now has the contact param (#812) -+ All `with_test` params are now TRUE in the dev script (#801) +- All `with_test` params are now TRUE in the dev script (#801) -+ `test-golem-recommended` now has two new tests for `app_sys` and `get_golem_config` (#751) +- `test-golem-recommended` now has two new tests for `app_sys` and `get_golem_config` (#751) -+ `use_utils_ui()` `use_utils_server()` & now come with a `with_test` parameter that adds a test file for theses functions (#625 & #801) +- `use_utils_ui()` `use_utils_server()` & now come with a `with_test` parameter that adds a test file for theses functions (#625 & #801) -+ `{golem}` now checks if a module exists before adding a module related file (#779) +- `{golem}` now checks if a module exists before adding a module related file (#779) -+ Every `{rstudioapi}` calls is now conditionned by the availabily of this function (#776) +- Every `{rstudioapi}` calls is now conditionned by the availabily of this function (#776) -+ `use_external_*` functions no longer suggest to "Go to" (#713, @novica) +- `use_external_*` functions no longer suggest to "Go to" (#713, @novica) -+ `create_golem()` now comes with `with_git` parameter that can be used to initialize git repository while creating a project template +- `create_golem()` now comes with `with_git` parameter that can be used to initialize git repository while creating a project template -+ `use_recommended_tests()` now comes with `testServer` (#720). +- `use_recommended_tests()` now comes with `testServer` (#720). -+ `expect_html_equal()` now uses `testthat::expect_snapshot()` (#55). +- `expect_html_equal()` now uses `testthat::expect_snapshot()` (#55). -+ `add_modules()`, `add_fct()` and `add_utils()` now come with a `with_test` parameter that can be turned on to add a test file to the module (#719 & #141) +- `add_modules()`, `add_fct()` and `add_utils()` now come with a `with_test` parameter that can be turned on to add a test file to the module (#719 & #141) -+ /!\ All docker related functions have been moved to `{dockerfiler}`. This is more or less a breaking change, cause you'll need to install `{dockerfiler}` > 0.1.4 in order to build the Dockerfile __but__ `{golem}` will ask you to install `{dockerfiler}` > 0.1.4 if it can't find it, (#412) +- /!\ All docker related functions have been moved to `{dockerfiler}`. This is more or less a breaking change, cause you'll need to install `{dockerfiler}` > 0.1.4 in order to build the Dockerfile **but** `{golem}` will ask you to install `{dockerfiler}` > 0.1.4 if it can't find it, (#412) -+ Modules ID no longer contain an `_ui_` element, (#651, @MargotBr) +- Modules ID no longer contain an `_ui_` element, (#651, @MargotBr) -+ run_dev now has `options(shiny.port = httpuv::randomPort())` to prevent the browser from caching the CSS & JS files (#675) +- run_dev now has `options(shiny.port = httpuv::randomPort())` to prevent the browser from caching the CSS & JS files (#675) -+ You can now specify the path to R in `expect_running()`. +- You can now specify the path to R in `expect_running()`. ## Bug fix -+ Fixed a bug in the printing of the htmlTemplate code (#827) +- Fixed a bug in the printing of the htmlTemplate code (#827) -+ We now require the correct `{usethis}` version (822) +- We now require the correct `{usethis}` version (822) -+ `golem::amend_config()` now keeps the `!expr` (#709, @teofiln) +- `golem::amend_config()` now keeps the `!expr` (#709, @teofiln) -+ recommended tests now use `expect_type()` instead of `expect_is`, which was deprecated from `{testthat}` (#671) +- recommended tests now use `expect_type()` instead of `expect_is`, which was deprecated from `{testthat}` (#671) -+ Fixed check warning when using `golem::use_utils_server()` (#678), +- Fixed check warning when using `golem::use_utils_server()` (#678), -+ Fixed issue with expect_running & path to R (#700, @waiteb5) +- Fixed issue with expect_running & path to R (#700, @waiteb5) -+ `expect_running()` now find R.exe on windows. +- `expect_running()` now find R.exe on windows. -+ `use_recommended_tests()` no longer add `{processx}` to the `DESCRIPTION` (#710) +- `use_recommended_tests()` no longer add `{processx}` to the `DESCRIPTION` (#710) -+ `bundle_resource()` does not include empty stylesheet anymore (#689, @erikvona) +- `bundle_resource()` does not include empty stylesheet anymore (#689, @erikvona) ## Internal changes -+ Create `{golem}` is more robust and now comes with an `overwrite` argument (#777) +- Create `{golem}` is more robust and now comes with an `overwrite` argument (#777) -+ `{testthat}` and `{rlang}` are no longer hard dependencies (#742) +- `{testthat}` and `{rlang}` are no longer hard dependencies (#742) # golem 0.3.1 (CRAN Version) @@ -139,316 +134,315 @@ Update in the tests for CRAN (skip not installed + examples). ### `add_*` -+ You can now create a skeleton for a Shiny input binding using the `golem::add_js_binding("name")` function (#452, @DivadNojnarg) +- You can now create a skeleton for a Shiny input binding using the `golem::add_js_binding("name")` function (#452, @DivadNojnarg) -+ You can now create a skeleton for a Shiny output binding using the `golem::add_js_output_binding("name")` function (@DivadNojnarg) +- You can now create a skeleton for a Shiny output binding using the `golem::add_js_output_binding("name")` function (@DivadNojnarg) -+ `add_html_template()` creates an htmlTemplate. +- `add_html_template()` creates an htmlTemplate. ### `use_*` -+ `use_external_file()` allows to add any file to the `www` folder, `use_external_css_file()`, `use_external_html_template()`, and `use_external_js_file()` will download them from a URL (#295, #491). +- `use_external_file()` allows to add any file to the `www` folder, `use_external_css_file()`, `use_external_html_template()`, and `use_external_js_file()` will download them from a URL (#295, #491). -+ `use_internal_css_file()`, `use_internal_file()`, `use_internal_html_template()`, `use_internal_js_file()` functions allow to any file from the current computer to the `www` folder (@KasperThystrup, #529) +- `use_internal_css_file()`, `use_internal_file()`, `use_internal_html_template()`, `use_internal_js_file()` functions allow to any file from the current computer to the `www` folder (@KasperThystrup, #529) ### Tests helper -+ `expect_running()` expects the current shiny app to be running. +- `expect_running()` expects the current shiny app to be running. ### Hooks -+ Every `{golem}` project now have a `project_hook` that is launched after the project creation. +- Every `{golem}` project now have a `project_hook` that is launched after the project creation. -+ `module_template()` is the default function for `{golem}` module creation. Users will now be able to define a custom `module_template()` function for `add_module()`, allowing to extend `{golem}` with your own module creation function. See ?golem::module_template for more info (#365) +- `module_template()` is the default function for `{golem}` module creation. Users will now be able to define a custom `module_template()` function for `add_module()`, allowing to extend `{golem}` with your own module creation function. See ?golem::module_template for more info (#365) -+ `add_js_` and `add_css_` functions now have a template function, allowing to pass a file constructor. +- `add_js_` and `add_css_` functions now have a template function, allowing to pass a file constructor. ### Misc -+ `is_running()` checks if the current running application is a `{golem}` based application (#366) +- `is_running()` checks if the current running application is a `{golem}` based application (#366) -+ `utils_ui.R` now contains a "make_action_button()" function (#457, @DivadNojnarg) +- `utils_ui.R` now contains a "make_action_button()" function (#457, @DivadNojnarg) -+ `run_dev()` launches the `run_dev.R` script (#478, @KoderKow) +- `run_dev()` launches the `run_dev.R` script (#478, @KoderKow) -+ `run_dev()` performs a check on golem name. +- `run_dev()` performs a check on golem name. -+ `sanity_check()` function has been added to check for any 'browser()' or commented #TODO / #TOFIX / #BUG in the code (#1354 @Swechhya) +- `sanity_check()` function has been added to check for any 'browser()' or commented #TODO / #TOFIX / #BUG in the code (#1354 @Swechhya) ## New features -+ The modules are now created with the new skeleton when the installed version of `{shiny}` is >= 1.5.0. +- The modules are now created with the new skeleton when the installed version of `{shiny}` is >= 1.5.0. -+ `use_external_*()` function don't open files by default (#404) +- `use_external_*()` function don't open files by default (#404) -+ `use_recommended_tests*()` now calls ` use_spell_check()` (#430) +- `use_recommended_tests*()` now calls ` use_spell_check()` (#430) -+ The `02_dev.R` now includes more CI links +- The `02_dev.R` now includes more CI links -+ `golem::expect_running()` is now bundled in default tests +- `golem::expect_running()` is now bundled in default tests -+ Default tests now test for functions formals (#437) +- Default tests now test for functions formals (#437) -+ You can now pass arguments to internal `roxygenise()` & `load_all()` (#467) +- You can now pass arguments to internal `roxygenise()` & `load_all()` (#467) -+ `Bundle_resources()` now handle subfolders (#446) +- `Bundle_resources()` now handle subfolders (#446) -+ `run_app()` now includes the default arguments of `shinyApp()` (#254, @chasemc) +- `run_app()` now includes the default arguments of `shinyApp()` (#254, @chasemc) -+ `create_golem()` now adds strict dependency versions (#466) +- `create_golem()` now adds strict dependency versions (#466) -+ `{golem}` app now comes with a meta tags "app-builder", which default to "golem", and that can be changed or turn off in `bundle_resources()`. +- `{golem}` app now comes with a meta tags "app-builder", which default to "golem", and that can be changed or turn off in `bundle_resources()`. -+ `with_golem_options` can now explicit calls `print` on the `app` object, solving some issues with benchmarking the application. This explicit print can be turned off by setting `print` to FALSE in `with_golem_options` (#148) +- `with_golem_options` can now explicit calls `print` on the `app` object, solving some issues with benchmarking the application. This explicit print can be turned off by setting `print` to FALSE in `with_golem_options` (#148) -+ `dockerignore` is now available. +- `dockerignore` is now available. -+ The `add_helpers` and `add_utils` now have roxygen comments (Richard Pilbery, #330) +- The `add_helpers` and `add_utils` now have roxygen comments (Richard Pilbery, #330) -+ `dev/03_dev.R` now has `devtools::build()` (#603) +- `dev/03_dev.R` now has `devtools::build()` (#603) -+ `detach_all_attached()` is now silent (#605) +- `detach_all_attached()` is now silent (#605) ## Soft deprecated -+ `add_ui_server_files()` is now signaled as deprecated. Please comment on https://github.com/ThinkR-open/golem/issues/445 if you want it to be kept inside the package +- `add_ui_server_files()` is now signaled as deprecated. Please comment on https://github.com/ThinkR-open/golem/issues/445 if you want it to be kept inside the package ## Breaking changes -+ `add_dockerfile*` function now return the `{dockerfiler}` object instead of the path to it. It allows to modify the Dockerfile object programmatically. (#493) +- `add_dockerfile*` function now return the `{dockerfiler}` object instead of the path to it. It allows to modify the Dockerfile object programmatically. (#493) -+ The `get_golem_config` now first look for a `GOLEM_CONFIG_ACTIVE` before looking for `R_CONFIG_ACTIVE` (#563) +- The `get_golem_config` now first look for a `GOLEM_CONFIG_ACTIVE` before looking for `R_CONFIG_ACTIVE` (#563) ## Bug fix -+ `add_` functions no longer append to file if it already exists (#393) +- `add_` functions no longer append to file if it already exists (#393) -+ `config::get()` is no longer exported to prevent namespace conflicts with `base::get()` +- `config::get()` is no longer exported to prevent namespace conflicts with `base::get()` -+ fixed issue with favicon when package is built (#387) +- fixed issue with favicon when package is built (#387) -+ `use_external_*()` function don't add ext if already there (#405) +- `use_external_*()` function don't add ext if already there (#405) -+ `create_golem` function does not modify any existing file (#423, @antoine-sachet) +- `create_golem` function does not modify any existing file (#423, @antoine-sachet) -+ `add_resources_path()` now correctly handles empty folder (#395) +- `add_resources_path()` now correctly handles empty folder (#395) -+ test for app launching is now skipped if not interactive() +- test for app launching is now skipped if not interactive() -+ `add_utils` and `add_fct` now print to the console (#427, @novica) +- `add_utils` and `add_fct` now print to the console (#427, @novica) -+ Multiple CRAN repo are now correctly passed to the Dockerfile (#462) +- Multiple CRAN repo are now correctly passed to the Dockerfile (#462) -+ app_config, DESC and golem-config.yml are now updated whenever you change the name of the package using a golem function (#469 ) +- app_config, DESC and golem-config.yml are now updated whenever you change the name of the package using a golem function (#469 ) -+ `test_recommended` now work in every case (hopefully) +- `test_recommended` now work in every case (hopefully) -- `usethis::use_mit_license` does not have the `name` argument anymore so if fits new version of `{usethis}` (#594) +* `usethis::use_mit_license` does not have the `name` argument anymore so if fits new version of `{usethis}` (#594) -- Typo fix preventing `invoke_js("prompt")` and `invoke_js("confirm")` to work (#606) +* Typo fix preventing `invoke_js("prompt")` and `invoke_js("confirm")` to work (#606) ## Internal changes -+ `document_and_reload()` now has `export_all = FALSE,helpers = FALSE,attach_testthat = FALSE`, allowing the function to behave more closely to what library() does (#399) +- `document_and_reload()` now has `export_all = FALSE,helpers = FALSE,attach_testthat = FALSE`, allowing the function to behave more closely to what library() does (#399) -+ Dockerfile generation now removes the copied file and tar.gz +- Dockerfile generation now removes the copied file and tar.gz # golem 0.2.1 ## New functions -+ `add_dockerfile()` was completely refactored. It now starts from r-ver, uses explicit package versions from you local machine, and tries to set as much System Requirements as possible by using `{sysreq}`, and parses and installs the Remotes tag from the DESCRIPTION (#189, #175) - -+ `add_dockerfile()` allow now to directly use the source of the package by mounting the source folder in the container and running `remotes::install_local()` +- `add_dockerfile()` was completely refactored. It now starts from r-ver, uses explicit package versions from you local machine, and tries to set as much System Requirements as possible by using `{sysreq}`, and parses and installs the Remotes tag from the DESCRIPTION (#189, #175) -+ `add_dockerfile()` now builds the tar.gz (#273) +- `add_dockerfile()` allow now to directly use the source of the package by mounting the source folder in the container and running `remotes::install_local()` -+ `add_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). +- `add_dockerfile()` now builds the tar.gz (#273) -+ We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). +- `add_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). -+ New JavaScript functions to use alert, prompt and confirm (#108, @zwycl) +- We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). -+ `use_external_js_file` and `use_external_css_file` are designed to download .js and .css file off the web to the appropriate directory (#130, @zwycl) +- New JavaScript functions to use alert, prompt and confirm (#108, @zwycl) +- `use_external_js_file` and `use_external_css_file` are designed to download .js and .css file off the web to the appropriate directory (#130, @zwycl) ## New features -+ `{golem}` now comes with an internal config file. Please refer to the `config` Vignette for more information. +- `{golem}` now comes with an internal config file. Please refer to the `config` Vignette for more information. -+ `bundle_resources()` comes with every new app and bundles all the css and js files you put inside the `inst/app/www` folder, by matchine the file extension. +- `bundle_resources()` comes with every new app and bundles all the css and js files you put inside the `inst/app/www` folder, by matchine the file extension. -+ There is now an `app_sys()` function, which is a wrapper around `system.file(..., package = "myapp")` (#207, @novica) +- There is now an `app_sys()` function, which is a wrapper around `system.file(..., package = "myapp")` (#207, @novica) -+ `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157) +- `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157) -+ You can now create a golem without any comment (#171, @ArthurData) +- You can now create a golem without any comment (#171, @ArthurData) -+ The default `app_ui()` now has a `request` parameter, to natively handle bookmarking. +- The default `app_ui()` now has a `request` parameter, to natively handle bookmarking. -+ `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157). It also uses `get_golem_wd()` as a default path, to be consistent with the rest of `{golem}` (#219, @j450h1) +- `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157). It also uses `get_golem_wd()` as a default path, to be consistent with the rest of `{golem}` (#219, @j450h1) -+ `add_module` now allows to create and `fct_` and an `utils_` file (#154, @novica) +- `add_module` now allows to create and `fct_` and an `utils_` file (#154, @novica) -+ `golem::detach_all_attached()` is now silent (#186, @annakau) +- `golem::detach_all_attached()` is now silent (#186, @annakau) -+ There is now a series of addins for going to a specific golem file (#212, @novica), and also to wrap a selected text into `ns()` (#143, @kokbent) +- There is now a series of addins for going to a specific golem file (#212, @novica), and also to wrap a selected text into `ns()` (#143, @kokbent) -+ Creation of a golem project is now a little bit more talkative (#63, @novica) +- Creation of a golem project is now a little bit more talkative (#63, @novica) -+ golem apps now have a title tag in the header by default, (#172, @novica) +- golem apps now have a title tag in the header by default, (#172, @novica) -+ The `rsconnect` folder is now added to `.Rbuildignore` (#244) +- The `rsconnect` folder is now added to `.Rbuildignore` (#244) -+ `devtools::test()` in 03_deploy.R is now `devtools::check()` +- `devtools::test()` in 03_deploy.R is now `devtools::check()` -+ modules bow have a placeholder for content +- modules bow have a placeholder for content -+ Dev scripts have been rewritten and rerordered a litte bit +- Dev scripts have been rewritten and rerordered a litte bit ## Breaking changes -+ `invoke_js()` now takes a list of elements to send to JS (through `...`) instead of a vector (#155, @zwycl) +- `invoke_js()` now takes a list of elements to send to JS (through `...`) instead of a vector (#155, @zwycl) -+ `get_dependencies` was removed from this package, please use `desc::desc_get_deps()` instead (#251) +- `get_dependencies` was removed from this package, please use `desc::desc_get_deps()` instead (#251) -+ `{golem}` now uses `here::here()` to determine the default working directory (#287) +- `{golem}` now uses `here::here()` to determine the default working directory (#287) -+ Modules used to be exported by default. You now have to specify it when creating the modules (#144) +- Modules used to be exported by default. You now have to specify it when creating the modules (#144) -+ `run_app()` is no longer explicitely namespaced in the run_dev script (#267) +- `run_app()` is no longer explicitely namespaced in the run_dev script (#267) -+ JavaScript files now default to having `$(document).ready()` (#227) +- JavaScript files now default to having `$(document).ready()` (#227) -+ Every filesystem manipulation is now done with `{fs}`. That should be pretty transparent for most users but please open an issue if it causes problem (#285) +- Every filesystem manipulation is now done with `{fs}`. That should be pretty transparent for most users but please open an issue if it causes problem (#285) ## Bug fix -+ The Dockerfile is now correctly added to .Rbuildignore (#81) +- The Dockerfile is now correctly added to .Rbuildignore (#81) -+ The dockerfile for shinyproxy no longer has a typo (#156, @fmmattioni) +- The dockerfile for shinyproxy no longer has a typo (#156, @fmmattioni) -+ `normalizePath()` now has a correct winlash (@kokbent) +- `normalizePath()` now has a correct winlash (@kokbent) -+ spellcheck in files (@privefl) +- spellcheck in files (@privefl) -+ Message to link to `golem_add_external_resources()` is now conditional to R being in a golem project (#167, @novica) +- Message to link to `golem_add_external_resources()` is now conditional to R being in a golem project (#167, @novica) -+ Better error on missing name in add_*, (#120, @novica) +- Better error on missing name in add\_\*, (#120, @novica) -+ When adding file, the extension is now ignored if provided by the user (#231) +- When adding file, the extension is now ignored if provided by the user (#231) -+ The dots R/run_app.R are now documented by default (#243) +- The dots R/run_app.R are now documented by default (#243) -+ Bug fix of the pkgdown website (#180) +- Bug fix of the pkgdown website (#180) -+ `{golem}` now correctly handles command line creation of projet inside the current directory (#248) +- `{golem}` now correctly handles command line creation of projet inside the current directory (#248) -+ The test are now more robust when it comes to random name generation (#281) +- The test are now more robust when it comes to random name generation (#281) ## Internal changes -+ We no longer depend on `{stringr}` (#201, @TomerPacific) +- We no longer depend on `{stringr}` (#201, @TomerPacific) -+ get_golem_wd() is now used everywhere in `{golem}` (#237, @felixgolcher) +- get_golem_wd() is now used everywhere in `{golem}` (#237, @felixgolcher) -# golem 0.1.0 - CRAN release candidate, v2 +# golem 0.1.0 - CRAN release candidate, v2 ## New Functions -+ `get_golem_wd` allows to print the current golem working directory, and `set_golem_wd` to change it. +- `get_golem_wd` allows to print the current golem working directory, and `set_golem_wd` to change it. ## Breaking changes -+ In order to work, the functions creating files need a `golem.wd`. This working directory is set by `set_golem_options` or the first time you create a file. It default to `"."`, the current directory. +- In order to work, the functions creating files need a `golem.wd`. This working directory is set by `set_golem_options` or the first time you create a file. It default to `"."`, the current directory. -+ Changes in the name of the args in `set_golem_options`: `pkg_path` is now `golem_wd`, `pkg_name` is now `golem_name`, `pkg_version` is now `golem_version` +- Changes in the name of the args in `set_golem_options`: `pkg_path` is now `golem_wd`, `pkg_name` is now `golem_name`, `pkg_version` is now `golem_version` ## Internal changes -+ The `installed.packages()` function is no longer used. +- The `installed.packages()` function is no longer used. -+ Every filesystem manipulation is now done with `{fs}` (#285) +- Every filesystem manipulation is now done with `{fs}` (#285) # golem 0.0.1.9999 - CRAN release candidate ## Changes in the way run_app and deploy files are build -+ There is now a unique framework for run_app, that allows to deploy anywhere and can accept arguments. These arguments can then be retrieved with `get_golem_options()`. +- There is now a unique framework for run_app, that allows to deploy anywhere and can accept arguments. These arguments can then be retrieved with `get_golem_options()`. > See https://rtask.thinkr.fr/blog/shinyapp-runapp-shinyappdir-difference/ ## Breaking Changes -+ There is no need for `ui.R` and `server.R` to exist by default. Removed. Can be recreated with `add_ui_server_files()` +- There is no need for `ui.R` and `server.R` to exist by default. Removed. Can be recreated with `add_ui_server_files()` ## New function -+ There is now `add_shinyserver_file` & `add_shinyappsio_file`, #40 -+ `add_ui_server_files()` creates an ui & server.R files. +- There is now `add_shinyserver_file` & `add_shinyappsio_file`, #40 +- `add_ui_server_files()` creates an ui & server.R files. ## Small functions updates -+ Functions that create file(s) now automatically create folder if it's not there. Can be prevented with `dir_create = FALSE` -+ Functions that create file(s) can now be prevented from opening with `open = FALSE`, #75 -+ We have made explicit how to add external files (css & js) to the app, #78 -+ Launch test is now included in the default tests #48 +- Functions that create file(s) now automatically create folder if it's not there. Can be prevented with `dir_create = FALSE` +- Functions that create file(s) can now be prevented from opening with `open = FALSE`, #75 +- We have made explicit how to add external files (css & js) to the app, #78 +- Launch test is now included in the default tests #48 # golem 0.0.1.6000+ ## Changes -* `create_golem()` now switch to the newly created project -* `use_git()` is not listed in `dev/01_start.R` +- `create_golem()` now switch to the newly created project +- `use_git()` is not listed in `dev/01_start.R` ## Breaking changes -* Renamed `add_rconnect_file()` to `add_rstudioconnect_file()` -* Renamed `create_shiny_template()` to `create_golem()` -* Renamed `js()` to `activate_js()` -* Renamed `use_recommended_dep()` to `use_recommended_deps()` +- Renamed `add_rconnect_file()` to `add_rstudioconnect_file()` +- Renamed `create_shiny_template()` to `create_golem()` +- Renamed `js()` to `activate_js()` +- Renamed `use_recommended_dep()` to `use_recommended_deps()` ## New functions -* `invoke_js()` allows to call JS functions from the server side. #52 +- `invoke_js()` allows to call JS functions from the server side. #52 # golem 0.0.1.5000 ## Changes -* The dev files are now split in three - start / dev / deploy +- The dev files are now split in three - start / dev / deploy -* Every function that adds a file now check if the file already exists, and ask the user if they want to overwrite it (#15) +- Every function that adds a file now check if the file already exists, and ask the user if they want to overwrite it (#15) -* Every module is now named mod_x_ui / mod_x_server, for consistency. +- Every module is now named mod_x_ui / mod_x_server, for consistency. -* You can now create package with "illegal" names, using the command line `golem::create_shiny_template()`. #18 +- You can now create package with "illegal" names, using the command line `golem::create_shiny_template()`. #18 -* `add_browser_button()` is now named `browser_button()`, so that all the `add_*` function are only reserved for function adding files to the `golem`. +- `add_browser_button()` is now named `browser_button()`, so that all the `add_*` function are only reserved for function adding files to the `golem`. -+ `add_*_files` now check if the folder exists, if not suggests to create it. #36 +* `add_*_files` now check if the folder exists, if not suggests to create it. #36 ## New functions -* You now have a `browser_dev()` function that behaves like `warning_dev` and friends. #46 +- You now have a `browser_dev()` function that behaves like `warning_dev` and friends. #46 -* Added `set_golem_options()` to add local options used internally by {golem} && added it to the `01_start.R`. #49 +- Added `set_golem_options()` to add local options used internally by {golem} && added it to the `01_start.R`. #49 -* Added `add_dockerfile()` to create a Dockerfile from a DESCRIPTION. +- Added `add_dockerfile()` to create a Dockerfile from a DESCRIPTION. -* Added `add_dockerfile_shinyproxy()` to create a Dockerfile from a DESCRIPTION, to be used in Shiny Proxy. +- Added `add_dockerfile_shinyproxy()` to create a Dockerfile from a DESCRIPTION, to be used in Shiny Proxy. -* Added `add_dockerfile_heroku()` to create a Dockerfile from a DESCRIPTION, to be used with Heroku. +- Added `add_dockerfile_heroku()` to create a Dockerfile from a DESCRIPTION, to be used with Heroku. -* `add_css_file()`, `add_js_file()` and `add_js_handler()` create a CSS, JS, and JS with Shiny custom handler files. +- `add_css_file()`, `add_js_file()` and `add_js_handler()` create a CSS, JS, and JS with Shiny custom handler files. ## Removed -* `use_utils_prod` is now included in golem so you don't have to explicitly include the functions. +- `use_utils_prod` is now included in golem so you don't have to explicitly include the functions. ## Docs -* Golem now has four vignettes +- Golem now has four vignettes # golem 0.0.1.0002 @@ -456,6 +450,6 @@ Last round of functions, and some documentation cleanup. # golem 0.0.0.9000 -* Moved from {shinytemplate} to {golem} +- Moved from {shinytemplate} to {golem} -* Added a `NEWS.md` file to track changes to the package. +- Added a `NEWS.md` file to track changes to the package. diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 9c38a977..a1b6a42e 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -1,27 +1,25 @@ add_dockerfile_with_renv_ <- function( - source_folder = ".", - lockfile = NULL, - output_dir = fs::path(tempdir(), "deploy"), - distro = "focal", - FROM = "rocker/verse", - AS = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - extra_sysreqs = NULL, - update_tar_gz = TRUE, - document = FALSE, - ... - # build_golem_from_source = TRUE, -) { - - if (is.null(lockfile)) { - rlang::check_installed( - c("renv","attachment"), - reason = "to build a Dockerfile with automatic renv.lock creation. Use the `lockfile` parameter to pass your own `renv.lock` file." - ) - - } + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + FROM = "rocker/verse", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + document = FALSE, + ... + # build_golem_from_source = TRUE, + ) { + if (is.null(lockfile)) { + rlang::check_installed( + c("renv", "attachment"), + reason = "to build a Dockerfile with automatic renv.lock creation. Use the `lockfile` parameter to pass your own `renv.lock` file." + ) + } # Small hack to prevent warning from rlang::lang() in tests @@ -38,10 +36,7 @@ add_dockerfile_with_renv_ <- function( } if (is.null(lockfile)) { - - - if ( isTRUE(document) ){ - + if (isTRUE(document)) { cli_cat_line("You set `document = TRUE` and you did not pass your own renv.lock file,") cli_cat_line("as a consequence {golem} will use `attachment::att_amend_desc()` to update your ") cli_cat_line("DESCRIPTION file before creating the renv.lock file") @@ -57,14 +52,19 @@ add_dockerfile_with_renv_ <- function( lockfile <- attachment_create_renv_for_prod( path = source_folder, - check_if_suggests_is_installed = FALSE, document = document, + check_if_suggests_is_installed = FALSE, document = document, output = file.path(output_dir, "renv.lock.prod"), ... ) } - file.copy(from = lockfile, to = output_dir) - socle <- dockerfiler_dock_from_renv( + fs_file_copy( + path = lockfile, + new_path = output_dir, + overwrite = TRUE + ) + + socle <- dockerfiler::dock_from_renv( lockfile = lockfile, distro = distro, FROM = FROM, @@ -78,8 +78,7 @@ add_dockerfile_with_renv_ <- function( socle$write(as = file.path(output_dir, "Dockerfile_base")) - my_dock <- dockerfiler::Dockerfile$new(FROM = tolower(paste0(golem::get_golem_name(), "_base"))) - + my_dock <- dockerfiler::Dockerfile$new(FROM = tolower(tolower(paste0(golem::get_golem_name(), "_base")))) my_dock$COPY("renv.lock.prod", "renv.lock") @@ -155,24 +154,23 @@ add_dockerfile_with_renv_ <- function( #' @rdname dockerfiles #' @export add_dockerfile_with_renv <- function( - source_folder = ".", - lockfile = NULL, - output_dir = fs::path(tempdir(), "deploy"), - distro = "focal", - from = "rocker/verse", - as = NULL, - sysreqs = TRUE, - port = 80, - host = "0.0.0.0", - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - document = TRUE, - extra_sysreqs = NULL, - update_tar_gz = TRUE, - dockerfile_cmd = NULL, - ... -) { + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + document = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL, + ...) { base_dock <- add_dockerfile_with_renv_( source_folder = source_folder, lockfile = lockfile, @@ -210,8 +208,8 @@ add_dockerfile_with_renv <- function( docker build -f Dockerfile --progress=plain -t %s . docker run -p %s:%s %s # then go to 127.0.0.1:%s", - tolower(paste0(golem::get_golem_name(), "_base")), - tolower(paste0(golem::get_golem_name(), ":latest")), + paste0(golem::get_golem_name(), "_base"), + paste0(golem::get_golem_name(), ":latest"), port, port, tolower(paste0(golem::get_golem_name(), ":latest")), @@ -231,21 +229,20 @@ docker run -p %s:%s %s #' @export #' @export add_dockerfile_with_renv_shinyproxy <- function( - source_folder = ".", - lockfile = NULL, - output_dir = fs::path(tempdir(), "deploy"), - distro = "focal", - from = "rocker/verse", - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - extra_sysreqs = NULL, - open = TRUE, - document = TRUE, - update_tar_gz = TRUE, - ... -) { + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + document = TRUE, + update_tar_gz = TRUE, + ...) { add_dockerfile_with_renv( source_folder = source_folder, lockfile = lockfile, @@ -275,21 +272,20 @@ add_dockerfile_with_renv_shinyproxy <- function( #' @export #' @export add_dockerfile_with_renv_heroku <- function( - source_folder = ".", - lockfile = NULL, - output_dir = fs::path(tempdir(), "deploy"), - distro = "focal", - from = "rocker/verse", - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - extra_sysreqs = NULL, - open = TRUE, - document = TRUE, - update_tar_gz = TRUE, - ... -) { + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + document = TRUE, + update_tar_gz = TRUE, + ...) { add_dockerfile_with_renv( source_folder = source_folder, lockfile = lockfile, diff --git a/R/utils.R b/R/utils.R index ef475bc1..908285ba 100644 --- a/R/utils.R +++ b/R/utils.R @@ -391,6 +391,48 @@ yesno <- function(...) { menu(c("Yes", "No")) == 1 } + +# Checking that a package is installed +check_is_installed <- function( + pak, + ... +) { + if ( + !requireNamespace(pak, ..., quietly = TRUE) + ) { + stop( + sprintf( + "The {%s} package is required to run this function.\nYou can install it with `install.packages('%s')`.", + pak, + pak + ), + call. = FALSE + ) + } +} + +required_version <- function( + pak, + version +) { + if ( + utils::packageVersion(pak) < version + ) { + stop( + sprintf( + "This function require the version '%s' of the {%s} package.\nYou can update with `install.packages('%s')`.", + version, + pak, + pak + ), + call. = FALSE + ) + } +} + + + + add_sass_code <- function(where, dir, name) { if (fs_file_exists(where)) { if (fs_file_exists("dev/run_dev.R")) { From 1e79e54f0225c781f92756c575759ff674fd5a21 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 10 Mar 2023 11:51:58 +0100 Subject: [PATCH 134/190] fix: run the function instead of $ its body --- R/add_dockerfiles_renv.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 8764c164..24afc09f 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -78,7 +78,7 @@ add_dockerfile_with_renv_ <- function( socle$write(as = file.path(output_dir, "Dockerfile_base")) - my_dock <- dockerfiler_Dockerfile$new(FROM = tolower(tolower(paste0(golem::get_golem_name(), "_base")))) + my_dock <- dockerfiler_Dockerfile()$new(FROM = tolower(tolower(paste0(golem::get_golem_name(), "_base")))) my_dock$COPY("renv.lock.prod", "renv.lock") @@ -209,7 +209,7 @@ docker build -f Dockerfile --progress=plain -t %s . docker run -p %s:%s %s # then go to 127.0.0.1:%s", tolower(paste0(golem::get_golem_name(), "_base")), - tolower( paste0(golem::get_golem_name(), ":latest")), + tolower(paste0(golem::get_golem_name(), ":latest")), port, port, tolower(paste0(golem::get_golem_name(), ":latest")), From 2637ab1ec1c98418adea57f758167edbb9094bb4 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 10 Mar 2023 14:02:51 +0100 Subject: [PATCH 135/190] deploy: CRAN submission --- .Rbuildignore | 3 ++- CRAN-SUBMISSION | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 CRAN-SUBMISSION diff --git a/.Rbuildignore b/.Rbuildignore index e7cb3cb1..40b216b1 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -22,4 +22,5 @@ ^Meta$ ^\.github$ ^revdep$ -README.html \ No newline at end of file +README.html +^CRAN-SUBMISSION$ diff --git a/CRAN-SUBMISSION b/CRAN-SUBMISSION new file mode 100644 index 00000000..8608538e --- /dev/null +++ b/CRAN-SUBMISSION @@ -0,0 +1,3 @@ +Version: 0.4.0 +Date: 2023-03-10 13:02:19 UTC +SHA: ce67fa5e76d40f1ca68b6dda10064bae37fbcb6e From 030156530874420c51e59defc637341a9d0b5b69 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 14 Mar 2023 22:13:21 +0100 Subject: [PATCH 136/190] feat: is_golem tries to guess if current folder is a golem based app This can be used to check before running a module/project template close issue #836 --- NAMESPACE | 1 + R/is_golem.R | 33 +++++++++++++++++++++++++++++++++ man/is_golem.Rd | 15 +++++++++++++++ tests/testthat/test-is_golem.R | 5 +++++ 4 files changed, 54 insertions(+) create mode 100644 R/is_golem.R create mode 100644 man/is_golem.Rd create mode 100644 tests/testthat/test-is_golem.R diff --git a/NAMESPACE b/NAMESPACE index e7919bb6..2369a3c4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -49,6 +49,7 @@ export(get_golem_wd) export(get_sysreqs) export(install_dev_deps) export(invoke_js) +export(is_golem) export(is_running) export(js_handler_template) export(js_template) diff --git a/R/is_golem.R b/R/is_golem.R new file mode 100644 index 00000000..7a65240d --- /dev/null +++ b/R/is_golem.R @@ -0,0 +1,33 @@ +#' Is the directory a golem-based app? +#' +#' Trying to guess if `path` is a golem-based app. +#' +#' @param path Path to the directory to check. +#' Defaults to the current working directory. +#' +#' @export +#' +#' @example +#' is_golem() +is_golem <- function(path = getwd()) { + files_from_shiny_example <- grep( + "^(?!REMOVEME).*", + list.files( + system.file("shinyexample", package = "golem"), + recursive = TRUE + ), + perl = TRUE, + value = TRUE + ) + files_from_shiny_example <- grep( + "favicon.ico", + files_from_shiny_example, + perl = TRUE, + value = TRUE, + invert = TRUE + ) + + all( + files_from_shiny_example %in% list.files(path, recursive = TRUE) + ) +} diff --git a/man/is_golem.Rd b/man/is_golem.Rd new file mode 100644 index 00000000..8fdc204d --- /dev/null +++ b/man/is_golem.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/is_golem.R +\name{is_golem} +\alias{is_golem} +\title{Is the directory a golem-based app?} +\usage{ +is_golem(path = getwd()) +} +\arguments{ +\item{path}{Path to the directory to check. +Defaults to the current working directory.} +} +\description{ +Trying to guess if \code{path} is a golem-based app. +} diff --git a/tests/testthat/test-is_golem.R b/tests/testthat/test-is_golem.R new file mode 100644 index 00000000..79656ffb --- /dev/null +++ b/tests/testthat/test-is_golem.R @@ -0,0 +1,5 @@ +test_that("is_golem works", { + expect_true( + is_golem(pkg) + ) +}) From 1439d56a895fad65f65a8e55abed8046c8c11d45 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 15 Mar 2023 17:03:53 +0100 Subject: [PATCH 137/190] test: adding a test to is_golem --- tests/testthat/test-is_golem.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/testthat/test-is_golem.R b/tests/testthat/test-is_golem.R index 79656ffb..8fe64dd0 100644 --- a/tests/testthat/test-is_golem.R +++ b/tests/testthat/test-is_golem.R @@ -2,4 +2,7 @@ test_that("is_golem works", { expect_true( is_golem(pkg) ) + expect_false( + is_golem(tempdir()) + ) }) From 6b764eb9b354a11173b6b3ebba549bccfc0005fb Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 15 Mar 2023 21:16:05 +0100 Subject: [PATCH 138/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index ef1e936a..df0cad8b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.0 +Version: 0.4.1 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index fe719608..d968cad1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,13 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. +# 0.4.1 to 0.5.0 + +## New functions + +* `is_golem()` tries to guess if the current folder is a `{golem}`-based app (#836) + +## New functions + # 0.4.0 ## New functions From 60ec1f40e9fca2865e6dd8d2b178ade7c3c3b4d0 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 15 Mar 2023 21:24:42 +0100 Subject: [PATCH 139/190] fix: rule number 1, it's always a typo --- inst/mantests/build.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 98069b56..3dbe73fb 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -376,7 +376,7 @@ golem::document_and_reload() usethis::use_dev_package( "golem", - remotes = "https://github.com/ThinkR-open/golem" + remote = "https://github.com/ThinkR-open/golem" ) devtools::test() From 487dedfc9328d42c731e7d2e16cca773d59dd2fd Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 17 Mar 2023 11:58:31 +0100 Subject: [PATCH 140/190] deploy: forgot to add the new fun to pkgdown --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 0726a74e..deb079c8 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -147,6 +147,7 @@ reference: - make_dev - with_golem_options - maintenance_page + - is_golem - title: internal contents: From 7917d2b6d9ae9d6ea7cb7138a58bfb6b7301851e Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Fri, 24 Mar 2023 19:08:20 +0100 Subject: [PATCH 141/190] Fix 987 --- R/modules_fn.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/modules_fn.R b/R/modules_fn.R index 95a8f8fd..e08b7b18 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -209,7 +209,7 @@ module_template <- function( write_there("#' @noRd ") } write_there(sprintf("mod_%s_server <- function(id){", name)) - write_there(" moduleServer( id, function(input, output, session){") + write_there(" moduleServer(id, function(input, output, session){") write_there(" ns <- session$ns") write_there(ph_server) write_there(" })") From c9dbc802f4491f9ca03e41718733416555cab48f Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Fri, 24 Mar 2023 09:48:14 +0100 Subject: [PATCH 142/190] Add function body upon call to add_fct - fix 1004 adding bare skeleton function body - function name taken from 'name' arg to add_fct --- R/add_r_files.R | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/R/add_r_files.R b/R/add_r_files.R index a4bcb24f..047097fe 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -168,6 +168,12 @@ append_roxygen_comment <- function( write_there("#'") write_there(sprintf("#' @description A %s function", ext)) write_there("#'") + if (file_type == "function") { + write_there("#' @param arg1 first function argument") + write_there("#'") + write_there("#' @param arg2 second function argument") + write_there("#'") + } write_there(sprintf("#' @return The return value, if any, from executing the %s.", file_type)) write_there("#'") if (export) { @@ -175,4 +181,12 @@ append_roxygen_comment <- function( } else { write_there("#' @noRd") } + if (file_type == "function") { + write_there(paste(name, "<- function(arg1, arg2, ...) {")) + write_there("# ... some computations ...") + write_there("# ... final compuations ...") + write_there("") + write_there(" return(NULL)") + write_there("}") + } } From 97b31e6ab06ec3cc75a85e77d0cc5ed38ab6e98f Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Fri, 24 Mar 2023 11:06:23 +0100 Subject: [PATCH 143/190] Change to lean function body --- R/add_r_files.R | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/R/add_r_files.R b/R/add_r_files.R index 047097fe..98ead3b9 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -182,11 +182,7 @@ append_roxygen_comment <- function( write_there("#' @noRd") } if (file_type == "function") { - write_there(paste(name, "<- function(arg1, arg2, ...) {")) - write_there("# ... some computations ...") - write_there("# ... final compuations ...") - write_there("") - write_there(" return(NULL)") + write_there(paste(name, "<- function(arg1, arg2) {")) write_there("}") } } From 00d632772bdba9baba1cc36cd5894341f7d6cbc3 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 29 Mar 2023 17:14:35 +0200 Subject: [PATCH 144/190] hotfix: no param on function when calling add_fct Issue #1004 --- R/add_r_files.R | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/R/add_r_files.R b/R/add_r_files.R index 98ead3b9..719927f6 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -168,12 +168,6 @@ append_roxygen_comment <- function( write_there("#'") write_there(sprintf("#' @description A %s function", ext)) write_there("#'") - if (file_type == "function") { - write_there("#' @param arg1 first function argument") - write_there("#'") - write_there("#' @param arg2 second function argument") - write_there("#'") - } write_there(sprintf("#' @return The return value, if any, from executing the %s.", file_type)) write_there("#'") if (export) { @@ -182,7 +176,7 @@ append_roxygen_comment <- function( write_there("#' @noRd") } if (file_type == "function") { - write_there(paste(name, "<- function(arg1, arg2) {")) + write_there(paste(name, "<- function() {")) write_there("}") } } From 22a896ae00799eb31bf1956b545a19ce86ac21c9 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 29 Mar 2023 17:36:10 +0200 Subject: [PATCH 145/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index df0cad8b..d8313f0f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.1 +Version: 0.4.2 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index d968cad1..d1c67b5e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,10 @@ * `is_golem()` tries to guess if the current folder is a `{golem}`-based app (#836) +## Features + ++ `add_fct()` now adds the skeleton for a function (#1004, @ilyaZar) + ## New functions # 0.4.0 From c99bbb27d02a0ab2bc9e383cdd444376c6adc12d Mon Sep 17 00:00:00 2001 From: Nils Schnakenberg Date: Fri, 31 Mar 2023 15:29:02 +0200 Subject: [PATCH 146/190] fix: stick to tidyverse style for new modules --- R/modules_fn.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/modules_fn.R b/R/modules_fn.R index 95a8f8fd..e6fd2e6f 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -171,7 +171,7 @@ module_template <- function( } write_there("#'") write_there("#' @importFrom shiny NS tagList ") - write_there(sprintf("mod_%s_ui <- function(id){", name)) + write_there(sprintf("mod_%s_ui <- function(id) {", name)) write_there(" ns <- NS(id)") write_there(" tagList(") write_there(ph_ui) @@ -188,7 +188,7 @@ module_template <- function( } else { write_there("#' @noRd ") } - write_there(sprintf("mod_%s_server <- function(input, output, session){", name)) + write_there(sprintf("mod_%s_server <- function(input, output, session) {", name)) write_there(" ns <- session$ns") write_there(ph_server) write_there("}") @@ -208,8 +208,8 @@ module_template <- function( } else { write_there("#' @noRd ") } - write_there(sprintf("mod_%s_server <- function(id){", name)) - write_there(" moduleServer( id, function(input, output, session){") + write_there(sprintf("mod_%s_server <- function(id) {", name)) + write_there(" moduleServer(id, function(input, output, session) {") write_there(" ns <- session$ns") write_there(ph_server) write_there(" })") From 6d44c6191ca3a57d909b4eaff25c3ceebfab845d Mon Sep 17 00:00:00 2001 From: Ivo Kwee Date: Sun, 12 Mar 2023 06:17:41 +0100 Subject: [PATCH 147/190] Add -it flag when run docker so ^C still works Add -it flag when run docker so ^C still works --- R/add_dockerfiles_renv.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 24afc09f..3039a7c1 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -206,7 +206,7 @@ add_dockerfile_with_renv <- function( out <- sprintf( "docker build -f Dockerfile_base --progress=plain -t %s . docker build -f Dockerfile --progress=plain -t %s . -docker run -p %s:%s %s +docker run -it -p %s:%s %s # then go to 127.0.0.1:%s", tolower(paste0(golem::get_golem_name(), "_base")), tolower(paste0(golem::get_golem_name(), ":latest")), From 08520c24595b8d98d8f88691eed35e93f61294c5 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 10:58:04 +0200 Subject: [PATCH 148/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 13 +++++++------ README.md | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d8313f0f..1e86da14 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.2 +Version: 0.4.3 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index d1c67b5e..18204880 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,11 +6,15 @@ * `is_golem()` tries to guess if the current folder is a `{golem}`-based app (#836) -## Features +## New features / user visible changes + `add_fct()` now adds the skeleton for a function (#1004, @ilyaZar) -## New functions +## Bug fix + ++ Docker commands now take the `-it` flag so it can be killed with `^C` (#, @ivokwee) + +## Internal changes # 0.4.0 @@ -44,10 +48,7 @@ ## Internal changes -add_dockerfile_with_renv now works well with uppercase in package name - -add_dockerfile_with_renv now works well with uppercase in package name - ++ `add_dockerfile_with_renv` now works well with uppercase in package name # golem 0.3.5 diff --git a/README.md b/README.md index a5feac9d..3c220cc4 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.0 +You’re reading the doc about version : 0.4.3 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-03-10 09:53:27 CET" +#> [1] "2023-04-04 10:54:56 CEST" ``` Here are the test & coverage results : @@ -33,15 +33,17 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.0 ──── -#> Duration: 1m 23.6s +#> Warning: [is_golem.R:10] @example +#> '/Users/colinfay/git/github/thinkr-open/golem/is_golem()' doesn't exist +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.3 ──── +#> Duration: 1m 9.1s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` ``` r covr::package_coverage() -#> golem Coverage: 69.16% +#> golem Coverage: 68.65% #> R/addins.R: 0.00% #> R/bootstrap_rstudio_api.R: 0.00% #> R/enable_roxygenize.R: 0.00% @@ -55,11 +57,12 @@ covr::package_coverage() #> R/test_helpers.R: 30.26% #> R/js.R: 43.75% #> R/reload.R: 45.36% +#> R/utils.R: 54.14% #> R/use_recommended.R: 54.55% #> R/bootstrap_desc.R: 55.56% #> R/install_dev_deps.R: 57.14% -#> R/utils.R: 58.30% #> R/bootstrap_attachment.R: 61.54% +#> R/bootstrap_dockerfiler.R: 63.33% #> R/add_dockerfiles.R: 74.19% #> R/bootstrap_usethis.R: 76.56% #> R/boostrap_fs.R: 77.78% @@ -70,11 +73,10 @@ covr::package_coverage() #> R/add_resource_path.R: 88.89% #> R/create_golem.R: 89.47% #> R/make_dev.R: 90.00% -#> R/add_r_files.R: 91.67% +#> R/add_r_files.R: 91.95% #> R/add_files.R: 92.31% #> R/add_rstudio_files.R: 93.10% #> R/golem-yaml-get.R: 93.18% -#> R/bootstrap_dockerfiler.R: 93.33% #> R/add_dockerfiles_renv.R: 93.75% #> R/boostrap_cli.R: 100.00% #> R/boostrap_crayon.R: 100.00% @@ -85,6 +87,7 @@ covr::package_coverage() #> R/disable_autoload.R: 100.00% #> R/golem-yaml-set.R: 100.00% #> R/golem-yaml-utils.R: 100.00% +#> R/is_golem.R: 100.00% #> R/is_running.R: 100.00% #> R/pkg_tools.R: 100.00% #> R/set_golem_options.R: 100.00% From aa87e628981be0132a100c9355ac683c47490ad1 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 10:58:30 +0200 Subject: [PATCH 149/190] feat: add script for what to do after merge --- inst/to-do-after-merge-dev.R | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 inst/to-do-after-merge-dev.R diff --git a/inst/to-do-after-merge-dev.R b/inst/to-do-after-merge-dev.R new file mode 100644 index 00000000..9924f9d7 --- /dev/null +++ b/inst/to-do-after-merge-dev.R @@ -0,0 +1,6 @@ +desc::desc_bump_version("patch") +file.edit("NEWS.md") +devtools::build_readme() +gert::git_add(c("DESCRIPTION", "NEWS.md", "README.md", "NEWS.md")) +gert::git_commit("chore: version bump & news update") +gert::git_push() From c5fbc5c096c5fbdbf965f1dfb82abc34c8a48389 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 29 Mar 2023 12:16:14 +0200 Subject: [PATCH 150/190] Add use_readme_rmd() - check if README.Rmd exists; throw error if arg overwrite = FALSE, but overwrite if TRUE - uses a template from a function that returns an appropriate character - sets devtools::check chunk with option error=TRUE at the moment to ease testing this feature Possible To-Dos: - use smarter template e.g. from specific template-file.Rmd a-la usethis+whisker - add use_readme_md() that adds the correspodining .md file --- R/use_readme.R | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 R/use_readme.R diff --git a/R/use_readme.R b/R/use_readme.R new file mode 100644 index 00000000..164036bd --- /dev/null +++ b/R/use_readme.R @@ -0,0 +1,104 @@ +#' Generate a README.Rmd +#' +#' @param overwrite an optional \code{logical} flag; if \code{TRUE}, overwrite +#' existing \code{README.Rmd}, else throws an error if \code{README.Rmd} exists +#' +#' @return pure side-effect function that generates template \code{README.Rmd} +#' @export +use_readme_rmd <- function(overwrite = FALSE) { + stopifnot(`Arg. 'overwrite' must be logical` = is.logical(overwrite)) + + tmp_pth <- get_rmd_pth() + check_overwrite(overwrite, tmp_pth) + + readme_tmpl <- generate_readme_tmpl( + pkg_name = pkg_name() + ) + + writeLines( + text = readme_tmpl, + con = file.path(tmp_pth) + ) +} +get_rmd_pth <- function() { + file.path( + get_golem_wd(), + "README.Rmd") +} +check_overwrite <- function(overwrite, tmp_pth) { + if (isTRUE(overwrite)) { + file.create(tmp_pth) + } else { + if (file.exists(tmp_pth)) { + stop("README.Rmd already exists. Set `overwrite = TRUE` to overwrite.") + } + } +} +generate_readme_tmpl <- function(pkg_name) { + tmp_file <- '--- +output: github_document +--- + + + +```{r, include = FALSE} + knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.path = "man/figures/README-", + out.width = "100%" +) +``` + +# `{PKG}` + + + + +## Installation + +You can install the development version of `{PKG}` like so: + +```{r} +# FILL THIS IN! HOW CAN PEOPLE INSTALL YOUR DEV PACKAGE? +``` + +## Run + +You can launch the application by running: + +```{r, eval = FALSE} +PKG::run_app() +``` + +## About + +You are reading the doc about version : `r golem::pkg_version()` + +This README has been compiled on the + +```{r} +Sys.time() +``` + +Here are the tests results and package coverage: + +```{r, error = TRUE} +devtools::check(quiet = TRUE) +``` + +```{r echo = FALSE} +unloadNamespace("PKG") +``` + +```{r} +covr::package_coverage() +``` +' +tmp_file <- stringr::str_replace_all( + tmp_file, + "PKG", + pkg_name +) +return(tmp_file) +} From 11eeea2b3df5bcc7ff1bf3db206e3c5d3c387eb1 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 29 Mar 2023 12:28:45 +0200 Subject: [PATCH 151/190] Run grkstyle::grk_style_file("R/use_readme.R") on the file --- R/use_readme.R | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/R/use_readme.R b/R/use_readme.R index 164036bd..2306e0b5 100644 --- a/R/use_readme.R +++ b/R/use_readme.R @@ -23,7 +23,8 @@ use_readme_rmd <- function(overwrite = FALSE) { get_rmd_pth <- function() { file.path( get_golem_wd(), - "README.Rmd") + "README.Rmd" + ) } check_overwrite <- function(overwrite, tmp_pth) { if (isTRUE(overwrite)) { @@ -95,10 +96,10 @@ unloadNamespace("PKG") covr::package_coverage() ``` ' -tmp_file <- stringr::str_replace_all( - tmp_file, - "PKG", - pkg_name -) -return(tmp_file) + tmp_file <- stringr::str_replace_all( + tmp_file, + "PKG", + pkg_name + ) + return(tmp_file) } From d92d6f382c985e6debb1143641ab5e02fade998e Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 12:00:43 +0200 Subject: [PATCH 152/190] feat: implement use_readme_rmd - Rework of @ilyaZar first implem Issue #1011 --- NAMESPACE | 1 + R/bootstrap_usethis.R | 11 +++ R/use_readme.R | 133 ++++++++++++------------------- inst/shinyexample/dev/01_start.R | 2 +- inst/utils/empty_readme.Rmd | 59 ++++++++++++++ man/use_readme_rmd.Rd | 30 +++++++ tests/testthat/test-use_readme.R | 40 ++++++++++ 7 files changed, 193 insertions(+), 83 deletions(-) create mode 100644 inst/utils/empty_readme.Rmd create mode 100644 man/use_readme_rmd.Rd create mode 100644 tests/testthat/test-use_readme.R diff --git a/NAMESPACE b/NAMESPACE index 2369a3c4..5c44bf87 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -80,6 +80,7 @@ export(use_internal_file) export(use_internal_html_template) export(use_internal_js_file) export(use_module_test) +export(use_readme_rmd) export(use_recommended_deps) export(use_recommended_tests) export(use_utils_server) diff --git a/R/bootstrap_usethis.R b/R/bootstrap_usethis.R index 1e033f2c..a92ad3a1 100644 --- a/R/bootstrap_usethis.R +++ b/R/bootstrap_usethis.R @@ -114,3 +114,14 @@ usethis_use_spell_check <- function( error ) } + +usethis_use_readme_rmd <- function( + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to create a readme." + ) + usethis::use_readme_rmd( + open = open + ) +} diff --git a/R/use_readme.R b/R/use_readme.R index 2306e0b5..6ed54393 100644 --- a/R/use_readme.R +++ b/R/use_readme.R @@ -1,105 +1,74 @@ #' Generate a README.Rmd -#' +#' @inheritParams usethis::use_readme_rmd +#' @inheritParams add_module +#' @inheritParams fill_desc #' @param overwrite an optional \code{logical} flag; if \code{TRUE}, overwrite #' existing \code{README.Rmd}, else throws an error if \code{README.Rmd} exists #' #' @return pure side-effect function that generates template \code{README.Rmd} #' @export -use_readme_rmd <- function(overwrite = FALSE) { +use_readme_rmd <- function( + open = rlang::is_interactive(), + pkg_name = golem::get_golem_name(), + overwrite = FALSE, + pkg = golem::get_golem_wd() +) { stopifnot(`Arg. 'overwrite' must be logical` = is.logical(overwrite)) - tmp_pth <- get_rmd_pth() - check_overwrite(overwrite, tmp_pth) + # We move the working directory to perform this action, + # in case we're launching the action from somewhere else + old <- setwd(pkg) + on.exit(setwd(old)) - readme_tmpl <- generate_readme_tmpl( - pkg_name = pkg_name() + # Guess the readme path + readme_path <- file.path( + pkg, + "README.Rmd" ) - writeLines( - text = readme_tmpl, - con = file.path(tmp_pth) + # Removing the README if it already exists and overwrite is TRUE + check_overwrite( + overwrite, + readme_path ) -} -get_rmd_pth <- function() { - file.path( - get_golem_wd(), - "README.Rmd" + + usethis_use_readme_rmd() + + readme_tmpl <- generate_readme_tmpl( + pkg_name = pkg_name ) + + write( + x = readme_tmpl, + file = readme_path, + append = FALSE, + sep = "\n" + ) + return(invisible(TRUE)) } + check_overwrite <- function(overwrite, tmp_pth) { - if (isTRUE(overwrite)) { - file.create(tmp_pth) - } else { - if (file.exists(tmp_pth)) { + # If the user wants to overwrite, we remove the file + # Otherwise, error if the file already exists + if (file.exists(tmp_pth)){ + if (isTRUE(overwrite)) { + unlink(tmp_pth, TRUE, TRUE) + } else { stop("README.Rmd already exists. Set `overwrite = TRUE` to overwrite.") } } -} -generate_readme_tmpl <- function(pkg_name) { - tmp_file <- '--- -output: github_document ---- - - - -```{r, include = FALSE} - knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>", - fig.path = "man/figures/README-", - out.width = "100%" -) -``` - -# `{PKG}` - - - -## Installation - -You can install the development version of `{PKG}` like so: - -```{r} -# FILL THIS IN! HOW CAN PEOPLE INSTALL YOUR DEV PACKAGE? -``` - -## Run - -You can launch the application by running: - -```{r, eval = FALSE} -PKG::run_app() -``` - -## About - -You are reading the doc about version : `r golem::pkg_version()` - -This README has been compiled on the - -```{r} -Sys.time() -``` - -Here are the tests results and package coverage: - -```{r, error = TRUE} -devtools::check(quiet = TRUE) -``` +} -```{r echo = FALSE} -unloadNamespace("PKG") -``` +generate_readme_tmpl <- function(pkg_name) { -```{r} -covr::package_coverage() -``` -' - tmp_file <- stringr::str_replace_all( - tmp_file, - "PKG", - pkg_name + tmp_file <- readLines( + golem_sys("utils/empty_readme.Rmd") + ) + return( + sprintf( + tmp_file, + pkg_name + ) ) - return(tmp_file) } diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 7a963694..9c27f7ee 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -38,7 +38,7 @@ golem::install_dev_deps() ## Create Common Files ---- ## See ?usethis for more information usethis::use_mit_license("Golem User") # You can set another license here -usethis::use_readme_rmd(open = FALSE) +golem::use_readme_rmd(open = FALSE) devtools::build_readme() # Note that `contact` is required since usethis version 2.1.5 # If your {usethis} version is older, you can remove that param diff --git a/inst/utils/empty_readme.Rmd b/inst/utils/empty_readme.Rmd new file mode 100644 index 00000000..b2f575a4 --- /dev/null +++ b/inst/utils/empty_readme.Rmd @@ -0,0 +1,59 @@ +--- +output: github_document +--- + + + +```{r, include = FALSE} + knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.path = "man/figures/README-", + out.width = "100%%" +) +``` + +# `{%s}` + + + + +## Installation + +You can install the development version of `{%s}` like so: + +```{r} +# FILL THIS IN! HOW CAN PEOPLE INSTALL YOUR DEV PACKAGE? +``` + +## Run + +You can launch the application by running: + +```{r, eval = FALSE} +%s::run_app() +``` + +## About + +You are reading the doc about version : `r golem::pkg_version()` + +This README has been compiled on the + +```{r} +Sys.time() +``` + +Here are the tests results and package coverage: + +```{r, error = TRUE} +devtools::check(quiet = TRUE) +``` + +```{r echo = FALSE} +unloadNamespace("%s") +``` + +```{r, error = TRUE} +covr::package_coverage() +``` diff --git a/man/use_readme_rmd.Rd b/man/use_readme_rmd.Rd new file mode 100644 index 00000000..89a04779 --- /dev/null +++ b/man/use_readme_rmd.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_readme.R +\name{use_readme_rmd} +\alias{use_readme_rmd} +\title{Generate a README.Rmd} +\usage{ +use_readme_rmd( + open = rlang::is_interactive(), + pkg_name = golem::get_golem_name(), + overwrite = FALSE, + pkg = golem::get_golem_wd() +) +} +\arguments{ +\item{open}{Open the newly created file for editing? Happens in RStudio, if +applicable, or via \code{\link[utils:file.edit]{utils::file.edit()}} otherwise.} + +\item{pkg_name}{The name of the package} + +\item{overwrite}{an optional \code{logical} flag; if \code{TRUE}, overwrite +existing \code{README.Rmd}, else throws an error if \code{README.Rmd} exists} + +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} +} +\value{ +pure side-effect function that generates template \code{README.Rmd} +} +\description{ +Generate a README.Rmd +} diff --git a/tests/testthat/test-use_readme.R b/tests/testthat/test-use_readme.R new file mode 100644 index 00000000..33d9f7e0 --- /dev/null +++ b/tests/testthat/test-use_readme.R @@ -0,0 +1,40 @@ +test_that("generate_readme_tmpl works", { res <- generate_readme_tmpl("my_pkg") + expect_true( + grepl("my_pkg", paste(res, collapse = " ")) + ) + expect_true( + grepl("my_pkg::run_app()", paste(res, collapse = " ")) + ) + expect_true( + grepl("covr::package_coverage", paste(res, collapse = " ")) + ) + expect_true( + grepl("unloadNamespace", paste(res, collapse = " ")) + ) + expect_true( + grepl("devtools::check", paste(res, collapse = " ")) + ) +}) + + +test_that("check_overwrite works", { + expect_error( + check_overwrite(FALSE, golem_sys("utils/empty_readme.Rmd")), + "README.Rmd already exists. Set `overwrite = TRUE` to overwrite." + ) +}) + +test_that("use_readme_rmd works", { + expect_true( + use_readme_rmd( + open = FALSE, + overwrite = TRUE, + pkg = getwd(), + pkg_name = "rand_name" + ) + ) + expect_true( + file.exists("README.Rmd") + ) + devtools:::build_readme() +}) From 446b149aa458ea31fb3680f05d33e9381f008d5b Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 12:00:54 +0200 Subject: [PATCH 153/190] fix: typo in doc --- R/is_golem.R | 2 +- man/is_golem.Rd | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/R/is_golem.R b/R/is_golem.R index 7a65240d..d391582d 100644 --- a/R/is_golem.R +++ b/R/is_golem.R @@ -7,7 +7,7 @@ #' #' @export #' -#' @example +#' @examples #' is_golem() is_golem <- function(path = getwd()) { files_from_shiny_example <- grep( diff --git a/man/is_golem.Rd b/man/is_golem.Rd index 8fdc204d..784130f9 100644 --- a/man/is_golem.Rd +++ b/man/is_golem.Rd @@ -13,3 +13,6 @@ Defaults to the current working directory.} \description{ Trying to guess if \code{path} is a golem-based app. } +\examples{ +is_golem() +} From 7fba280891311cf973c9ed42ea6bb89a1859f995 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 12:14:59 +0200 Subject: [PATCH 154/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ README.md | 15 +++++++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 1e86da14..b266e03b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.3 +Version: 0.4.4 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index 18204880..0e0ef406 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ * `is_golem()` tries to guess if the current folder is a `{golem}`-based app (#836) +* `use_readme_rmd()` adds a {golem} specific READM.Rmd (@ilyaZar, #1011) + ## New features / user visible changes + `add_fct()` now adds the skeleton for a function (#1004, @ilyaZar) diff --git a/README.md b/README.md index 3c220cc4..1168ea43 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.3 +You’re reading the doc about version : 0.4.4 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-04-04 10:54:56 CEST" +#> [1] "2023-04-04 12:09:15 CEST" ``` Here are the test & coverage results : @@ -33,17 +33,15 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> Warning: [is_golem.R:10] @example -#> '/Users/colinfay/git/github/thinkr-open/golem/is_golem()' doesn't exist -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.3 ──── -#> Duration: 1m 9.1s +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.4 ──── +#> Duration: 1m 18s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` ``` r covr::package_coverage() -#> golem Coverage: 68.65% +#> golem Coverage: 69.04% #> R/addins.R: 0.00% #> R/bootstrap_rstudio_api.R: 0.00% #> R/enable_roxygenize.R: 0.00% @@ -64,8 +62,8 @@ covr::package_coverage() #> R/bootstrap_attachment.R: 61.54% #> R/bootstrap_dockerfiler.R: 63.33% #> R/add_dockerfiles.R: 74.19% -#> R/bootstrap_usethis.R: 76.56% #> R/boostrap_fs.R: 77.78% +#> R/bootstrap_usethis.R: 78.57% #> R/modules_fn.R: 80.00% #> R/use_utils.R: 83.33% #> R/use_favicon.R: 85.56% @@ -78,6 +76,7 @@ covr::package_coverage() #> R/add_rstudio_files.R: 93.10% #> R/golem-yaml-get.R: 93.18% #> R/add_dockerfiles_renv.R: 93.75% +#> R/use_readme.R: 97.14% #> R/boostrap_cli.R: 100.00% #> R/boostrap_crayon.R: 100.00% #> R/bootstrap_pkgload.R: 100.00% From 7ec50d0cb07e734a60a480bff1049179524c1dd0 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 17:31:18 +0200 Subject: [PATCH 155/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ README.md | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b266e03b..e79d4999 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.4 +Version: 0.4.5 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index 0e0ef406..7dbb2aed 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,8 @@ + `add_fct()` now adds the skeleton for a function (#1004, @ilyaZar) ++ The module skeleton now stick to tidyverse style (#1019, @ni2scmn) + ## Bug fix + Docker commands now take the `-it` flag so it can be killed with `^C` (#, @ivokwee) diff --git a/README.md b/README.md index 1168ea43..58339fef 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.4 +You’re reading the doc about version : 0.4.5 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-04-04 12:09:15 CEST" +#> [1] "2023-04-04 17:26:33 CEST" ``` Here are the test & coverage results : @@ -33,8 +33,8 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.4 ──── -#> Duration: 1m 18s +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.5 ──── +#> Duration: 1m 19.5s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` From 49de77494a859f55a87cf2034b3dd141a36b8cdb Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Sun, 2 Apr 2023 19:36:31 +0200 Subject: [PATCH 156/190] docs: improve explanation comments in 01_start - add more precise description for each argument in fill_desc() - change some capital letters to lower case in fill_desc() comments --- inst/shinyexample/dev/01_start.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 9c27f7ee..3a106e91 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -19,14 +19,14 @@ ## to change the name in the app_sys() function in app_config.R /!\ ## golem::fill_desc( - pkg_name = "shinyexample", # The Name of the package containing the App - pkg_title = "PKG_TITLE", # The Title of the package containing the App - pkg_description = "PKG_DESC.", # The Description of the package containing the App + pkg_name = "shinyexample", # The name of the golem package containing the app (typically lowercase, no underscore or periods) + pkg_title = "PKG_TITLE", # What the Package Does (One Line, Title Case, No Period) + pkg_description = "PKG_DESC.", # What the package does (one paragraph). author_first_name = "AUTHOR_FIRST", # Your First Name author_last_name = "AUTHOR_LAST", # Your Last Name - author_email = "AUTHOR@MAIL.COM", # Your Email - repo_url = NULL, # The URL of the GitHub Repo (optional), - pkg_version = "0.0.0.9000" # The Version of the package containing the App + author_email = "AUTHOR@MAIL.COM", # Your email + repo_url = NULL, # The URL of the GitHub repo (optional), + pkg_version = "0.0.0.9000" # The version of the package containing the app ) ## Set {golem} options ---- From c318e2490f5a60520e14174f815c5445beda1a19 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 17:37:26 +0200 Subject: [PATCH 157/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ README.md | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e79d4999..32956dc0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.5 +Version: 0.4.6 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index 7dbb2aed..ecd21bc1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,8 @@ + The module skeleton now stick to tidyverse style (#1019, @ni2scmn) ++ Better comments to `fill_desc()` in `01_start.R` (#1021, @ilyaZar) + ## Bug fix + Docker commands now take the `-it` flag so it can be killed with `^C` (#, @ivokwee) diff --git a/README.md b/README.md index 58339fef..2b056e51 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.5 +You’re reading the doc about version : 0.4.6 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-04-04 17:26:33 CEST" +#> [1] "2023-04-04 17:33:09 CEST" ``` Here are the test & coverage results : @@ -33,8 +33,8 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.5 ──── -#> Duration: 1m 19.5s +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.6 ──── +#> Duration: 1m 19.4s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` From 4386a7cd0d8aea913ebfb3d2649d7261843c9220 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Tue, 4 Apr 2023 18:04:35 +0200 Subject: [PATCH 158/190] refactor: add use_git_remote to() to dev/01_start.R --- inst/shinyexample/dev/01_start.R | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 3a106e91..ec712fde 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -48,6 +48,10 @@ usethis::use_news_md(open = FALSE) ## Use git ---- usethis::use_git() +## Sets the remote associated with 'name' to 'url' +usethis::use_git_remote( + name = "origin", + url = "https://github.com//.git") ## Init Testing Infrastructure ---- ## Create a template for tests From 415f5055e1ba9fd3170dcb5b93a8881bfc4f2119 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 21:02:36 +0200 Subject: [PATCH 159/190] chore: script for bumping version and all --- inst/to-do-after-merge-dev.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/to-do-after-merge-dev.R b/inst/to-do-after-merge-dev.R index 9924f9d7..1d63140b 100644 --- a/inst/to-do-after-merge-dev.R +++ b/inst/to-do-after-merge-dev.R @@ -1,5 +1,5 @@ desc::desc_bump_version("patch") -file.edit("NEWS.md") +rstudioapi::navigateToFile("NEWS.md") devtools::build_readme() gert::git_add(c("DESCRIPTION", "NEWS.md", "README.md", "NEWS.md")) gert::git_commit("chore: version bump & news update") From 879ebdfdd8dd4d32ad2d1635f426fa5ce42b5833 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 21:03:05 +0200 Subject: [PATCH 160/190] style: indentation --- inst/shinyexample/dev/01_start.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index ec712fde..2997851f 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -51,7 +51,8 @@ usethis::use_git() ## Sets the remote associated with 'name' to 'url' usethis::use_git_remote( name = "origin", - url = "https://github.com//.git") + url = "https://github.com//.git" +) ## Init Testing Infrastructure ---- ## Create a template for tests From 73a3a1747418fb09fbe21b5b1e1c4fd2a0f9e33a Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 21:07:18 +0200 Subject: [PATCH 161/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 3 +++ README.md | 8 ++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 32956dc0..99ee635b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.6 +Version: 0.4.7 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index ecd21bc1..5c3008a2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -16,6 +16,9 @@ + Better comments to `fill_desc()` in `01_start.R` (#1021, @ilyaZar) ++ `01_start.R` now has a call to `usethis::use_git_remote()` (#1015, @ilyaZar) + + ## Bug fix + Docker commands now take the `-it` flag so it can be killed with `^C` (#, @ivokwee) diff --git a/README.md b/README.md index 2b056e51..c6ddef42 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.6 +You’re reading the doc about version : 0.4.7 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-04-04 17:33:09 CEST" +#> [1] "2023-04-04 21:03:49 CEST" ``` Here are the test & coverage results : @@ -33,8 +33,8 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.6 ──── -#> Duration: 1m 19.4s +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.7 ──── +#> Duration: 1m 16.9s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` From 3cb6494528678bd8cd214c27d5c0b1836143221b Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Sun, 2 Apr 2023 17:44:49 +0200 Subject: [PATCH 162/190] fix: improve code coverage for R/golem_utils_ui.R - fix missing tests for make_action_button - possible bugfix for make_action_button - check with grepl instead of grep to avoid numeric(0) in if-statement which would make the corresponding test fail for button_6: class attribute present but not equal to "action-button" gives an error --- inst/utils/golem_utils_ui.R | 2 +- inst/utils/test-golem_utils_ui.R | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/inst/utils/golem_utils_ui.R b/inst/utils/golem_utils_ui.R index 5a9ee8d4..609a0004 100644 --- a/inst/utils/golem_utils_ui.R +++ b/inst/utils/golem_utils_ui.R @@ -333,7 +333,7 @@ make_action_button <- function(tag, inputId = NULL) { # some obvious checks if (!inherits(tag, "shiny.tag")) stop("Must provide a shiny tag.") if (!is.null(tag$attribs$class)) { - if (grep("action-button", tag$attribs$class)) { + if (isTRUE(grepl("action-button", tag$attribs$class))) { stop("tag is already an action button") } } diff --git a/inst/utils/test-golem_utils_ui.R b/inst/utils/test-golem_utils_ui.R index a33935c8..1231bb58 100644 --- a/inst/utils/test-golem_utils_ui.R +++ b/inst/utils/test-golem_utils_ui.R @@ -165,8 +165,9 @@ test_that("Test columns wrappers works", { }) test_that("Test make_action_button works", { + tmp_tag <- a(href = "#", "My super link", style = "color: lightblue;") button <- make_action_button( - a(href = "#", "My super link", style = "color: lightblue;"), + tmp_tag, inputId = "mylink" ) expect_s3_class(button, "shiny.tag") @@ -174,4 +175,31 @@ test_that("Test make_action_button works", { as.character(button), 'My super link' ) + expect_error( + button_2 <- make_action_button( + unclass(tmp_tag), + inputId = "mylink_2") + ) + expect_error( + button_3 <- make_action_button( + button, + inputId = "mylink_3") + ) + expect_error( + button_4 <- make_action_button( + tmp_tag, + inputId = NULL) + ) + tmp_tag_2 <- tmp_tag + tmp_tag_2$attribs$id <- "id_already_present" + expect_warning( + button_5 <- make_action_button( + tmp_tag_2, + inputId = "mylink_5") + ) + tmp_tag_3 <- tmp_tag + tmp_tag_3$attribs$class <- "class_already_present" + button_6 <- make_action_button( + tmp_tag_3, + inputId = "someID") }) From e609c5239bbe99147c72ffa71ed92d2d21c20910 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Sun, 2 Apr 2023 18:20:09 +0200 Subject: [PATCH 163/190] fix: make code coverage of R/golem_utils_ui.R 100% - fix missing test for undisplay --- inst/utils/test-golem_utils_ui.R | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/inst/utils/test-golem_utils_ui.R b/inst/utils/test-golem_utils_ui.R index 1231bb58..f46a4429 100644 --- a/inst/utils/test-golem_utils_ui.R +++ b/inst/utils/test-golem_utils_ui.R @@ -103,6 +103,19 @@ test_that("Test undisplay works", { as.character(b_undisplay), '' ) + + c <- shiny::tags$p(src = "plop", style = "some_style", "pouet") + expect_s3_class(c, "shiny.tag") + expect_equal( + as.character(c), + '

pouet

' + ) + c_undisplay <- undisplay(c) + expect_s3_class(c_undisplay, "shiny.tag") + expect_equal( + as.character(c_undisplay), + '

pouet

' + ) }) test_that("Test display works", { From 4c6885afa9215f1e021a1f00ebdaa6c92786b3b7 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Sun, 2 Apr 2023 18:59:02 +0200 Subject: [PATCH 164/190] fix: make code coverage of R/golem_utils_server.R 100% - add missing tests for rv and rvtl --- inst/utils/test-golem_utils_server.R | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/inst/utils/test-golem_utils_server.R b/inst/utils/test-golem_utils_server.R index 770d3877..c003227b 100644 --- a/inst/utils/test-golem_utils_server.R +++ b/inst/utils/test-golem_utils_server.R @@ -51,4 +51,15 @@ test_that("rv and rvtl work", { expect_true( inherits(rvtl, "function") ) + + rv_test_1 <- rv(a = "a", b = 2) + rv_test_2 <- reactiveValues(a = "a", b = 2) + shiny::reactiveConsole(TRUE) + expect_identical(rv_test_1$a, rv_test_2$a) + expect_identical(rv_test_1$b, rv_test_2$b) + expect_identical( + rvtl(rv_test_2), + shiny::reactiveValuesToList(rv_test_1) + ) + shiny::reactiveConsole(FALSE) }) From 9cfa0c659c95e329e1c92bab3c595f32c7e8d9cb Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 21:23:59 +0200 Subject: [PATCH 165/190] style: formatting --- inst/utils/test-golem_utils_ui.R | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/inst/utils/test-golem_utils_ui.R b/inst/utils/test-golem_utils_ui.R index f46a4429..b9e96a4e 100644 --- a/inst/utils/test-golem_utils_ui.R +++ b/inst/utils/test-golem_utils_ui.R @@ -191,28 +191,33 @@ test_that("Test make_action_button works", { expect_error( button_2 <- make_action_button( unclass(tmp_tag), - inputId = "mylink_2") + inputId = "mylink_2" + ) ) expect_error( button_3 <- make_action_button( button, - inputId = "mylink_3") + inputId = "mylink_3" + ) ) expect_error( button_4 <- make_action_button( tmp_tag, - inputId = NULL) + inputId = NULL + ) ) tmp_tag_2 <- tmp_tag tmp_tag_2$attribs$id <- "id_already_present" expect_warning( button_5 <- make_action_button( tmp_tag_2, - inputId = "mylink_5") + inputId = "mylink_5" + ) ) tmp_tag_3 <- tmp_tag tmp_tag_3$attribs$class <- "class_already_present" button_6 <- make_action_button( tmp_tag_3, - inputId = "someID") + inputId = "someID" + ) }) From 90c09d90bf9d8cadc4c112126fdda0e494a356a3 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 4 Apr 2023 21:27:23 +0200 Subject: [PATCH 166/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 1 + README.md | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 99ee635b..15a32206 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.7 +Version: 0.4.8 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index 5c3008a2..791f30fb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -18,6 +18,7 @@ + `01_start.R` now has a call to `usethis::use_git_remote()` (#1015, @ilyaZar) ++ Test for utilsui and server now has full code coverate (#1020, @ilyaZar) ## Bug fix diff --git a/README.md b/README.md index c6ddef42..6b9bbeb4 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.7 +You’re reading the doc about version : 0.4.8 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-04-04 21:03:49 CEST" +#> [1] "2023-04-04 21:24:51 CEST" ``` Here are the test & coverage results : @@ -33,8 +33,8 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.7 ──── -#> Duration: 1m 16.9s +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.8 ──── +#> Duration: 1m 19.1s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` From 5ad4e21ccfde1013119a305fed2386ac1ff26c43 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Fri, 24 Mar 2023 16:15:24 +0100 Subject: [PATCH 167/190] Fix 997 in two ways 1. add a double check to look for modules with names starting with "mod_mod_xxx" (see R/add_r_files.R) 2. add syntax check for module names to add_module(): explicitly ask the user if s/he wants to name her module "mod_xxx" or "mod_mod_xxx" --- R/add_r_files.R | 20 ++++++++++++-------- R/modules_fn.R | 1 + R/utils.R | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/R/add_r_files.R b/R/add_r_files.R index 719927f6..eb8a42af 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -30,14 +30,18 @@ add_r_files <- function( # Remove the "mod_" if any module <- mod_remove(module) if (!is_existing_module(module)) { - stop( - sprintf( - "The module '%s' does not exist.\nYou can call `golem::add_module('%s')` to create it.", - module, - module - ), - call. = FALSE - ) + # Check for esoteric 'mod_mod_' module names and if that fails throw error + if (!is_existing_module(paste0("mod_", module))) { + stop( + sprintf( + "The module '%s' does not exist.\nYou can call `golem::add_module('%s')` to create it.", + module, + module + ), + call. = FALSE + ) + } + module <- paste0("mod_", module) } module <- paste0("mod_", module, "_") } diff --git a/R/modules_fn.R b/R/modules_fn.R index e6fd2e6f..1e92f016 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -40,6 +40,7 @@ add_module <- function( ) { check_name_length(name) name <- file_path_sans_ext(name) + name <- check_name_syntax(name) old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) diff --git a/R/utils.R b/R/utils.R index 908285ba..94c95b57 100644 --- a/R/utils.R +++ b/R/utils.R @@ -508,3 +508,31 @@ do_if_unquiet <- function(expr) { force(expr) } } + +# This functions checks that the 'name' argument +# of add_module() does not start with 'mod_' as +# this is prepended by add_module() per default. +check_name_syntax <- function(name) { + check_mod <- grepl("^mod_", name) + name_proposed <- gsub("^mod_", "", name) + if (isTRUE(check_mod)) { + msg <- paste0( + "Argument 'name' starts with 'mod_' but {golem} prepends 'mod_' to your", + " module name automatically.\nDo you want to name your module: " + ) + cat(msg) + ask <- menu(paste0("'mod_", c(name_proposed, name), "'?")) + if (ask == 1) { + return(name_proposed) + } else if (ask == 2) { + # optional + # message("Keeping name: '", name, "' as module name.") + return(name) + } else { + warning("Could not check name syntax properly ...") + return(invisible(name)) + } + } else { + return(name) + } +} From 886e9f94a59db56e6ee4fdedc69af978afaa1471 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 15:52:29 +0200 Subject: [PATCH 168/190] feat: adding an alert info --- R/boostrap_cli.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/R/boostrap_cli.R b/R/boostrap_cli.R index 8f9a9eda..f7d6541a 100644 --- a/R/boostrap_cli.R +++ b/R/boostrap_cli.R @@ -33,3 +33,13 @@ cli_cat_rule <- function(...) { ) }) } + +cli_cli_alert_info <- function(...) { + check_cli_installed() + + do_if_unquiet({ + cli::cli_alert_info( + ... + ) + }) +} From bea0407e945ccb3d98108c9c040499704170aa75 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 15:53:48 +0200 Subject: [PATCH 169/190] feat: correct use of the mod_remove in add_module Close #997 --- R/modules_fn.R | 71 ++++++++++++++++++++++++------- R/utils.R | 26 +++-------- tests/testthat/test-add_modules.R | 15 +++++++ 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/R/modules_fn.R b/R/modules_fn.R index 1e92f016..120f5823 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -37,14 +37,24 @@ add_module <- function( module_template = golem::module_template, with_test = FALSE, ... -) { + ) { + # Let's start with the checks for the validity of the name check_name_length(name) - name <- file_path_sans_ext(name) - name <- check_name_syntax(name) + check_name_syntax(name) + + # We now check that: + # - The file name has no "mod_" prefix + # - The file name has no extension + name <- mod_remove( + file_path_sans_ext(name) + ) + # Performing the creation inside the pkg root old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) + # The module creation only works if the R folder + # is there dir_created <- create_if_needed( fs_path(pkg, "R"), type = "directory" @@ -55,17 +65,23 @@ add_module <- function( return(invisible(FALSE)) } + # Now we build the correct module file name where <- fs_path( "R", paste0("mod_", name, ".R") ) + # If the file doesn't exist, we create it if (!fs_file_exists(where)) { fs_file_create(where) - module_template(name = name, path = where, export = export, ...) + module_template( + name = name, + path = where, + export = export, + ... + ) - # write_there(" ") cat_created(where) open_or_go_to(where, open) } else { @@ -75,20 +91,38 @@ add_module <- function( ) } + # Creating all the files that come with the module if (!is.null(fct)) { - add_fct(fct, module = name, open = open) + add_fct( + fct, + module = name, + open = open + ) } if (!is.null(utils)) { - add_utils(utils, module = name, open = open) + add_utils( + utils, + module = + name, + open = open + ) } if (!is.null(js)) { - add_js_file(js, pkg = pkg, open = open) + add_js_file( + js, + pkg = pkg, + open = open + ) } if (!is.null(js_handler)) { - add_js_handler(js_handler, pkg = pkg, open = open) + add_js_handler( + js_handler, + pkg = pkg, + open = open + ) } if (with_test) { @@ -153,7 +187,7 @@ module_template <- function( ph_ui = " ", ph_server = " ", ... -) { + ) { write_there <- function(...) { write(..., file = path, append = TRUE) } @@ -237,7 +271,7 @@ use_module_test <- function( name, pkg = get_golem_wd(), open = TRUE -) { + ) { # Remove the extension if any name <- file_path_sans_ext(name) # Remove the "mod_" if any @@ -339,9 +373,14 @@ use_module_test <- function( } mod_remove <- function(string) { - gsub( - "^mod_", - "", - string - ) + while ( + grepl("^mod_", string) + ) { + string <- gsub( + "^mod_", + "", + string + ) + } + string } diff --git a/R/utils.R b/R/utils.R index 94c95b57..279d7a92 100644 --- a/R/utils.R +++ b/R/utils.R @@ -513,26 +513,12 @@ do_if_unquiet <- function(expr) { # of add_module() does not start with 'mod_' as # this is prepended by add_module() per default. check_name_syntax <- function(name) { - check_mod <- grepl("^mod_", name) - name_proposed <- gsub("^mod_", "", name) - if (isTRUE(check_mod)) { - msg <- paste0( - "Argument 'name' starts with 'mod_' but {golem} prepends 'mod_' to your", - " module name automatically.\nDo you want to name your module: " + if (isTRUE(grepl("^mod_", name))) { + cli_cli_alert_info( + "You set a 'name' that starts with 'mod_'." + ) + cli_cli_alert_info( + "This is not necessary as golem will prepend 'mod_' to your module name automatically." ) - cat(msg) - ask <- menu(paste0("'mod_", c(name_proposed, name), "'?")) - if (ask == 1) { - return(name_proposed) - } else if (ask == 2) { - # optional - # message("Keeping name: '", name, "' as module name.") - return(name) - } else { - warning("Could not check name syntax properly ...") - return(invisible(name)) - } - } else { - return(name) } } diff --git a/tests/testthat/test-add_modules.R b/tests/testthat/test-add_modules.R index 85c767ea..9a98bd29 100644 --- a/tests/testthat/test-add_modules.R +++ b/tests/testthat/test-add_modules.R @@ -49,5 +49,20 @@ test_that("add_module", { remove_file("R/mod_test2.R") remove_file("R/mod_test2_fct_ftest.R") remove_file("R/mod_test2_utils_utest.R") + + # Checking that the mod_ prefix is removed and added + add_module( + "mod_mod_mod_test2.R", + open = FALSE, + pkg = pkg, + fct = "ftest", + utils = "utest" + ) + expect_true(file.exists("R/mod_test2.R")) + expect_true(file.exists("R/mod_test2_fct_ftest.R")) + expect_true(file.exists("R/mod_test2_utils_utest.R")) + remove_file("R/mod_test2.R") + remove_file("R/mod_test2_fct_ftest.R") + remove_file("R/mod_test2_utils_utest.R") }) }) From 72efe624a2ab93cd110d13783f76665544895042 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 16:13:12 +0200 Subject: [PATCH 170/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ README.md | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 15a32206..d05e3d55 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.8 +Version: 0.4.9 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index 791f30fb..09039554 100644 --- a/NEWS.md +++ b/NEWS.md @@ -24,6 +24,8 @@ + Docker commands now take the `-it` flag so it can be killed with `^C` (#, @ivokwee) ++ add_module() now behaves correctly when trying to use `mod_mod_XXX` (#997, @ilyaZar) + ## Internal changes # 0.4.0 diff --git a/README.md b/README.md index 6b9bbeb4..fedce6b9 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.8 +You’re reading the doc about version : 0.4.9 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-04-04 21:24:51 CEST" +#> [1] "2023-05-16 16:10:34 CEST" ``` Here are the test & coverage results : @@ -33,15 +33,15 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.8 ──── -#> Duration: 1m 19.1s +#> ── R CMD check results ──────────────────────────────────────── golem 0.4.9 ──── +#> Duration: 1m 8.9s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` ``` r covr::package_coverage() -#> golem Coverage: 69.04% +#> golem Coverage: 69.13% #> R/addins.R: 0.00% #> R/bootstrap_rstudio_api.R: 0.00% #> R/enable_roxygenize.R: 0.00% @@ -55,8 +55,8 @@ covr::package_coverage() #> R/test_helpers.R: 30.26% #> R/js.R: 43.75% #> R/reload.R: 45.36% -#> R/utils.R: 54.14% #> R/use_recommended.R: 54.55% +#> R/utils.R: 55.31% #> R/bootstrap_desc.R: 55.56% #> R/install_dev_deps.R: 57.14% #> R/bootstrap_attachment.R: 61.54% @@ -64,20 +64,19 @@ covr::package_coverage() #> R/add_dockerfiles.R: 74.19% #> R/boostrap_fs.R: 77.78% #> R/bootstrap_usethis.R: 78.57% -#> R/modules_fn.R: 80.00% +#> R/modules_fn.R: 79.00% #> R/use_utils.R: 83.33% #> R/use_favicon.R: 85.56% #> R/desc.R: 86.25% +#> R/boostrap_cli.R: 88.00% #> R/add_resource_path.R: 88.89% #> R/create_golem.R: 89.47% #> R/make_dev.R: 90.00% -#> R/add_r_files.R: 91.95% +#> R/add_r_files.R: 91.01% #> R/add_files.R: 92.31% #> R/add_rstudio_files.R: 93.10% #> R/golem-yaml-get.R: 93.18% #> R/add_dockerfiles_renv.R: 93.75% -#> R/use_readme.R: 97.14% -#> R/boostrap_cli.R: 100.00% #> R/boostrap_crayon.R: 100.00% #> R/bootstrap_pkgload.R: 100.00% #> R/bootstrap_roxygen2.R: 100.00% @@ -91,6 +90,7 @@ covr::package_coverage() #> R/pkg_tools.R: 100.00% #> R/set_golem_options.R: 100.00% #> R/templates.R: 100.00% +#> R/use_readme.R: 100.00% ``` ## Tool series From 72ee9721a4b17f3955a321e62a586bdb799d15b2 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 5 Apr 2023 19:22:18 +0200 Subject: [PATCH 171/190] fix: set_golem_name updates name for ./tests content - save old golem name in tmp-variable and use helper-func inside set_golem_name() to overwrite tests/testthat.R - helper-func screens for old_name instances and replaces them with new_name Refs: #805 --- R/golem-yaml-set.R | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 6326acad..29a32955 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -33,6 +33,7 @@ set_golem_name <- function( pkg = golem::pkg_path(), talkative = TRUE ) { + old_name <- golem::pkg_name() path <- fs_path_abs(pkg) # Changing in YAML @@ -63,9 +64,35 @@ set_golem_name <- function( file = "DESCRIPTION" ) + # Changing in ./tests/ if dir present + set_golem_name_tests( + old_name = old_name, + new_name = name + ) + invisible(name) } +set_golem_name_tests <- function( + old_name, + new_name +) { + pth_dir_tests <- file.path( + get_golem_wd(), + "tests") + + check_dir_tests <- fs_dir_exists(pth_dir_tests) + + if (check_dir_tests) { + pth_testthat_r <- file.path(pth_dir_tests, "testthat.R") + old_testthat_r <- readLines(pth_testthat_r) + new_testthat_r <- gsub(old_name, new_name, old_testthat_r) + writeLines(new_testthat_r, pth_testthat_r) + } + + return(invisible(old_name)) +} + #' @export #' @rdname golem_opts set_golem_version <- function( From 8e23a4323b588b29d5deaf8cec408c13bf3f9648 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 5 Apr 2023 19:42:10 +0200 Subject: [PATCH 172/190] fix: set_golem_name updates name for ./vignettes content - save in tmp-variable and use helper-func inside set_golem_name() to change file name from "vignettes/.R" to "vignettes/.R" - helper-func screens for old_name instances inside .R> and replaces them with new_name, then saves output to "vignettes/.R" Refs: #805 --- R/golem-yaml-set.R | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 29a32955..8a7cd976 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -70,6 +70,12 @@ set_golem_name <- function( new_name = name ) + # Changing in ./vignettes/ if dir present + set_golem_name_vignettes( + old_name = old_name, + new_name = name + ) + invisible(name) } @@ -93,6 +99,35 @@ set_golem_name_tests <- function( return(invisible(old_name)) } +set_golem_name_vignettes <- function( + old_name, + new_name +) { + pth_dir_vignettes <- file.path( + get_golem_wd(), + "vignettes") + + check_dir_vignettes <- fs_dir_exists(pth_dir_vignettes) + + if (check_dir_vignettes) { + pth_vignette_old <- file.path( + pth_dir_vignettes, + paste0(old_name, ".Rmd") + ) + old_vignette_r <- readLines(pth_vignette_old) + new_vignette_r <- gsub(old_name, new_name, old_vignette_r) + + pth_vignette_new <- file.path( + pth_dir_vignettes, + paste0(new_name, ".Rmd") + ) + writeLines(new_vignette_r, pth_vignette_new) + file.remove(pth_vignette_old) + } + + return(invisible(old_name)) +} + #' @export #' @rdname golem_opts set_golem_version <- function( From 61e1556337e72bf4e02286c5753884c341784d33 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 17:00:58 +0200 Subject: [PATCH 173/190] feat: message and path - adding a message to the user to check for other places for the name - passing the path to sub functions --- R/golem-yaml-set.R | 47 +++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 8a7cd976..9b8a518d 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -4,7 +4,7 @@ set_golem_wd <- function( golem_wd = golem::pkg_path(), pkg = golem::pkg_path(), talkative = TRUE -) { + ) { if ( golem_wd == "golem::pkg_path()" | normalizePath(golem_wd) == normalizePath(golem::pkg_path()) @@ -31,9 +31,9 @@ set_golem_wd <- function( set_golem_name <- function( name = golem::pkg_name(), pkg = golem::pkg_path(), - talkative = TRUE -) { - old_name <- golem::pkg_name() + talkative = TRUE, + old_name = golem::pkg_name() + ) { path <- fs_path_abs(pkg) # Changing in YAML @@ -44,6 +44,7 @@ set_golem_name <- function( pkg = pkg, talkative = talkative ) + # Changing in app-config.R change_app_config_name( name = name, @@ -67,13 +68,25 @@ set_golem_name <- function( # Changing in ./tests/ if dir present set_golem_name_tests( old_name = old_name, - new_name = name + new_name = name, + path = path ) # Changing in ./vignettes/ if dir present set_golem_name_vignettes( old_name = old_name, - new_name = name + new_name = name, + path = path + ) + + cli_cli_alert_info( + "The name of your app has been changed to {name}.", + ) + cli_cli_alert_info( + "Please note that the old name {old_name} might still be in some places, for example in the ./docs folder." + ) + cli_cli_alert_info( + "You might need to change it manually there.", ) invisible(name) @@ -81,11 +94,13 @@ set_golem_name <- function( set_golem_name_tests <- function( old_name, - new_name -) { + new_name, + path + ) { pth_dir_tests <- file.path( - get_golem_wd(), - "tests") + path, + "tests" + ) check_dir_tests <- fs_dir_exists(pth_dir_tests) @@ -101,11 +116,13 @@ set_golem_name_tests <- function( set_golem_name_vignettes <- function( old_name, - new_name -) { + new_name, + path + ) { pth_dir_vignettes <- file.path( - get_golem_wd(), - "vignettes") + path, + "vignettes" + ) check_dir_vignettes <- fs_dir_exists(pth_dir_vignettes) @@ -134,7 +151,7 @@ set_golem_version <- function( version = golem::pkg_version(), pkg = golem::pkg_path(), talkative = TRUE -) { + ) { path <- fs_path_abs(pkg) # Changing in YAML From 0255f45df7439de47e48c9349790e39e4b1f1bfd Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 17:07:30 +0200 Subject: [PATCH 174/190] hofix: changing to sprintf --- R/golem-yaml-set.R | 4 ++-- man/golem_opts.Rd | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index 9b8a518d..f7764ca5 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -80,10 +80,10 @@ set_golem_name <- function( ) cli_cli_alert_info( - "The name of your app has been changed to {name}.", + sprintf("The name of your app has been changed to %s", name) ) cli_cli_alert_info( - "Please note that the old name {old_name} might still be in some places, for example in the ./docs folder." + sprintf("Please note that the old name %s might still be in some places, for example in the ./docs folder.", old_name) ) cli_cli_alert_info( "You might need to change it manually there.", diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index e6c115bf..faae8277 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -34,7 +34,8 @@ set_golem_wd( set_golem_name( name = golem::pkg_name(), pkg = golem::pkg_path(), - talkative = TRUE + talkative = TRUE, + old_name = golem::pkg_name() ) set_golem_version( From ed7e0b9aa9d6b083d57fb9407a7bb8206c458495 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 17:07:42 +0200 Subject: [PATCH 175/190] test: unlinking the README files in test --- tests/testthat/test-zzzzzzzzzz.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 63480161..53321f1a 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -1,5 +1,7 @@ # # For setting back old usethis settings try({ + unlink("README.Rmd", TRUE, TRUE) + unlink("README.md", TRUE, TRUE) unlink(pkg, TRUE, TRUE) options("usethis.quiet" = old_usethis.quiet) }) From b72e98f161dfdf5d0afb99395c7a5be69da111d5 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 17:15:33 +0200 Subject: [PATCH 176/190] doc: missing doc in the entry --- R/golem-yaml-set.R | 2 +- R/set_golem_options.R | 1 + man/golem_opts.Rd | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index f7764ca5..a814a5d0 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -33,7 +33,7 @@ set_golem_name <- function( pkg = golem::pkg_path(), talkative = TRUE, old_name = golem::pkg_name() - ) { +) { path <- fs_path_abs(pkg) # Changing in YAML diff --git a/R/set_golem_options.R b/R/set_golem_options.R index e371c731..fbd5b5d3 100644 --- a/R/set_golem_options.R +++ b/R/set_golem_options.R @@ -26,6 +26,7 @@ #' @param name The name of the app #' @param version The version of the app #' @param config_file path to the {golem} config file +#' @param old_name The old name of the app, used when changing the name #' @inheritParams config::get #' #' @rdname golem_opts diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index faae8277..131887a4 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -70,6 +70,8 @@ the value of the \code{R_CONFIG_ACTIVE} environment variable \item{name}{The name of the app} +\item{old_name}{The old name of the app, used when changing the name} + \item{version}{The version of the app} \item{golem_name}{Name of the current golem.} From e2428c93a0ff3e40d7bb3c2d3316216fbf9e7dd8 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 16 May 2023 17:18:36 +0200 Subject: [PATCH 177/190] chore: version bump & news update --- DESCRIPTION | 2 +- NEWS.md | 2 ++ README.md | 16 ++++++++-------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d05e3d55..61338070 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.4.9 +Version: 0.4.10 Authors@R: c( person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), comment = c(ORCID = "0000-0001-7343-1846")), diff --git a/NEWS.md b/NEWS.md index 09039554..b75e8e37 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,8 @@ + Test for utilsui and server now has full code coverate (#1020, @ilyaZar) ++ When setting a new name, golem now browses tests & vignettes (#805, @ilyaZar) + ## Bug fix + Docker commands now take the `-it` flag so it can be killed with `^C` (#, @ivokwee) diff --git a/README.md b/README.md index fedce6b9..d00f62c7 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ shiny applications. ## About -You’re reading the doc about version : 0.4.9 +You’re reading the doc about version : 0.4.10 This README has been compiled on the ``` r Sys.time() -#> [1] "2023-05-16 16:10:34 CEST" +#> [1] "2023-05-16 17:15:55 CEST" ``` Here are the test & coverage results : @@ -33,15 +33,15 @@ Here are the test & coverage results : ``` r devtools::check(quiet = TRUE) #> ℹ Loading golem -#> ── R CMD check results ──────────────────────────────────────── golem 0.4.9 ──── -#> Duration: 1m 8.9s +#> ── R CMD check results ─────────────────────────────────────── golem 0.4.10 ──── +#> Duration: 1m 8.3s #> #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ ``` ``` r covr::package_coverage() -#> golem Coverage: 69.13% +#> golem Coverage: 69.29% #> R/addins.R: 0.00% #> R/bootstrap_rstudio_api.R: 0.00% #> R/enable_roxygenize.R: 0.00% @@ -68,8 +68,8 @@ covr::package_coverage() #> R/use_utils.R: 83.33% #> R/use_favicon.R: 85.56% #> R/desc.R: 86.25% -#> R/boostrap_cli.R: 88.00% #> R/add_resource_path.R: 88.89% +#> R/golem-yaml-set.R: 88.89% #> R/create_golem.R: 89.47% #> R/make_dev.R: 90.00% #> R/add_r_files.R: 91.01% @@ -77,20 +77,20 @@ covr::package_coverage() #> R/add_rstudio_files.R: 93.10% #> R/golem-yaml-get.R: 93.18% #> R/add_dockerfiles_renv.R: 93.75% +#> R/use_readme.R: 97.14% +#> R/boostrap_cli.R: 100.00% #> R/boostrap_crayon.R: 100.00% #> R/bootstrap_pkgload.R: 100.00% #> R/bootstrap_roxygen2.R: 100.00% #> R/browser_button.R: 100.00% #> R/bundle_resources.R: 100.00% #> R/disable_autoload.R: 100.00% -#> R/golem-yaml-set.R: 100.00% #> R/golem-yaml-utils.R: 100.00% #> R/is_golem.R: 100.00% #> R/is_running.R: 100.00% #> R/pkg_tools.R: 100.00% #> R/set_golem_options.R: 100.00% #> R/templates.R: 100.00% -#> R/use_readme.R: 100.00% ``` ## Tool series From b97bbe7217a93fccf8a6f9f1293deac33547292d Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 5 Apr 2023 14:53:08 +0200 Subject: [PATCH 178/190] fix: allow golem to use person vector - remove 'author_XXX' args and replace with 'authors' - make 'authors' arg default to class 'person' - use class check on 'authors', throw error if it fails - use desc$set_authors This mostly draws from the ideas of @jmeyer2482 and @ColinFay: Co-authored-by: Colin Fay Co-authored-by: jmeyer2482 --- R/desc.R | 48 +++++++++++++----------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/R/desc.R b/R/desc.R index c939c4a2..533cb61b 100644 --- a/R/desc.R +++ b/R/desc.R @@ -3,10 +3,8 @@ #' @param pkg_name The name of the package #' @param pkg_title The title of the package #' @param pkg_description Description of the package -#' @param author_first_name First Name of the author -#' @param author_last_name Last Name of the author -#' @param author_email Email of the author -#' @param author_orcid ORCID of the author +#' @param authors a character string (or vector) of class person +#' (see [person()] for details) #' @param repo_url URL (if needed) #' @param pkg_version The version of the package. Default is 0.0.0.9000 #' @param pkg Path to look for the DESCRIPTION. Default is `get_golem_wd()`. @@ -19,47 +17,27 @@ fill_desc <- function( pkg_name, pkg_title, pkg_description, - author_first_name, - author_last_name, - author_email, - author_orcid = NULL, + authors = c(person("Angelo", + "Canty", + role = "aut", + comment = "S original, "), + person(c("Brian", "D."), + "Ripley", + role = c("aut", "trl", "cre"), + comment = "R port", + email = "ripley@stats.ox.ac.uk")), repo_url = NULL, pkg_version = "0.0.0.9000", pkg = get_golem_wd() ) { + stopifnot(`'authors' must be of class 'person'` = inherits(authors, "person")) path <- fs_path_abs(pkg) desc <- desc_description( file = fs_path(path, "DESCRIPTION") ) + desc$set_authors(authors) - if (!is.null(author_orcid) & !is.character(author_orcid)) { - stop("ORCID ID must be provided as a character object") - } - - - if (is.null(author_orcid)) { - desc$set( - "Authors@R", - sprintf( - "person('%s', '%s', email = '%s', role = c('cre', 'aut'))", - author_first_name, - author_last_name, - author_email - ) - ) - } else { - desc$set( - "Authors@R", - sprintf( - "person('%s', '%s', email = '%s', role = c('cre', 'aut'), comment = c(ORCID = '%s'))", - author_first_name, - author_last_name, - author_email, - author_orcid - ) - ) - } desc$del( keys = "Maintainer" ) From d93fdb8fbee7c291ebd6e94fd5980ff76bef8145 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 5 Apr 2023 15:05:03 +0200 Subject: [PATCH 179/190] merge interface changes into dev/01_start.R --- inst/shinyexample/dev/01_start.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 2997851f..0996b09a 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -22,9 +22,11 @@ golem::fill_desc( pkg_name = "shinyexample", # The name of the golem package containing the app (typically lowercase, no underscore or periods) pkg_title = "PKG_TITLE", # What the Package Does (One Line, Title Case, No Period) pkg_description = "PKG_DESC.", # What the package does (one paragraph). - author_first_name = "AUTHOR_FIRST", # Your First Name - author_last_name = "AUTHOR_LAST", # Your Last Name - author_email = "AUTHOR@MAIL.COM", # Your email + authors = person( + given = "AUTHOR_FIRST", # Your First Name + family = "AUTHOR_LAST", # Your Last Name + email = "AUTHOR@MAIL.COM" # Your email + ), repo_url = NULL, # The URL of the GitHub repo (optional), pkg_version = "0.0.0.9000" # The version of the package containing the app ) From a807104a3b073afb7f12121262b626e696088982 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 5 Apr 2023 15:05:20 +0200 Subject: [PATCH 180/190] docs: update vignette - update vignette/a_start.Rmd with new fill_desc() interface - delete some redundant whitespaces in same file --- vignettes/a_start.Rmd | 46 ++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 6e17673c..8535ab89 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -32,25 +32,25 @@ install.packages("golem") ``` -The development version of `{golem}` can be installed from GitHub using the `{remotes}` package: +The development version of `{golem}` can be installed from GitHub using the `{remotes}` package: ```{r} remotes::install_github("Thinkr-open/golem") ``` -## Getting started +## Getting started -Note before using `{golem}`: +Note before using `{golem}`: - A `{golem}` app is contained inside a package, so knowing how to build a package is highly recommended. On the plus side, everything you know about package development can be reused in `{golem}`. -- A `{golem}` app works better if you are working with `shiny modules`, so knowing how modules work is recommended, but not mandatory. +- A `{golem}` app works better if you are working with `shiny modules`, so knowing how modules work is recommended, but not mandatory. -In the rest of the Vignettes, we'll assume you're working in RStudio. +In the rest of the Vignettes, we'll assume you're working in RStudio. ### Create a package -Once the package is installed, you can got to File > New Project... in RStudio, and choose "Package for Shiny App Using golem" input. +Once the package is installed, you can got to File > New Project... in RStudio, and choose "Package for Shiny App Using golem" input. ```{r, echo=FALSE, out.width="80%", fig.align="center", eval=TRUE} knitr::include_graphics("golemtemplate.png") @@ -69,7 +69,7 @@ try(fs::dir_delete(x), silent = TRUE) golem::create_golem(path = x, package_name = "golex", open = FALSE) ``` -This command allows you to create "illegally-named" package (for example, `1234`) by passing the `check_name` argument to `FALSE`. Note that this is not recommended and __should only be done if you know what you are doing__. +This command allows you to create "illegally-named" package (for example, `1234`) by passing the `check_name` argument to `FALSE`. Note that this is not recommended and __should only be done if you know what you are doing__. Once you've got that, a new RStudio project will be launched. Here is the structure of this project: @@ -91,19 +91,19 @@ If you're already familiar with R packages, most of these files will seem very f + `R/app_config.R`: Used to read inside `{golem}` config file located at `inst/golem-config.yml`. -+ `R/app_server.R`, `R/app_ui.R`: Top level UI and server elements. ++ `R/app_server.R`, `R/app_ui.R`: Top level UI and server elements. + `R/run_app.R`: a function to configure and launch the application. + `dev/`: Scripts that will be used along the process of developing your app. You don't need to fill all the script before starting: use them as a notebook for keeping track of what you're doing all along the project life. -+ `inst/app/www`: Where you will add external dependencies in `www` (images, css, etc), notably added with the `golem` functions used to create external resources. ++ `inst/app/www`: Where you will add external dependencies in `www` (images, css, etc), notably added with the `golem` functions used to create external resources. -+ `man`: Package documentation, to be generated by R & `{roxygen2}`. ++ `man`: Package documentation, to be generated by R & `{roxygen2}`. ## `dev/01_start.R` -Once you've created your project, the first file that opens is `dev/01_start.R`. This file contains a series of commands that you'll have to run once, at the beginning of the project. +Once you've created your project, the first file that opens is `dev/01_start.R`. This file contains a series of commands that you'll have to run once, at the beginning of the project. Note that you don't have to fill everything, event thought it's strongly recommended. @@ -113,14 +113,16 @@ First, fill the DESCRIPTION by adding information about the package that will co ```{r } golem::fill_desc( - pkg_name = "shinyexample", # The Name of the package containing the App - pkg_title = "PKG_TITLE", # The Title of the package containing the App - pkg_description = "PKG_DESC.", # The Description of the package containing the App - author_first_name = "AUTHOR_FIRST", # Your First Name - author_last_name = "AUTHOR_LAST", # Your Last Name - author_email = "AUTHOR@MAIL.COM", # Your Email - repo_url = NULL, # The (optional) URL of the GitHub Repo - pkg_version = "0.0.0.9000" # The Version of the package containing the App + pkg_name = "shinyexample", # The name of the golem package containing the app (typically lowercase, no underscore or periods) + pkg_title = "PKG_TITLE", # What the Package Does (One Line, Title Case, No Period) + pkg_description = "PKG_DESC.", # What the package does (one paragraph). + authors = person( + given = "AUTHOR_FIRST", # Your First Name + family = "AUTHOR_LAST", # Your Last Name + email = "AUTHOR@MAIL.COM" # Your email + ), + repo_url = NULL, # The URL of the GitHub repo (optional), + pkg_version = "0.0.0.9000" # The version of the package containing the app ) ``` @@ -134,7 +136,7 @@ Please DO run this line of code, as it sets a series of global options inside `g golem::set_golem_options() ``` -### Set common Files +### Set common Files If you want to use the MIT license, README, code of conduct, lifecycle badge, a news file, etc. @@ -178,7 +180,7 @@ golem::remove_favicon() golem::use_favicon(path = "path/to/favicon") ``` -Note that you can add an url, and the favicon will be downloaded to the `inst/app/www` folder. +Note that you can add an url, and the favicon will be downloaded to the `inst/app/www` folder. > **Note**: If you are deploying your app with [ShinyProxy](https://www.shinyproxy.io/), your favicon should have the `.png` extension, otherwise it is not going to work. @@ -195,7 +197,7 @@ golem::use_utils_server() ## Try the app -To run the app, launch : +To run the app, launch : ```{r} golem::run_dev() From 0a529cafbd8db0b70d0ad387cd4a92a1d41e0844 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 5 Apr 2023 15:07:13 +0200 Subject: [PATCH 181/190] refactor: update tests for fill_desc() - changes since values of authors are of type person() which requires parsing the entries --- tests/testthat/test-desc.R | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 7561442f..fe1a6936 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -10,9 +10,11 @@ test_that("desc works", { pkg_name = fakename, pkg_title = "newtitle", pkg_description = "Newdescription.", - author_first_name = "firstname", - author_last_name = "lastname", - author_email = "name@test.com", + authors = person( + given = "firstname", + family = "lastname", + email = "name@test.com" + ), repo_url = "http://repo_url.com", pkg_version = "0.0.0.9000" ) @@ -23,9 +25,7 @@ test_that("desc works", { fakename, "newtitle", "Newdescription.", - "firstname", - "lastname", - "name@test.com", + "person('firstname', 'lastname', , 'name@test.com')", "http://repo_url.com", "0.0.0.9000" ) @@ -34,13 +34,19 @@ test_that("desc works", { expect_true( all( as.logical(lapply( - add_desc, + add_desc[-4], function(x) { any(grepl(x, desc)) } )) ) ) + # add additional test, as authors = person(...) requires parsing test + tmp_test_add_desc <- eval(parse(text = add_desc[4])) + tmp_test_desc <- eval(parse(text = desc[[5]])) + expect_identical( + tmp_test_add_desc, + tmp_test_desc) expect_true( stringr::str_detect(output, "DESCRIPTION file modified") From 7fe091538c9b1ed33a33631ed5f86597c1adafc7 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 5 Apr 2023 16:05:16 +0200 Subject: [PATCH 182/190] refactor: update inst/mantests/build.R - adjust interace of fill_desc() so build.R runs smoothly --- inst/mantests/build.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 3dbe73fb..c3875135 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -183,9 +183,11 @@ golem::fill_desc( pkg_name = "golemmetrics", # The Name of the package containing the App pkg_title = "A App with Metrics about 'Golem'", # The Title of the package containing the App pkg_description = "Read metrics about {golem}.", # The Description of the package containing the App - author_first_name = "Colin", # Your First Name - author_last_name = "Fay", # Your Last Name - author_email = "colin@thinkr.fr", # Your Email + authors = person( + given = "Colin", # Your First Name + family = "Fay", # Your Last Name + role = c("cre", "aut"), + email = "colin@thinkr.fr"), repo_url = NULL, # The URL of the GitHub Repo (optional) pkg_version = "0.0.0.9000" # The Version of the package containing the App ) From 9e21f4111a7d16bcefd92f3928fcf02f2b8ba991 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 17 May 2023 09:12:00 +0200 Subject: [PATCH 183/190] test: fix tests by adding authors + expect warning --- tests/testthat/DESCRIPTION | 9 ++++-- tests/testthat/test-desc.R | 29 ++++++++++++++++++-- tests/testthat/test-renv_stuff.R | 47 ++++++++++++++++++++++---------- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/tests/testthat/DESCRIPTION b/tests/testthat/DESCRIPTION index f2f99f57..d0637eec 100644 --- a/tests/testthat/DESCRIPTION +++ b/tests/testthat/DESCRIPTION @@ -1,13 +1,16 @@ Package: shinyexample Title: newtitle Version: 0.0.0.9000 -Authors@R: person('firstname', 'lastname', email = 'name@test.com', role = c('cre', 'aut')) +Authors@R: c( + person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), + comment = c(ORCID = "0000-0001-7343-1846")) + ) Description: Newdescription. License: What license is it under? Encoding: UTF-8 LazyData: true -Imports: - shiny, +Imports: + shiny, golem RoxygenNote: 6.1.0 URL: http://repo_url.com diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index fe1a6936..c1b7aa51 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -1,4 +1,3 @@ - test_that("desc works", { testthat::skip_if_not_installed("desc") with_dir(pkg, { @@ -46,10 +45,36 @@ test_that("desc works", { tmp_test_desc <- eval(parse(text = desc[[5]])) expect_identical( tmp_test_add_desc, - tmp_test_desc) + tmp_test_desc + ) expect_true( stringr::str_detect(output, "DESCRIPTION file modified") ) + + # test retrocompatibility + withr::with_options( + c("golem.quiet" = FALSE), + { + expect_warning( + fill_desc( + pkg_name = fakename, + pkg_title = "newtitle", + pkg_description = "Newdescription.", + author_first_name = "firstname", + author_last_name = "lastname", + author_email = "test@test.com" + ) + ) + expect_equal( + as.character(desc::desc_get("Title")), + "newtitle" + ) + expect_equal( + as.character(desc::desc_get_authors()), + "firstname lastname " + ) + } + ) }) }) diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R index 07853af1..6c226434 100644 --- a/tests/testthat/test-renv_stuff.R +++ b/tests/testthat/test-renv_stuff.R @@ -9,6 +9,15 @@ test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all outp add_dockerfile_with_renv_heroku )) { deploy_folder <- create_deploy_folder() + desc::desc_set_authors( + person( + given = "colin", + family = "fay", + email = "contact@colinfay.me", + role = c("aut", "cre"), + comment = c(ORCID = "0000-0001-5879-4195") + ) + ) fun(output_dir = deploy_folder, open = FALSE) @@ -22,25 +31,35 @@ test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all outp } }) }) -test_that("suggested package ore not in renv prod", { +test_that("suggested package are not in renv prod", { skip_if_not_installed("renv") skip_if_not_installed("dockerfiler", "0.2.0") skip_if_not_installed("attachment", "0.3.1") - with_dir(pkg, { - desc_file <- file.path("DESCRIPTION") - desc_lines <- readLines(desc_file) - # desc_lines <- c(desc_lines,"Suggests: \n idontexist") - desc_lines[desc_lines == "Suggests: "] <- "Suggests: \n idontexist," - writeLines(desc_lines,desc_file) - deploy_folder <- create_deploy_folder() - - add_dockerfile_with_renv(output_dir = deploy_folder, open = FALSE) + with_dir( + pkg, + { + desc_file <- file.path("DESCRIPTION") + desc_lines <- readLines(desc_file) + # desc_lines <- c(desc_lines,"Suggests: \n idontexist") + desc_lines[desc_lines == "Suggests: "] <- "Suggests: \n idontexist," + writeLines(desc_lines, desc_file) + deploy_folder <- create_deploy_folder() + desc::desc_set_authors( + person( + given = "colin", + family = "fay", + email = "contact@colinfay.me", + role = c("aut", "cre"), + comment = c(ORCID = "0000-0001-5879-4195") + ) + ) + add_dockerfile_with_renv(output_dir = deploy_folder, open = FALSE) - base <- paste(readLines(file.path(deploy_folder,"renv.lock.prod")),collapse = " ") - expect_false(grepl(pattern = "idontexist",x = base)) - expect_true(grepl(pattern = "shiny",x = base)) + base <- paste(readLines(file.path(deploy_folder, "renv.lock.prod")), collapse = " ") + expect_false(grepl(pattern = "idontexist", x = base)) + expect_true(grepl(pattern = "shiny", x = base)) - unlink(deploy_folder, force = TRUE, recursive = TRUE) + unlink(deploy_folder, force = TRUE, recursive = TRUE) } ) }) From 5b9767ce21545019c7ffa780759b8093326d3537 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 17 May 2023 09:12:19 +0200 Subject: [PATCH 184/190] fix: only alert if named has changed --- R/golem-yaml-set.R | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R index a814a5d0..b84fb461 100644 --- a/R/golem-yaml-set.R +++ b/R/golem-yaml-set.R @@ -79,15 +79,14 @@ set_golem_name <- function( path = path ) - cli_cli_alert_info( - sprintf("The name of your app has been changed to %s", name) - ) - cli_cli_alert_info( - sprintf("Please note that the old name %s might still be in some places, for example in the ./docs folder.", old_name) - ) - cli_cli_alert_info( - "You might need to change it manually there.", - ) + if (old_name != name){ + cli_cli_alert_info( + sprintf("Please note that the old name %s might still be in some places, for example in the ./docs folder.", old_name) + ) + cli_cli_alert_info( + "You might need to change it manually there.", + ) + } invisible(name) } From 090a6fcc0aa4f8b4d701550e5ee4e7a3ba78fdae Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 17 May 2023 09:13:26 +0200 Subject: [PATCH 185/190] fix: keep the old params for backward compatibility --- R/desc.R | 63 +++++++++++++++++++++++++++----- inst/shinyexample/dev/01_start.R | 2 +- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/R/desc.R b/R/desc.R index 533cb61b..dec57239 100644 --- a/R/desc.R +++ b/R/desc.R @@ -17,20 +17,63 @@ fill_desc <- function( pkg_name, pkg_title, pkg_description, - authors = c(person("Angelo", - "Canty", - role = "aut", - comment = "S original, "), - person(c("Brian", "D."), - "Ripley", - role = c("aut", "trl", "cre"), - comment = "R port", - email = "ripley@stats.ox.ac.uk")), + authors = person( + given = NULL, + family = NULL, + email = NULL, + role = NULL, + comment = NULL + ), repo_url = NULL, pkg_version = "0.0.0.9000", - pkg = get_golem_wd() + pkg = get_golem_wd(), + author_first_name = NULL, + author_last_name = NULL, + author_email = NULL, + author_orcid = NULL ) { + stopifnot(`'authors' must be of class 'person'` = inherits(authors, "person")) + + # Handling retrocompatibility + + # Case 1 : old author params are not null + any_author_params_is_not_null <- all( + vapply( + list( + author_first_name, + author_last_name, + author_email, + author_orcid + ), is.null, logical(1) + ) + ) + + if (!any_author_params_is_not_null) { + warning("The `author_first_name`, `author_last_name`, `author_email` and `author_orcid` parameters will be deprecated from fill_desc() in the next version of {golem}. \nPlease use the `authors` parameter instead.\nSee ?person for more details on how to use it.") + # Case 1.1 : old author params are null and authors is empty + if (length(authors) == 0) { + # We use the old author params to fill the DESCRIPTION file + cli_cli_alert_info( + "the `authors` argument is empty, using `author_first_name`, `author_last_name`, `author_email` and `author_orcid` to fill the DESCRIPTION file." + ) + authors <- person( + given = author_first_name, + family = author_last_name, + email = author_email, + role = NULL, + comment = c(ORCID = author_orcid) + ) + } else { + # Case 1.2, old author params are null and authors is not empty + # We keep the authors as is + cli_cli_alert_info( + "the `authors` argument is not empty, using it to fill the DESCRIPTION file, the old author params are ignored." + ) + } + } + # the else here is the case 2 : old author params are null and authors is set, we keep the authors as is + path <- fs_path_abs(pkg) desc <- desc_description( diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 0996b09a..c4a37bfc 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -26,7 +26,7 @@ golem::fill_desc( given = "AUTHOR_FIRST", # Your First Name family = "AUTHOR_LAST", # Your Last Name email = "AUTHOR@MAIL.COM" # Your email - ), + ), repo_url = NULL, # The URL of the GitHub repo (optional), pkg_version = "0.0.0.9000" # The version of the package containing the app ) From c7533e4aa4082f26bbddef80934cfd2fa6143bde Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Tue, 4 Apr 2023 10:33:59 +0200 Subject: [PATCH 186/190] Revert "Add -it flag when run docker so ^C still works" This reverts commit ac237d5d270a5def6b3c67544b537f2ab51b4a2d. --- R/add_dockerfiles_renv.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R index 3039a7c1..24afc09f 100644 --- a/R/add_dockerfiles_renv.R +++ b/R/add_dockerfiles_renv.R @@ -206,7 +206,7 @@ add_dockerfile_with_renv <- function( out <- sprintf( "docker build -f Dockerfile_base --progress=plain -t %s . docker build -f Dockerfile --progress=plain -t %s . -docker run -it -p %s:%s %s +docker run -p %s:%s %s # then go to 127.0.0.1:%s", tolower(paste0(golem::get_golem_name(), "_base")), tolower(paste0(golem::get_golem_name(), ":latest")), From aa9bfb244dc9d025cabdd8a1fce6771d4ef87d0c Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 17 May 2023 09:16:11 +0200 Subject: [PATCH 187/190] ci: code coverage --- .github/workflows/test-coverage.yaml | 50 ++++++++++++++++++++++++++++ codecov.yml | 2 ++ 2 files changed, 52 insertions(+) create mode 100644 .github/workflows/test-coverage.yaml diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 00000000..2c5bb502 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,50 @@ +# 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: + branches: [main, master] + +name: test-coverage + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage + + - name: Test coverage + run: | + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) + shell: Rscript {0} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/codecov.yml b/codecov.yml index 8f36b6cc..04c55859 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,7 +6,9 @@ coverage: default: target: auto threshold: 1% + informational: true patch: default: target: auto threshold: 1% + informational: true From f73055021f66ed09a5019f645d4b28cdb97a20c8 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Fri, 24 Mar 2023 19:08:20 +0100 Subject: [PATCH 188/190] Fix 987 --- R/modules_fn.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/modules_fn.R b/R/modules_fn.R index 120f5823..13b3293b 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -243,8 +243,8 @@ module_template <- function( } else { write_there("#' @noRd ") } - write_there(sprintf("mod_%s_server <- function(id) {", name)) - write_there(" moduleServer(id, function(input, output, session) {") + write_there(sprintf("mod_%s_server <- function(id){", name)) + write_there(" moduleServer(id, function(input, output, session){") write_there(" ns <- session$ns") write_there(ph_server) write_there(" })") From 41d3c77b4d7be05103de9002f9b85433850f1d99 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 24 May 2023 14:20:40 +0200 Subject: [PATCH 189/190] fix: add user supplied path to config for guess_where_config() - add helper try_user_config_location() that searchers for the new config location which is to be set inside R/app_config.R - update guess_where_config() with a final guess using this helper --- R/config.R | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/R/config.R b/R/config.R index 1d0bd68c..744336e1 100644 --- a/R/config.R +++ b/R/config.R @@ -55,9 +55,43 @@ guess_where_config <- function( fs_path_abs(ret_path) ) } + + ret_path <- try_user_config_location(pth = golem::pkg_path()) + if (fs_file_exists(ret_path)) { + return(fs_path_abs(ret_path)) + } + return(NULL) } +try_user_config_location <- function(pth) { + # I. try to retrieve from possible user-change in app_config.R + user_location_default <- file.path(pth, "R/app_config.R") + if (isFALSE(fs_file_exists(user_location_default))) return(NULL) + + # II. if successfull, read file and find line where new config is located + tmp_guess_text <- readLines(user_location_default) + tmp_guess_line <- which(grepl("app_sys\\(", tmp_guess_text)) + if (identical(integer(0), tmp_guess_line)) return(NULL) + + # III. if successfull, identify the char that gives new config-path + tmp_config_expr <- regexpr("\\(.*\\)$", tmp_guess_text[tmp_guess_line]) + tmp_config_char <- regmatches( + tmp_guess_text[tmp_guess_line], + tmp_config_expr + ) + + # IV. clean that char from artefacts of regexpr() + out_config_char <- substr( + substring(tmp_config_char, 3), + start = 1, + stop = nchar(substring(tmp_config_char, 3)) - 2 + ) + + # V. return full path to new config file including pkg-path and 'inst' + return(file.path(pth, "inst", out_config_char)) +} + #' Get the path to the current config File #' #' This function tries to guess where the golem-config file is located. From 30687a13fa2cae79f0f257303c0dbd06aef0c9e4 Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Wed, 24 May 2023 14:21:27 +0200 Subject: [PATCH 190/190] fix: logical scalar operation should use && instead of & --- R/config.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/config.R b/R/config.R index 744336e1..c1b83cdb 100644 --- a/R/config.R +++ b/R/config.R @@ -48,7 +48,7 @@ guess_where_config <- function( }) if ( - !is_try_error(ret_path) & + !is_try_error(ret_path) && fs_file_exists(ret_path) ) { return(