From e3d125d6c0e3ea1dc79f7210130d134eb76e250e Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Fri, 4 Oct 2019 23:11:55 +0200 Subject: [PATCH 001/211] road to #175 and #189 --- R/add_deploy_helpers.R | 135 ++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 63 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 4c02095a..0e2132fe 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -120,6 +120,9 @@ add_shinyserver_file <- function( #' Default is 0.0.0.0. #' @export #' @rdname dockerfiles +#' @importFrom sysreqs sysreqs +#' @importFrom desc desc_get_deps +#' @importFrom dockerfiler Dockerfile #' @examples #' \donttest{ #' # Add a standard Dockerfile @@ -141,19 +144,27 @@ add_dockerfile <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/tidyverse:", + "rocker/r-ver:", R.Version()$major,".", R.Version()$minor ), as = NULL, port = 80, - host = "0.0.0.0" + host = "0.0.0.0", + sysreqs = TRUE, + repos = "https://cran.rstudio.com/" + # , function_to_launch = "run_app" ) { + where <- file.path(pkg, output) if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(input, FROM = from, AS = as) + + + + + dock <- dock_from_desc(input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(port) dock$CMD( glue::glue( @@ -176,14 +187,16 @@ add_dockerfile_shinyproxy <- function( R.Version()$major,".", R.Version()$minor ), - as = NULL + as = NULL, + sysreqs = TRUE, + repos = "https://cran.rstudio.com/" ){ where <- file.path(pkg, output) if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(input, FROM = from, AS = as) + dock <- dock_from_desc(input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -210,7 +223,9 @@ add_dockerfile_heroku <- function( R.Version()$major,".", R.Version()$minor ), - as = NULL + as = NULL, + sysreqs = TRUE, + repos = "https://cran.rstudio.com/" ){ where <- file.path(pkg, output) @@ -218,7 +233,7 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(input, FROM = from, AS = as) + dock <- dock_from_desc(input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$CMD( glue::glue( @@ -273,68 +288,62 @@ alert_build <- function(input, output){ #' @importFrom utils installed.packages dock_from_desc <- function( path = "DESCRIPTION", - FROM = "rocker/r-base", - AS = NULL + FROM = "rocker/r-ver", + AS = NULL, + sysreqs = TRUE, + repos = "https://cran.rstudio.com/" ){ - x <- dockerfiler::Dockerfile$new(FROM, AS) - x$RUN("R -e 'install.packages(\"remotes\")'") + if (sysreqs){ + system_requirement <- unique(sysreqs::sysreqs(desc = path,platform = "linux-x86_64-debian-gcc")) + }else{ + system_requirement <- NULL + } + packages <- desc::desc_get_deps(input)$package + packages <- packages[packages != "R"] # remove R + packages <- packages[ !packages %in% c("base", "boot", "class", "cluster", + "codetools", "compiler", "datasets", + "foreign", "graphics", "grDevices", + "grid", "KernSmooth", "lattice", "MASS", + "Matrix", "methods", "mgcv", "nlme", + "nnet", "parallel", "rpart", "spatial", + "splines", "stats", "stats4", "survival", + "tcltk", "tools", "utils")] # remove base and recommended + pkg <- setNames(lapply(packages, packageVersion), packages) + dock <- dockerfiler::Dockerfile$new(FROM = from) - # We need to be sure install_cran is there - x$RUN("R -e 'remotes::install_github(\"r-lib/remotes\", ref = \"97bbf81\")'") - - desc <- read.dcf(path) - - # Handle cases where there is no deps - imp <- attempt::attempt({ - desc[, "Imports"] - }, silent = TRUE) - - if (class(imp)[1] != "try-error"){ - # Remove base packages which are not on CRAN - # And shouldn't be installed - reco <- c("base", "compiler", "datasets", "graphics", "grDevices", "grid", - "methods", "parallel", "splines", "stats", "stats4", "tcltk", - "tools", "utils") #3.5 - - - - - # And Remotes package, which will be handled - # by install_local - rem <- attempt::attempt({ - desc[, "Remotes"] - }, silent = TRUE) - - if (class(rem)[1] != "try-error"){ - rem <- strsplit(rem, "\n")[[1]] - rem <- vapply(rem, function(x){ - strsplit(x, "/")[[1]][2] - }, character(1)) - reco <- c(reco, unname(rem)) - } - - if (length(imp) > 0) { - imp <- gsub(",", "", imp) - imp <- strsplit(imp, "\n")[[1]] - for (i in seq_along(imp)){ - gg <- gsub(" \\(.*", "", imp[i]) - if (!(gg %in% reco)){ - # Specific versions can cause pblm in Docker (which have a specific date) - # And will be handled by install_local so we don't add them - if (!grepl("\\(", imp[i]) ){ - x$RUN(paste0("R -e 'remotes::install_cran(\"", imp[i], "\")'")) - } - } - } - } + if (length(system_requirement)>0){ + dock$RUN(paste("apt-get update && apt-get install -y ",paste(system_requirement,collapse = " "))) } - x$COPY( - from = paste0(desc[1], "_*.tar.gz"), + dock$RUN( + sprintf("echo \"options(repos = c(CRAN = '%s'), download.file.method = 'libcurl')\" >> /usr/local/lib/R/etc/Rprofile.site",repos)) + dock$RUN("R -e 'install.packages(\"remotes\")'") + + # We need to be sure install_cran is there + dock$RUN("R -e 'remotes::install_github(\"r-lib/remotes\", ref = \"97bbf81\")'") + + + + ping <- mapply(function(dock, ver, nm){ + res <- dock$RUN( + sprintf( + "Rscript -e 'remotes::install_version(\"%s\", version = \"%s\")'", + nm, ver + ) + ) + }, ver = pkg, nm = names(pkg), MoreArgs = list(dock = dock)) + dock + + + + + + dock$COPY( + from = paste0(read.dcf(input)[1], "_*.tar.gz"), to = "/app.tar.gz" ) - x$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") + dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") - x + dock } \ No newline at end of file From cae3eb1251eb6ee25190741c7205876a35676698 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Fri, 4 Oct 2019 23:52:38 +0200 Subject: [PATCH 002/211] fix #175 --- R/add_deploy_helpers.R | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 0e2132fe..890f0327 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -164,7 +164,7 @@ add_dockerfile <- function( - dock <- dock_from_desc(input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(port) dock$CMD( glue::glue( @@ -196,7 +196,7 @@ add_dockerfile_shinyproxy <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -233,7 +233,7 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$CMD( glue::glue( @@ -295,8 +295,12 @@ dock_from_desc <- function( ){ if (sysreqs){ + # please wait during system requirement calculation + cat_bullet("Please wait during system requirements calculation...",bullet = "info",bullet_col = "green") # TODO animated version ? system_requirement <- unique(sysreqs::sysreqs(desc = path,platform = "linux-x86_64-debian-gcc")) - }else{ + cat_bullet("done",bullet = "tick",bullet_col = "green") # TODO animated version ? + + }else{ system_requirement <- NULL } packages <- desc::desc_get_deps(input)$package From d023f5a2469134d55e1fdab0095006f071bbee70 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Fri, 4 Oct 2019 23:57:33 +0200 Subject: [PATCH 003/211] path -> input everywhere --- R/add_deploy_helpers.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 890f0327..23db375e 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -164,7 +164,7 @@ add_dockerfile <- function( - dock <- dock_from_desc(path = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(input = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(port) dock$CMD( glue::glue( @@ -196,7 +196,7 @@ add_dockerfile_shinyproxy <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(input = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -233,7 +233,7 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(input = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$CMD( glue::glue( @@ -287,7 +287,7 @@ alert_build <- function(input, output){ # From {dockerfiler}, in wait for the version to be on CRAN #' @importFrom utils installed.packages dock_from_desc <- function( - path = "DESCRIPTION", + input = "DESCRIPTION", FROM = "rocker/r-ver", AS = NULL, sysreqs = TRUE, @@ -303,7 +303,7 @@ dock_from_desc <- function( }else{ system_requirement <- NULL } - packages <- desc::desc_get_deps(input)$package + packages <- desc::desc_get_deps(path)$package packages <- packages[packages != "R"] # remove R packages <- packages[ !packages %in% c("base", "boot", "class", "cluster", "codetools", "compiler", "datasets", @@ -344,7 +344,7 @@ dock_from_desc <- function( dock$COPY( - from = paste0(read.dcf(input)[1], "_*.tar.gz"), + from = paste0(read.dcf(path)[1], "_*.tar.gz"), to = "/app.tar.gz" ) dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") From eea9a3c1573cfc5cf802118b61a2cebdd40f068d Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 5 Oct 2019 00:05:06 +0200 Subject: [PATCH 004/211] input everywhere --- R/add_deploy_helpers.R | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 23db375e..46f222dd 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -109,7 +109,7 @@ add_shinyserver_file <- function( #' `add_dockerfile_heroku()` creates platform specific Dockerfile. #' #' @inheritParams add_module -#' @param input path to the DESCRIPTION file to use as an input. +#' @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/tidyverse: #' with `R.Version()$major` and `R.Version()$minor`. @@ -140,7 +140,7 @@ add_shinyserver_file <- function( #'} add_dockerfile <- function( - input = "DESCRIPTION", + path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0( @@ -164,22 +164,22 @@ add_dockerfile <- function( - dock <- dock_from_desc(input = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(port) dock$CMD( glue::glue( - "R -e \"options('shiny.port'={port},shiny.host='{host}');{read.dcf(input)[1]}::run_app()\"" + "R -e \"options('shiny.port'={port},shiny.host='{host}');{read.dcf(path)[1]}::run_app()\"" ) ) dock$write(output) - alert_build(input, output) + alert_build(path, output) } #' @export #' @rdname dockerfiles add_dockerfile_shinyproxy <- function( - input = "DESCRIPTION", + path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0( @@ -196,15 +196,15 @@ add_dockerfile_shinyproxy <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(input = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$EXPOSE(3838) dock$CMD(glue::glue( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0'); {read.dcf(input)[1]}::run_app()\"]" + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0'); {read.dcf(path)[1]}::run_app()\"]" )) dock$write(output) - alert_build(input, output) + alert_build(path, output) usethis::use_build_ignore(files = output) @@ -215,7 +215,7 @@ add_dockerfile_shinyproxy <- function( #' @export #' @rdname dockerfiles add_dockerfile_heroku <- function( - input = "DESCRIPTION", + path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0( @@ -233,20 +233,20 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(input = input, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) dock$CMD( glue::glue( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');{read.dcf(input)[1]}::run_app()\"" + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');{read.dcf(path)[1]}::run_app()\"" ) ) dock$write(output) - alert_build(input, output) + alert_build(path, output) apps_h <- gsub( "\\.", "-", - glue("{read.dcf(input)[1]}-{read.dcf('DESCRIPTION')[1,][['Version']]}") + glue("{read.dcf(path)[1]}-{read.dcf(path)[1,][['Version']]}") ) cat_rule( "From your command line, run:" ) @@ -273,13 +273,13 @@ add_dockerfile_heroku <- function( } -alert_build <- function(input, output){ +alert_build <- function(path, output){ cat_green_tick( glue("Dockerfile created at {output}") ) cat_red_bullet( glue::glue( - "Be sure to put your {read.dcf(input)[1]}_{read.dcf(input)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" + "Be sure to put your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" ) ) } @@ -287,7 +287,7 @@ alert_build <- function(input, output){ # From {dockerfiler}, in wait for the version to be on CRAN #' @importFrom utils installed.packages dock_from_desc <- function( - input = "DESCRIPTION", + path = "DESCRIPTION", FROM = "rocker/r-ver", AS = NULL, sysreqs = TRUE, From 1fd1fef8e2351940e995ad93a41361ff3401fc39 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 5 Oct 2019 00:07:32 +0200 Subject: [PATCH 005/211] fix #175 --- DESCRIPTION | 4 +- NAMESPACE | 160 +++++++++++++++++++------------------ R/add_deploy_helpers.R | 7 +- golem.Rproj | 36 ++++----- man/add_files.Rd | 70 ++++++++-------- man/add_module.Rd | 58 +++++++------- man/browser_button.Rd | 28 +++---- man/create_golem.Rd | 50 ++++++------ man/detach_all_attached.Rd | 22 ++--- man/dockerfiles.Rd | 127 +++++++++++++++-------------- man/document_and_reload.Rd | 30 +++---- man/favicon.Rd | 68 ++++++++-------- man/file_creation.Rd | 56 ++++++------- man/fill_desc.Rd | 60 +++++++------- man/get_dependencies.Rd | 56 ++++++------- man/get_golem_options.Rd | 32 ++++---- man/golem.Rd | 68 ++++++++-------- man/golem_js.Rd | 76 +++++++++--------- man/golem_wd.Rd | 58 +++++++------- man/made_dev.Rd | 56 ++++++------- man/make_dev.Rd | 30 +++---- man/prod.Rd | 34 ++++---- man/rstudio_deploy.Rd | 84 +++++++++---------- man/set_golem_options.Rd | 44 +++++----- man/testhelpers.Rd | 64 +++++++-------- man/use_recommended.Rd | 46 +++++------ man/utils_files.Rd | 40 +++++----- man/with_golem_options.Rd | 48 +++++------ 28 files changed, 763 insertions(+), 749 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 5b13e861..5653614a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -56,10 +56,10 @@ Imports: tools, usethis, utils, - yesno + yesno, + sysreqs Suggests: spelling, - yesno, covr, knitr, pkgdown, diff --git a/NAMESPACE b/NAMESPACE index f1a3eeb9..46031dcc 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,78 +1,82 @@ -# Generated by roxygen2: do not edit by hand - -export(activate_js) -export(add_css_file) -export(add_dockerfile) -export(add_dockerfile_heroku) -export(add_dockerfile_shinyproxy) -export(add_fct) -export(add_js_file) -export(add_js_handler) -export(add_module) -export(add_rstudioconnect_file) -export(add_shinyappsio_file) -export(add_shinyserver_file) -export(add_ui_server_files) -export(add_utils) -export(app_dev) -export(app_prod) -export(browser_button) -export(browser_dev) -export(cat_dev) -export(create_golem) -export(detach_all_attached) -export(document_and_reload) -export(expect_html_equal) -export(expect_shinytag) -export(expect_shinytaglist) -export(favicon) -export(fill_desc) -export(get_dependencies) -export(get_golem_options) -export(get_golem_wd) -export(invoke_js) -export(make_dev) -export(message_dev) -export(print_dev) -export(remove_favicon) -export(set_golem_options) -export(set_golem_wd) -export(use_favicon) -export(use_recommended_deps) -export(use_recommended_tests) -export(use_utils_server) -export(use_utils_ui) -export(warning_dev) -export(with_golem_options) -importFrom(attempt,attempt) -importFrom(attempt,stop_if_not) -importFrom(attempt,without_warning) -importFrom(cli,cat_bullet) -importFrom(cli,cat_line) -importFrom(cli,cat_rule) -importFrom(desc,description) -importFrom(glue,glue) -importFrom(htmltools,includeScript) -importFrom(htmltools,save_html) -importFrom(htmltools,tags) -importFrom(pkgload,load_all) -importFrom(pkgload,pkg_name) -importFrom(rlang,enquo) -importFrom(roxygen2,roxygenise) -importFrom(rstudioapi,isAvailable) -importFrom(rstudioapi,openProject) -importFrom(shiny,getShinyOption) -importFrom(stats,setNames) -importFrom(stringr,str_remove_all) -importFrom(testthat,expect) -importFrom(testthat,expect_equal) -importFrom(testthat,quasi_label) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_package) -importFrom(usethis,use_testthat) -importFrom(utils,capture.output) -importFrom(utils,file.edit) -importFrom(utils,getFromNamespace) -importFrom(utils,installed.packages) -importFrom(utils,sessionInfo) -importFrom(yesno,yesno) +# Generated by roxygen2: do not edit by hand + +export(activate_js) +export(add_css_file) +export(add_dockerfile) +export(add_dockerfile_heroku) +export(add_dockerfile_shinyproxy) +export(add_fct) +export(add_js_file) +export(add_js_handler) +export(add_module) +export(add_rstudioconnect_file) +export(add_shinyappsio_file) +export(add_shinyserver_file) +export(add_ui_server_files) +export(add_utils) +export(app_dev) +export(app_prod) +export(browser_button) +export(browser_dev) +export(cat_dev) +export(create_golem) +export(detach_all_attached) +export(document_and_reload) +export(expect_html_equal) +export(expect_shinytag) +export(expect_shinytaglist) +export(favicon) +export(fill_desc) +export(get_dependencies) +export(get_golem_options) +export(get_golem_wd) +export(invoke_js) +export(make_dev) +export(message_dev) +export(print_dev) +export(remove_favicon) +export(set_golem_options) +export(set_golem_wd) +export(use_favicon) +export(use_recommended_deps) +export(use_recommended_tests) +export(use_utils_server) +export(use_utils_ui) +export(warning_dev) +export(with_golem_options) +importFrom(attempt,attempt) +importFrom(attempt,stop_if_not) +importFrom(attempt,without_warning) +importFrom(cli,cat_bullet) +importFrom(cli,cat_line) +importFrom(cli,cat_rule) +importFrom(desc,desc_get_deps) +importFrom(desc,description) +importFrom(dockerfiler,Dockerfile) +importFrom(glue,glue) +importFrom(htmltools,includeScript) +importFrom(htmltools,save_html) +importFrom(htmltools,tags) +importFrom(pkgload,load_all) +importFrom(pkgload,pkg_name) +importFrom(rlang,enquo) +importFrom(roxygen2,roxygenise) +importFrom(rstudioapi,isAvailable) +importFrom(rstudioapi,openProject) +importFrom(shiny,getShinyOption) +importFrom(stats,setNames) +importFrom(stringr,str_remove_all) +importFrom(sysreqs,sysreqs) +importFrom(testthat,expect) +importFrom(testthat,expect_equal) +importFrom(testthat,quasi_label) +importFrom(usethis,use_build_ignore) +importFrom(usethis,use_package) +importFrom(usethis,use_testthat) +importFrom(utils,capture.output) +importFrom(utils,file.edit) +importFrom(utils,getFromNamespace) +importFrom(utils,installed.packages) +importFrom(utils,packageVersion) +importFrom(utils,sessionInfo) +importFrom(yesno,yesno) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 46f222dd..4db04523 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -118,6 +118,8 @@ add_shinyserver_file <- function( #' Default is 80. #' @param host The `options('shiny.host')` on which to run the Shiny App. #' Default is 0.0.0.0. +#' @param sysreqs boolean to check the System Requirements +#' @param repos character vector, the base URL of the repositories #' @export #' @rdname dockerfiles #' @importFrom sysreqs sysreqs @@ -285,7 +287,8 @@ alert_build <- function(path, output){ } # From {dockerfiler}, in wait for the version to be on CRAN -#' @importFrom utils installed.packages +#' @importFrom utils installed.packages packageVersion +#' dock_from_desc <- function( path = "DESCRIPTION", FROM = "rocker/r-ver", @@ -314,7 +317,7 @@ dock_from_desc <- function( "splines", "stats", "stats4", "survival", "tcltk", "tools", "utils")] # remove base and recommended pkg <- setNames(lapply(packages, packageVersion), packages) - dock <- dockerfiler::Dockerfile$new(FROM = from) + dock <- dockerfiler::Dockerfile$new(FROM = FROM) if (length(system_requirement)>0){ dock$RUN(paste("apt-get update && apt-get install -y ",paste(system_requirement,collapse = " "))) diff --git a/golem.Rproj b/golem.Rproj index 1788e686..8cc08ce6 100644 --- a/golem.Rproj +++ b/golem.Rproj @@ -1,18 +1,18 @@ -Version: 1.0 - -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -BuildType: Package -PackageUseDevtools: Yes -PackageInstallArgs: --no-multiarch --with-keep.source -PackageRoxygenize: rd,collate,namespace,vignette +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace,vignette diff --git a/man/add_files.Rd b/man/add_files.Rd index b6099368..5e4beffb 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -1,35 +1,35 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_files.R -\name{add_js_file} -\alias{add_js_file} -\alias{add_js_handler} -\alias{add_css_file} -\alias{add_ui_server_files} -\title{Create Files} -\usage{ -add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) - -add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) - -add_css_file(name, 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{ -\item{name}{The name of the module} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dir}{Path to the dir where the file while be created.} - -\item{open}{Should the file be opened?} - -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} -} -\description{ -These functions create files inside the \code{inst/app} folder. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_files.R +\name{add_js_file} +\alias{add_js_file} +\alias{add_js_handler} +\alias{add_css_file} +\alias{add_ui_server_files} +\title{Create Files} +\usage{ +add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE) + +add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE) + +add_css_file(name, 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{ +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dir}{Path to the dir where the file while be created.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} +} +\description{ +These functions create files inside the \code{inst/app} folder. +} diff --git a/man/add_module.Rd b/man/add_module.Rd index 63d8434c..005ee62e 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -1,29 +1,29 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_modules.R -\name{add_module} -\alias{add_module} -\title{Create a module} -\usage{ -add_module(name, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE, fct = NULL, utils = NULL) -} -\arguments{ -\item{name}{The name of the module} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{open}{Should the file be opened?} - -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} - -\item{fct}{The name of the fct file.} - -\item{utils}{The name of the utils file.} -} -\description{ -This function creates a module inside the \code{R/} folder, based -on a specific module structure. -} -\note{ -This function will prefix the \code{name} argument with \code{mod_}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_modules.R +\name{add_module} +\alias{add_module} +\title{Create a module} +\usage{ +add_module(name, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE, fct = NULL, utils = NULL) +} +\arguments{ +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} + +\item{fct}{The name of the fct file.} + +\item{utils}{The name of the utils file.} +} +\description{ +This function creates a module inside the \code{R/} folder, based +on a specific module structure. +} +\note{ +This function will prefix the \code{name} argument with \code{mod_}. +} diff --git a/man/browser_button.Rd b/man/browser_button.Rd index 91498f39..7c858744 100644 --- a/man/browser_button.Rd +++ b/man/browser_button.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/browser_button.R -\name{browser_button} -\alias{browser_button} -\title{Insert an hidden browser button} -\usage{ -browser_button() -} -\value{ -Prints the code to the console. -} -\description{ -See \url{https://rtask.thinkr.fr/blog/a-little-trick-for-debugging-shiny/} for more context. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/browser_button.R +\name{browser_button} +\alias{browser_button} +\title{Insert an hidden browser button} +\usage{ +browser_button() +} +\value{ +Prints the code to the console. +} +\description{ +See \url{https://rtask.thinkr.fr/blog/a-little-trick-for-debugging-shiny/} for more context. +} diff --git a/man/create_golem.Rd b/man/create_golem.Rd index 7d204a51..6fa90e16 100644 --- a/man/create_golem.Rd +++ b/man/create_golem.Rd @@ -1,25 +1,25 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/create_golem.R -\name{create_golem} -\alias{create_golem} -\title{Create a package for Shiny App using golem} -\usage{ -create_golem(path, check_name = TRUE, open = TRUE, - package_name = basename(path), ...) -} -\arguments{ -\item{path}{Name of the folder to create the package in. This will also be -used as the package name.} - -\item{check_name}{When using this function in the console, you can prevent -the package name from being checked.} - -\item{open}{boolean open the created project} - -\item{package_name}{package name to use} - -\item{...}{not used} -} -\description{ -Create a package for Shiny App using golem -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/create_golem.R +\name{create_golem} +\alias{create_golem} +\title{Create a package for Shiny App using golem} +\usage{ +create_golem(path, check_name = TRUE, open = TRUE, + package_name = basename(path), ...) +} +\arguments{ +\item{path}{Name of the folder to create the package in. This will also be +used as the package name.} + +\item{check_name}{When using this function in the console, you can prevent +the package name from being checked.} + +\item{open}{boolean open the created project} + +\item{package_name}{package name to use} + +\item{...}{not used} +} +\description{ +Create a package for Shiny App using golem +} diff --git a/man/detach_all_attached.Rd b/man/detach_all_attached.Rd index 3e12756f..9e9ce540 100644 --- a/man/detach_all_attached.Rd +++ b/man/detach_all_attached.Rd @@ -1,11 +1,11 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reload.R -\name{detach_all_attached} -\alias{detach_all_attached} -\title{Detach all attached package} -\usage{ -detach_all_attached() -} -\description{ -Detach all attached package -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reload.R +\name{detach_all_attached} +\alias{detach_all_attached} +\title{Detach all attached package} +\usage{ +detach_all_attached() +} +\description{ +Detach all attached package +} diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 3cb7c0e6..e2a4e99c 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,60 +1,67 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{add_dockerfile} -\alias{add_dockerfile} -\alias{add_dockerfile_shinyproxy} -\alias{add_dockerfile_heroku} -\title{Create a Dockerfile for Shiny App} -\usage{ -add_dockerfile(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, - host = "0.0.0.0") - -add_dockerfile_shinyproxy(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) - -add_dockerfile_heroku(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) -} -\arguments{ -\item{input}{path to the DESCRIPTION file to use as an input.} - -\item{output}{name of the Dockerfile output.} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: -with \code{R.Version()$major} and \code{R.Version()$minor}.} - -\item{as}{The AS of the Dockerfile. Default it NULL.} - -\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. -Default is 80.} - -\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. -Default is 0.0.0.0.} -} -\description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and -\code{add_dockerfile_heroku()} creates platform specific Dockerfile. -} -\examples{ -\donttest{ -# Add a standard Dockerfile -if (interactive()){ - add_dockerfile() -} -# Add a Dockerfile for ShinyProxy -if (interactive()){ - add_dockerfile_shinyproxy() -} -# Add a Dockerfile for Heroku -if (interactive()){ - add_dockerfile_heroku() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{add_dockerfile} +\alias{add_dockerfile} +\alias{add_dockerfile_shinyproxy} +\alias{add_dockerfile_heroku} +\title{Create a Dockerfile for Shiny App} +\usage{ +add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, + host = "0.0.0.0", sysreqs = TRUE, + repos = "https://cran.rstudio.com/") + +add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL, + sysreqs = TRUE, repos = "https://cran.rstudio.com/") + +add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL, + sysreqs = TRUE, repos = "https://cran.rstudio.com/") +} +\arguments{ +\item{path}{path to the DESCRIPTION file to use as an input.} + +\item{output}{name of the Dockerfile output.} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: +with \code{R.Version()$major} and \code{R.Version()$minor}.} + +\item{as}{The AS of the Dockerfile. Default it NULL.} + +\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. +Default is 80.} + +\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. +Default is 0.0.0.0.} + +\item{sysreqs}{boolean to check the System Requirements} + +\item{repos}{character vector, the base URL of the repositories} +} +\description{ +Build a container containing your Shiny App. \code{add_dockerfile()} creates +a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and +\code{add_dockerfile_heroku()} creates platform specific Dockerfile. +} +\examples{ +\donttest{ +# Add a standard Dockerfile +if (interactive()){ + add_dockerfile() +} +# Add a Dockerfile for ShinyProxy +if (interactive()){ + add_dockerfile_shinyproxy() +} +# 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 198041f0..d1c98f22 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -1,15 +1,15 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reload.R -\name{document_and_reload} -\alias{document_and_reload} -\title{Document and reload your package} -\usage{ -document_and_reload(pkg = ".") -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} -} -\description{ -This function calls \code{rstudioapi::documentSaveAll()}, -\code{roxygen2::roxygenise()} and \code{pkgload::load_all()}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reload.R +\name{document_and_reload} +\alias{document_and_reload} +\title{Document and reload your package} +\usage{ +document_and_reload(pkg = ".") +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} +} +\description{ +This function calls \code{rstudioapi::documentSaveAll()}, +\code{roxygen2::roxygenise()} and \code{pkgload::load_all()}. +} diff --git a/man/favicon.Rd b/man/favicon.Rd index e330df84..37eb31a3 100644 --- a/man/favicon.Rd +++ b/man/favicon.Rd @@ -1,34 +1,34 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_favicon.R -\name{use_favicon} -\alias{use_favicon} -\alias{remove_favicon} -\alias{favicon} -\title{Add a favicon to your shinyapp} -\usage{ -use_favicon(path, pkg = ".") - -remove_favicon(path = "inst/app/www/favicon.ico") - -favicon(ico = "www/favicon.ico", rel = "shortcut icon") -} -\arguments{ -\item{path}{Path to your favicon file (.ico or .png)} - -\item{pkg}{Path to the root of the package. Default is \code{"."}} - -\item{ico}{path to favicon file} - -\item{rel}{rel} -} -\description{ -This function adds the favicon from \code{ico} to your shiny app. -} -\examples{ -\donttest{ -if (interactive()){ - use_favicon() - use_favicon(path='path/to/your/favicon.ico') -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_favicon.R +\name{use_favicon} +\alias{use_favicon} +\alias{remove_favicon} +\alias{favicon} +\title{Add a favicon to your shinyapp} +\usage{ +use_favicon(path, pkg = ".") + +remove_favicon(path = "inst/app/www/favicon.ico") + +favicon(ico = "www/favicon.ico", rel = "shortcut icon") +} +\arguments{ +\item{path}{Path to your favicon file (.ico or .png)} + +\item{pkg}{Path to the root of the package. Default is \code{"."}} + +\item{ico}{path to favicon file} + +\item{rel}{rel} +} +\description{ +This function adds the favicon from \code{ico} to your shiny app. +} +\examples{ +\donttest{ +if (interactive()){ + use_favicon() + use_favicon(path='path/to/your/favicon.ico') +} +} +} diff --git a/man/file_creation.Rd b/man/file_creation.Rd index dea0be83..d0835101 100644 --- a/man/file_creation.Rd +++ b/man/file_creation.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_r_files.R -\name{add_fct} -\alias{add_fct} -\alias{add_utils} -\title{Add fct_ and utils_ files} -\usage{ -add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) - -add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) -} -\arguments{ -\item{name}{The name of the file} - -\item{module}{If not NULL, the file will be module specific in the naming (you don't need to add the leading \code{mod_})} - -\item{pkg}{The working directory} - -\item{open}{Should the file be opened once created?} - -\item{dir_create}{Should the folder be created if it doesn't exist?} -} -\description{ -These function adds files in the R/ folder -that starts either with fct_ or with utils_ -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_r_files.R +\name{add_fct} +\alias{add_fct} +\alias{add_utils} +\title{Add fct_ and utils_ files} +\usage{ +add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) + +add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) +} +\arguments{ +\item{name}{The name of the file} + +\item{module}{If not NULL, the file will be module specific in the naming (you don't need to add the leading \code{mod_})} + +\item{pkg}{The working directory} + +\item{open}{Should the file be opened once created?} + +\item{dir_create}{Should the folder be created if it doesn't exist?} +} +\description{ +These function adds files in the R/ folder +that starts either with fct_ or with utils_ +} diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index 8a6e3407..23cb45f5 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -1,30 +1,30 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/desc.R -\name{fill_desc} -\alias{fill_desc} -\title{Fill your description} -\usage{ -fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, - author_last_name, author_email, repo_url = NULL, - pkg = get_golem_wd()) -} -\arguments{ -\item{pkg_name}{The name of the package} - -\item{pkg_title}{The title of the package} - -\item{pkg_description}{Description of the package} - -\item{author_first_name}{First Name of the author} - -\item{author_last_name}{Last Name of the author} - -\item{author_email}{Email of the author} - -\item{repo_url}{URL (if needed)} - -\item{pkg}{Path to look for the DESCRIPTION} -} -\description{ -Fill your description -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/desc.R +\name{fill_desc} +\alias{fill_desc} +\title{Fill your description} +\usage{ +fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, + author_last_name, author_email, repo_url = NULL, + pkg = get_golem_wd()) +} +\arguments{ +\item{pkg_name}{The name of the package} + +\item{pkg_title}{The title of the package} + +\item{pkg_description}{Description of the package} + +\item{author_first_name}{First Name of the author} + +\item{author_last_name}{Last Name of the author} + +\item{author_email}{Email of the author} + +\item{repo_url}{URL (if needed)} + +\item{pkg}{Path to look for the DESCRIPTION} +} +\description{ +Fill your description +} diff --git a/man/get_dependencies.Rd b/man/get_dependencies.Rd index bbd90470..82c933ff 100644 --- a/man/get_dependencies.Rd +++ b/man/get_dependencies.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_dependencies.R -\name{get_dependencies} -\alias{get_dependencies} -\title{Return all package dependencies from current package} -\usage{ -get_dependencies(path = "DESCRIPTION", pkg = get_golem_wd(), - dput = FALSE, field = c("Imports")) -} -\arguments{ -\item{path}{path to the DESCRIPTION file} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dput}{if TRUE return a dput output instead of character vector} - -\item{field}{DESCRIPTION fields to parse. Default is Import} -} -\description{ -Return all package dependencies from current package -} -\examples{ -\donttest{ -if (interactive()){ - get_dependencies() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_dependencies.R +\name{get_dependencies} +\alias{get_dependencies} +\title{Return all package dependencies from current package} +\usage{ +get_dependencies(path = "DESCRIPTION", pkg = get_golem_wd(), + dput = FALSE, field = c("Imports")) +} +\arguments{ +\item{path}{path to the DESCRIPTION file} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dput}{if TRUE return a dput output instead of character vector} + +\item{field}{DESCRIPTION fields to parse. Default is Import} +} +\description{ +Return all package dependencies from current package +} +\examples{ +\donttest{ +if (interactive()){ + get_dependencies() +} +} +} diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index d580f9c9..1676a32c 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -1,16 +1,16 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/with_opt.R -\name{get_golem_options} -\alias{get_golem_options} -\title{Get all or one golem options} -\usage{ -get_golem_options(which = NULL) -} -\arguments{ -\item{which}{NULL (default), or the name of an option} -} -\description{ -This function is to be used inside the -server and UI from your app, in order to call the -parameters passed to \code{run_app()}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{get_golem_options} +\alias{get_golem_options} +\title{Get all or one golem options} +\usage{ +get_golem_options(which = NULL) +} +\arguments{ +\item{which}{NULL (default), or the name of an option} +} +\description{ +This function is to be used inside the +server and UI from your app, in order to call the +parameters passed to \code{run_app()}. +} diff --git a/man/golem.Rd b/man/golem.Rd index 4c82de3e..46274e47 100644 --- a/man/golem.Rd +++ b/man/golem.Rd @@ -1,34 +1,34 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/golem-package.R -\docType{package} -\name{golem} -\alias{golem} -\alias{golem-package} -\title{A package for building Shiny App} -\description{ -Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. -} -\seealso{ -Useful links: -\itemize{ - \item \url{https://github.com/ThinkR-open/golem} - \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} -} - -} -\author{ -\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) - -Authors: -\itemize{ - \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) - \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) - \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) -} - -Other contributors: -\itemize{ - \item ThinkR [copyright holder] -} - -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/golem-package.R +\docType{package} +\name{golem} +\alias{golem} +\alias{golem-package} +\title{A package for building Shiny App} +\description{ +Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/ThinkR-open/golem} + \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} +} + +} +\author{ +\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) + +Authors: +\itemize{ + \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) + \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) + \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) +} + +Other contributors: +\itemize{ + \item ThinkR [copyright holder] +} + +} diff --git a/man/golem_js.Rd b/man/golem_js.Rd index 85a0f3c2..7ef56ede 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -1,38 +1,38 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/js.R -\name{activate_js} -\alias{activate_js} -\alias{invoke_js} -\title{Interact with JavaScript built-in Functions} -\usage{ -activate_js() - -invoke_js(fun, ui_ref, session = shiny::getDefaultReactiveDomain()) -} -\arguments{ -\item{fun}{JS function to be invoked.} - -\item{ui_ref}{The UI reference to call the JS function on.} - -\item{session}{The shiny session within which to call \code{sendCustomMessage}. - -\describe{ -\item{show}{Show an element with the jQuery selector provided.} -\item{hide}{Hide an element with the jQuery selector provided.} -\item{showid}{Show an element with the id provided.} -\item{hideid}{Hide an element with the id provided.} -\item{showclass}{Same as showid, but with class.} -\item{hideclass}{Same as hideid, but with class.} -\item{showhref}{Same as showid, but with \code{a[href*=}} -\item{hidehref}{Same as hideid, but with \code{a[href*=}} -\item{clickon}{Click on an element. The full jQuery selector has to be used.} -\item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} -\item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} -}} -} -\description{ -\code{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 \code{invoke_js}. \code{invoke_js} can also be used -to launch any JS function created inside a Shiny JavaScript handler. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/js.R +\name{activate_js} +\alias{activate_js} +\alias{invoke_js} +\title{Interact with JavaScript built-in Functions} +\usage{ +activate_js() + +invoke_js(fun, ui_ref, session = shiny::getDefaultReactiveDomain()) +} +\arguments{ +\item{fun}{JS function to be invoked.} + +\item{ui_ref}{The UI reference to call the JS function on.} + +\item{session}{The shiny session within which to call \code{sendCustomMessage}. + +\describe{ +\item{show}{Show an element with the jQuery selector provided.} +\item{hide}{Hide an element with the jQuery selector provided.} +\item{showid}{Show an element with the id provided.} +\item{hideid}{Hide an element with the id provided.} +\item{showclass}{Same as showid, but with class.} +\item{hideclass}{Same as hideid, but with class.} +\item{showhref}{Same as showid, but with \code{a[href*=}} +\item{hidehref}{Same as hideid, but with \code{a[href*=}} +\item{clickon}{Click on an element. The full jQuery selector has to be used.} +\item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} +\item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} +}} +} +\description{ +\code{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 \code{invoke_js}. \code{invoke_js} can also be used +to launch any JS function created inside a Shiny JavaScript handler. +} diff --git a/man/golem_wd.Rd b/man/golem_wd.Rd index e4bd279d..92655d8f 100644 --- a/man/golem_wd.Rd +++ b/man/golem_wd.Rd @@ -1,29 +1,29 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{get_golem_wd} -\alias{get_golem_wd} -\alias{set_golem_wd} -\title{Get and set \code{{golem}} working directory} -\usage{ -get_golem_wd() - -set_golem_wd(path, talkative = TRUE) -} -\arguments{ -\item{path}{The path to set the golem working directory. -Note that it will be passed to \code{normalizePath}.} - -\item{talkative}{Should the function print where the -new path is defined?} -} -\value{ -The path to the working directory. -} -\description{ -Many \code{{golem}} functions rely on a specific working directory, -most of the time the root of the package. This working directory -is set by \code{set_golem_options} or the first time you create a file. -It default to \code{"."}, the current directory when starting a golem. -You can use these two functions if you need to manipulate this -directory. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{get_golem_wd} +\alias{get_golem_wd} +\alias{set_golem_wd} +\title{Get and set \code{{golem}} working directory} +\usage{ +get_golem_wd() + +set_golem_wd(path, talkative = TRUE) +} +\arguments{ +\item{path}{The path to set the golem working directory. +Note that it will be passed to \code{normalizePath}.} + +\item{talkative}{Should the function print where the +new path is defined?} +} +\value{ +The path to the working directory. +} +\description{ +Many \code{{golem}} functions rely on a specific working directory, +most of the time the root of the package. This working directory +is set by \code{set_golem_options} or the first time you create a file. +It default to \code{"."}, the current directory when starting a golem. +You can use these two functions if you need to manipulate this +directory. +} diff --git a/man/made_dev.Rd b/man/made_dev.Rd index 43a56371..b04e97ed 100644 --- a/man/made_dev.Rd +++ b/man/made_dev.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{cat_dev} -\alias{cat_dev} -\alias{print_dev} -\alias{message_dev} -\alias{warning_dev} -\alias{browser_dev} -\title{Functions already made dev dependent} -\usage{ -cat_dev(...) - -print_dev(...) - -message_dev(...) - -warning_dev(...) - -browser_dev(...) -} -\arguments{ -\item{...}{\R objects (see \sQuote{Details} for the types of objects - allowed).} -} -\description{ -This functions will be run only if \code{golem::app_dev()} -returns TRUE. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{cat_dev} +\alias{cat_dev} +\alias{print_dev} +\alias{message_dev} +\alias{warning_dev} +\alias{browser_dev} +\title{Functions already made dev dependent} +\usage{ +cat_dev(...) + +print_dev(...) + +message_dev(...) + +warning_dev(...) + +browser_dev(...) +} +\arguments{ +\item{...}{\R objects (see \sQuote{Details} for the types of objects + allowed).} +} +\description{ +This functions will be run only if \code{golem::app_dev()} +returns TRUE. +} diff --git a/man/make_dev.Rd b/man/make_dev.Rd index 487ca1c9..c9bcfd5f 100644 --- a/man/make_dev.Rd +++ b/man/make_dev.Rd @@ -1,15 +1,15 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{make_dev} -\alias{make_dev} -\title{Make a function dependent to dev mode} -\usage{ -make_dev(fun) -} -\arguments{ -\item{fun}{A function} -} -\description{ -The function returned will be run only if \code{golem::app_dev()} -returns TRUE. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{make_dev} +\alias{make_dev} +\title{Make a function dependent to dev mode} +\usage{ +make_dev(fun) +} +\arguments{ +\item{fun}{A function} +} +\description{ +The function returned will be run only if \code{golem::app_dev()} +returns TRUE. +} diff --git a/man/prod.Rd b/man/prod.Rd index 9255c15b..428ecb0b 100644 --- a/man/prod.Rd +++ b/man/prod.Rd @@ -1,17 +1,17 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{app_prod} -\alias{app_prod} -\alias{app_dev} -\title{Is the app in dev mode or prod mode?} -\usage{ -app_prod() - -app_dev() -} -\value{ -\code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} -} -\description{ -Is the app in dev mode or prod mode? -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{app_prod} +\alias{app_prod} +\alias{app_dev} +\title{Is the app in dev mode or prod mode?} +\usage{ +app_prod() + +app_dev() +} +\value{ +\code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} +} +\description{ +Is the app in dev mode or prod mode? +} diff --git a/man/rstudio_deploy.Rd b/man/rstudio_deploy.Rd index e848f0d4..e0c2189c 100644 --- a/man/rstudio_deploy.Rd +++ b/man/rstudio_deploy.Rd @@ -1,42 +1,42 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{add_rstudioconnect_file} -\alias{add_rstudioconnect_file} -\alias{add_rconnect_file} -\alias{add_shinyappsio_file} -\alias{add_shinyserver_file} -\title{Add an app.R at the root of your package to deploy on RStudio Connect} -\usage{ -add_rstudioconnect_file(pkg = get_golem_wd(), open = TRUE) - -add_shinyappsio_file(pkg = get_golem_wd(), open = TRUE) - -add_shinyserver_file(pkg = get_golem_wd(), open = TRUE) -} -\arguments{ -\item{pkg}{Where to put the app.R.} - -\item{open}{Open the file} -} -\description{ -Add an app.R at the root of your package to deploy on RStudio Connect -} -\note{ -In previous versions, this function was called add_rconnect_file. -} -\examples{ -\donttest{ -# Add a file for Connect -if (interactive()){ - add_rstudioconnect_file() -} -# Add a file for Shiny Server -if (interactive()){ - add_shinyserver_file() -} -# Add a file for Shinyapps.io -if (interactive()){ - add_shinyappsio_file() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{add_rstudioconnect_file} +\alias{add_rstudioconnect_file} +\alias{add_rconnect_file} +\alias{add_shinyappsio_file} +\alias{add_shinyserver_file} +\title{Add an app.R at the root of your package to deploy on RStudio Connect} +\usage{ +add_rstudioconnect_file(pkg = get_golem_wd(), open = TRUE) + +add_shinyappsio_file(pkg = get_golem_wd(), open = TRUE) + +add_shinyserver_file(pkg = get_golem_wd(), open = TRUE) +} +\arguments{ +\item{pkg}{Where to put the app.R.} + +\item{open}{Open the file} +} +\description{ +Add an app.R at the root of your package to deploy on RStudio Connect +} +\note{ +In previous versions, this function was called add_rconnect_file. +} +\examples{ +\donttest{ +# Add a file for Connect +if (interactive()){ + add_rstudioconnect_file() +} +# Add a file for Shiny Server +if (interactive()){ + add_shinyserver_file() +} +# Add a file for Shinyapps.io +if (interactive()){ + add_shinyappsio_file() +} +} +} diff --git a/man/set_golem_options.Rd b/man/set_golem_options.Rd index 5942ae8f..9a7e37fb 100644 --- a/man/set_golem_options.Rd +++ b/man/set_golem_options.Rd @@ -1,22 +1,22 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\title{{golem} options} -\usage{ -set_golem_options(golem_name = pkgload::pkg_name(), - golem_version = pkgload::pkg_version(), - golem_wd = pkgload::pkg_path(), app_prod = FALSE) -} -\arguments{ -\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?} -} -\description{ -Set a series of options to be used internally by \code{{golem}}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{set_golem_options} +\alias{set_golem_options} +\title{{golem} options} +\usage{ +set_golem_options(golem_name = pkgload::pkg_name(), + golem_version = pkgload::pkg_version(), + golem_wd = pkgload::pkg_path(), app_prod = FALSE) +} +\arguments{ +\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?} +} +\description{ +Set a series of options to be used internally by \code{{golem}}. +} diff --git a/man/testhelpers.Rd b/man/testhelpers.Rd index 2ca76003..eaee8929 100644 --- a/man/testhelpers.Rd +++ b/man/testhelpers.Rd @@ -1,32 +1,32 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/test_helpers.R -\name{expect_shinytag} -\alias{expect_shinytag} -\alias{expect_shinytaglist} -\alias{expect_html_equal} -\title{Test helpers} -\usage{ -expect_shinytag(object) - -expect_shinytaglist(object) - -expect_html_equal(ui, html) -} -\arguments{ -\item{object}{the object to test} - -\item{ui}{output of an UI function} - -\item{html}{html file to compare to ui} -} -\value{ -A testthat result -} -\description{ -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)) -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/test_helpers.R +\name{expect_shinytag} +\alias{expect_shinytag} +\alias{expect_shinytaglist} +\alias{expect_html_equal} +\title{Test helpers} +\usage{ +expect_shinytag(object) + +expect_shinytaglist(object) + +expect_html_equal(ui, html) +} +\arguments{ +\item{object}{the object to test} + +\item{ui}{output of an UI function} + +\item{html}{html file to compare to ui} +} +\value{ +A testthat result +} +\description{ +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/man/use_recommended.Rd b/man/use_recommended.Rd index d9faf102..c5f9fdd9 100644 --- a/man/use_recommended.Rd +++ b/man/use_recommended.Rd @@ -1,23 +1,23 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_recommended.R -\name{use_recommended_deps} -\alias{use_recommended_deps} -\alias{use_recommended_tests} -\title{Add recommended elements} -\usage{ -use_recommended_deps(pkg = ".", recommended = c("shiny", "DT", - "attempt", "glue", "htmltools", "golem")) - -use_recommended_tests(pkg = ".") -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{recommended}{A vector of recommended packages.} -} -\description{ -\describe{ -\item{use_recommended_deps}{Adds \code{shiny}, \code{DT}, \code{attempt}, \code{glue}, \code{golem}, \code{htmltools} to dependencies} -\item{use_recommended_tests}{Adds a test folder and copy the golem tests} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_recommended.R +\name{use_recommended_deps} +\alias{use_recommended_deps} +\alias{use_recommended_tests} +\title{Add recommended elements} +\usage{ +use_recommended_deps(pkg = ".", recommended = c("shiny", "DT", + "attempt", "glue", "htmltools", "golem")) + +use_recommended_tests(pkg = ".") +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{recommended}{A vector of recommended packages.} +} +\description{ +\describe{ +\item{use_recommended_deps}{Adds \code{shiny}, \code{DT}, \code{attempt}, \code{glue}, \code{golem}, \code{htmltools} to dependencies} +\item{use_recommended_tests}{Adds a test folder and copy the golem tests} +} +} diff --git a/man/utils_files.Rd b/man/utils_files.Rd index 9d871af3..ff8212f5 100644 --- a/man/utils_files.Rd +++ b/man/utils_files.Rd @@ -1,20 +1,20 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_utils.R -\name{use_utils_ui} -\alias{use_utils_ui} -\alias{use_utils_server} -\title{Use the utils files} -\usage{ -use_utils_ui(pkg = ".") - -use_utils_server(pkg = ".") -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} -} -\description{ -\describe{ -\item{use_utils_ui}{Copies the golem_utils_ui.R to the R folder.} -\item{use_utils_server}{Copies the golem_utils_server.R to the R folder.} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_utils.R +\name{use_utils_ui} +\alias{use_utils_ui} +\alias{use_utils_server} +\title{Use the utils files} +\usage{ +use_utils_ui(pkg = ".") + +use_utils_server(pkg = ".") +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} +} +\description{ +\describe{ +\item{use_utils_ui}{Copies the golem_utils_ui.R to the R folder.} +\item{use_utils_server}{Copies the golem_utils_server.R to the R folder.} +} +} diff --git a/man/with_golem_options.Rd b/man/with_golem_options.Rd index 83824abf..3ea7adf4 100644 --- a/man/with_golem_options.Rd +++ b/man/with_golem_options.Rd @@ -1,24 +1,24 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/with_opt.R -\name{with_golem_options} -\alias{with_golem_options} -\title{Add Golem options to a Shiny App} -\usage{ -with_golem_options(app, golem_opts) -} -\arguments{ -\item{app}{the app object.} - -\item{golem_opts}{A list of Options to be added to the app} -} -\value{ -a shiny.appObj object -} -\description{ -Add Golem options to a Shiny App -} -\note{ -You'll probably never have to write this function -as it is included in the golem template created on -launch. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{with_golem_options} +\alias{with_golem_options} +\title{Add Golem options to a Shiny App} +\usage{ +with_golem_options(app, golem_opts) +} +\arguments{ +\item{app}{the app object.} + +\item{golem_opts}{A list of Options to be added to the app} +} +\value{ +a shiny.appObj object +} +\description{ +Add Golem options to a Shiny App +} +\note{ +You'll probably never have to write this function +as it is included in the golem template created on +launch. +} From 48bf182941acdec7375b215408ad760222c0d4c8 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 5 Oct 2019 17:47:14 +0200 Subject: [PATCH 006/211] add remotes r-hub/sysreqs --- DESCRIPTION | 2 ++ tests/testthat/DESCRIPTION | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 5653614a..6a21effa 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -70,6 +70,8 @@ Suggests: VignetteBuilder: knitr Encoding: UTF-8 +Remotes: + r-hub/sysreqs LazyData: true Roxygen: list(markdown = TRUE) RoxygenNote: 6.1.1 diff --git a/tests/testthat/DESCRIPTION b/tests/testthat/DESCRIPTION index 1850df0b..f2f99f57 100644 --- a/tests/testthat/DESCRIPTION +++ b/tests/testthat/DESCRIPTION @@ -9,8 +9,6 @@ LazyData: true Imports: shiny, golem -Remotes: - Thinkr-open/golem RoxygenNote: 6.1.0 URL: http://repo_url.com BugReports: http://repo_url.com/issues From 587a8ed32d78062f25e3dfebbeb9f734799f9150 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 5 Oct 2019 17:55:26 +0200 Subject: [PATCH 007/211] set sysreqs to FALSE for fast testing --- tests/testthat/test-add_dockerfile.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-add_dockerfile.R b/tests/testthat/test-add_dockerfile.R index e38490b6..dcb93372 100644 --- a/tests/testthat/test-add_dockerfile.R +++ b/tests/testthat/test-add_dockerfile.R @@ -3,7 +3,7 @@ context("function add_dockerfile") test_that("add_dockerfile", { with_dir(pkg, { remove_file("Dockerfile") - output <- testthat::capture_output(add_dockerfile()) + output <- testthat::capture_output(add_dockerfile(sysreqs = FALSE)) expect_true(file.exists("Dockerfile")) test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") @@ -16,7 +16,7 @@ test_that("add_dockerfile", { test_that("add_dockerfile_heroku", { with_dir(pkg, { remove_file("Dockerfile") - output <- testthat::capture_output(add_dockerfile_heroku()) + output <- testthat::capture_output(add_dockerfile_heroku(sysreqs = FALSE)) expect_true(file.exists("Dockerfile")) test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") @@ -28,7 +28,7 @@ test_that("add_dockerfile_heroku", { test_that("add_dockerfile_shinyproxy", { with_dir(pkg, { remove_file("Dockerfile") - output <- testthat::capture_output(add_dockerfile_shinyproxy()) + output <- testthat::capture_output(add_dockerfile_shinyproxy(sysreqs = FALSE)) expect_true(file.exists("Dockerfile")) test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") From 66175c41ecf6032e92328a105c1be7fea2232ede Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 5 Oct 2019 19:06:43 +0200 Subject: [PATCH 008/211] use get_sysreqs to not depend from {sysreqs} packages --- DESCRIPTION | 155 ++++++++++++++++++++--------------------- NAMESPACE | 5 +- NEWS.md | 2 + R/add_deploy_helpers.R | 24 ++++--- R/get_sysreqs.R | 17 +++++ man/get_sysreqs.Rd | 16 +++++ 6 files changed, 130 insertions(+), 89 deletions(-) create mode 100644 R/get_sysreqs.R create mode 100644 man/get_sysreqs.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 6a21effa..9c58c848 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,78 +1,77 @@ -Package: golem -Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9001 -Authors@R: - c(person(given = "Vincent", - family = "Guyader", - role = c("cre", "aut"), - email = "vincent@thinkr.fr", - comment = c(ORCID = "0000-0003-0671-9270")), - person(given = "Colin", - family = "Fay", - role = "aut", - email = "contact@colinfay.me", - comment = c(ORCID = "0000-0001-7343-1846")), - 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 = "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. -License: MIT + file LICENSE -URL: https://github.com/ThinkR-open/golem -BugReports: https://github.com/ThinkR-open/golem/issues -Depends: - R (>= 3.0) -Imports: - attempt (>= 0.3.0), - cli, - crayon, - desc, - dockerfiler, - DT, - glue, - htmltools, - pkgload, - processx, - remotes, - rlang, - roxygen2, - rsconnect, - rstudioapi, - shiny, - stats, - stringr, - testthat, - tools, - usethis, - utils, - yesno, - sysreqs -Suggests: - spelling, - covr, - knitr, - pkgdown, - purrr, - rcmdcheck, - rmarkdown, - withr -VignetteBuilder: - knitr -Encoding: UTF-8 -Remotes: - r-hub/sysreqs -LazyData: true -Roxygen: list(markdown = TRUE) -RoxygenNote: 6.1.1 -Language: en-US +Package: golem +Title: A Framework for Robust Shiny Applications +Version: 0.1.0.9001 +Authors@R: + c(person(given = "Vincent", + family = "Guyader", + role = c("cre", "aut"), + email = "vincent@thinkr.fr", + comment = c(ORCID = "0000-0003-0671-9270")), + person(given = "Colin", + family = "Fay", + role = "aut", + email = "contact@colinfay.me", + comment = c(ORCID = "0000-0001-7343-1846")), + 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 = "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. +License: MIT + file LICENSE +URL: https://github.com/ThinkR-open/golem +BugReports: https://github.com/ThinkR-open/golem/issues +Depends: + R (>= 3.0) +Imports: + attempt (>= 0.3.0), + cli, + crayon, + desc, + dockerfiler, + DT, + glue, + htmltools, + pkgload, + processx, + remotes, + rlang, + roxygen2, + rsconnect, + rstudioapi, + shiny, + stats, + stringr, + testthat, + tools, + usethis, + utils, + yesno, + miniCRAN, + jsonlite +Suggests: + spelling, + covr, + knitr, + pkgdown, + purrr, + rcmdcheck, + rmarkdown, + withr +VignetteBuilder: + knitr +Encoding: UTF-8 +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 6.1.1 +Language: en-US diff --git a/NAMESPACE b/NAMESPACE index 46031dcc..1cf2ddbb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,6 +30,7 @@ export(fill_desc) export(get_dependencies) export(get_golem_options) export(get_golem_wd) +export(get_sysreqs) export(invoke_js) export(make_dev) export(message_dev) @@ -57,6 +58,8 @@ importFrom(glue,glue) importFrom(htmltools,includeScript) importFrom(htmltools,save_html) importFrom(htmltools,tags) +importFrom(jsonlite,fromJSON) +importFrom(miniCRAN,pkgDep) importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(rlang,enquo) @@ -66,7 +69,6 @@ importFrom(rstudioapi,openProject) importFrom(shiny,getShinyOption) importFrom(stats,setNames) importFrom(stringr,str_remove_all) -importFrom(sysreqs,sysreqs) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) @@ -74,6 +76,7 @@ importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) importFrom(usethis,use_testthat) importFrom(utils,capture.output) +importFrom(utils,download.file) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) importFrom(utils,installed.packages) diff --git a/NEWS.md b/NEWS.md index 2df98188..73b210d2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ ## New features ++ `add_dockerfile()` now use installed pacakge version and explicit System Requirements + + `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157) + `add_module` now allows to create and `fct_` and an `utils_` file (#154, @novica) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 4db04523..f4ee2af2 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -122,7 +122,6 @@ add_shinyserver_file <- function( #' @param repos character vector, the base URL of the repositories #' @export #' @rdname dockerfiles -#' @importFrom sysreqs sysreqs #' @importFrom desc desc_get_deps #' @importFrom dockerfiler Dockerfile #' @examples @@ -297,15 +296,7 @@ dock_from_desc <- function( repos = "https://cran.rstudio.com/" ){ - if (sysreqs){ - # please wait during system requirement calculation - cat_bullet("Please wait during system requirements calculation...",bullet = "info",bullet_col = "green") # TODO animated version ? - system_requirement <- unique(sysreqs::sysreqs(desc = path,platform = "linux-x86_64-debian-gcc")) - cat_bullet("done",bullet = "tick",bullet_col = "green") # TODO animated version ? - - }else{ - system_requirement <- NULL - } + packages <- desc::desc_get_deps(path)$package packages <- packages[packages != "R"] # remove R packages <- packages[ !packages %in% c("base", "boot", "class", "cluster", @@ -316,6 +307,19 @@ dock_from_desc <- function( "nnet", "parallel", "rpart", "spatial", "splines", "stats", "stats4", "survival", "tcltk", "tools", "utils")] # remove base and recommended + + + if (sysreqs){ + # please wait during system requirement calculation + cat_bullet("Please wait during system requirements calculation...",bullet = "info",bullet_col = "green") # TODO animated version ? + system_requirement <- unique(get_sysreqs(packages = packages)) + cat_bullet("done",bullet = "tick",bullet_col = "green") # TODO animated version ? + + }else{ + system_requirement <- NULL + } + + pkg <- setNames(lapply(packages, packageVersion), packages) dock <- dockerfiler::Dockerfile$new(FROM = FROM) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R new file mode 100644 index 00000000..1c06ec5c --- /dev/null +++ b/R/get_sysreqs.R @@ -0,0 +1,17 @@ + +#' get system requirements +#' +#' @param packages character vector of packages names +#' @param quiet boolean if TRUE the function is quiet +#' @importFrom miniCRAN pkgDep +#' @importFrom utils download.file +#' @importFrom jsonlite fromJSON +get_sysreqs <- function(packages, quiet = TRUE){ +all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") +url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) +path <- tempfile() +utils::download.file(url, path,mode = "wb",quiet = quiet) +out <- jsonlite::fromJSON(path) +unlink(path) +out +} diff --git a/man/get_sysreqs.Rd b/man/get_sysreqs.Rd new file mode 100644 index 00000000..e3607f9a --- /dev/null +++ b/man/get_sysreqs.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_sysreqs.R +\name{get_sysreqs} +\alias{get_sysreqs} +\title{get system requirements} +\usage{ +get_sysreqs(packages, quiet = TRUE) +} +\arguments{ +\item{packages}{character vector of packages names} + +\item{quiet}{boolean if TRUE the function is quiet} +} +\description{ +get system requirements +} From 5bea0e69dffa0c4c10751cc75905e538fe8b58bb Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 5 Oct 2019 19:21:21 +0200 Subject: [PATCH 009/211] remove miniCRAN depends --- DESCRIPTION | 153 ++++++++++++++++++++++++------------------------ NAMESPACE | 3 +- R/get_sysreqs.R | 6 +- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 9c58c848..a9556b90 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,77 +1,76 @@ -Package: golem -Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9001 -Authors@R: - c(person(given = "Vincent", - family = "Guyader", - role = c("cre", "aut"), - email = "vincent@thinkr.fr", - comment = c(ORCID = "0000-0003-0671-9270")), - person(given = "Colin", - family = "Fay", - role = "aut", - email = "contact@colinfay.me", - comment = c(ORCID = "0000-0001-7343-1846")), - 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 = "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. -License: MIT + file LICENSE -URL: https://github.com/ThinkR-open/golem -BugReports: https://github.com/ThinkR-open/golem/issues -Depends: - R (>= 3.0) -Imports: - attempt (>= 0.3.0), - cli, - crayon, - desc, - dockerfiler, - DT, - glue, - htmltools, - pkgload, - processx, - remotes, - rlang, - roxygen2, - rsconnect, - rstudioapi, - shiny, - stats, - stringr, - testthat, - tools, - usethis, - utils, - yesno, - miniCRAN, - jsonlite -Suggests: - spelling, - covr, - knitr, - pkgdown, - purrr, - rcmdcheck, - rmarkdown, - withr -VignetteBuilder: - knitr -Encoding: UTF-8 -LazyData: true -Roxygen: list(markdown = TRUE) -RoxygenNote: 6.1.1 -Language: en-US +Package: golem +Title: A Framework for Robust Shiny Applications +Version: 0.1.0.9001 +Authors@R: + c(person(given = "Vincent", + family = "Guyader", + role = c("cre", "aut"), + email = "vincent@thinkr.fr", + comment = c(ORCID = "0000-0003-0671-9270")), + person(given = "Colin", + family = "Fay", + role = "aut", + email = "contact@colinfay.me", + comment = c(ORCID = "0000-0001-7343-1846")), + 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 = "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. +License: MIT + file LICENSE +URL: https://github.com/ThinkR-open/golem +BugReports: https://github.com/ThinkR-open/golem/issues +Depends: + R (>= 3.0) +Imports: + attempt (>= 0.3.0), + cli, + crayon, + desc, + dockerfiler, + DT, + glue, + htmltools, + pkgload, + processx, + remotes, + rlang, + roxygen2, + rsconnect, + rstudioapi, + shiny, + stats, + stringr, + testthat, + tools, + usethis, + utils, + yesno, + jsonlite +Suggests: + spelling, + covr, + knitr, + pkgdown, + purrr, + rcmdcheck, + rmarkdown, + withr +VignetteBuilder: + knitr +Encoding: UTF-8 +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 6.1.1 +Language: en-US diff --git a/NAMESPACE b/NAMESPACE index 1cf2ddbb..190ec5ab 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,7 +30,6 @@ export(fill_desc) export(get_dependencies) export(get_golem_options) export(get_golem_wd) -export(get_sysreqs) export(invoke_js) export(make_dev) export(message_dev) @@ -59,7 +58,6 @@ importFrom(htmltools,includeScript) importFrom(htmltools,save_html) importFrom(htmltools,tags) importFrom(jsonlite,fromJSON) -importFrom(miniCRAN,pkgDep) importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(rlang,enquo) @@ -72,6 +70,7 @@ importFrom(stringr,str_remove_all) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) +importFrom(tools,package_dependencies) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) importFrom(usethis,use_testthat) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 1c06ec5c..18d9b528 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -3,11 +3,13 @@ #' #' @param packages character vector of packages names #' @param quiet boolean if TRUE the function is quiet -#' @importFrom miniCRAN pkgDep #' @importFrom utils download.file #' @importFrom jsonlite fromJSON +#' @importFrom tools package_dependencies get_sysreqs <- function(packages, quiet = TRUE){ -all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") + + # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") + all_deps <- paste(unique(unlist(tools::package_dependencies(packages))), collapse = ",") url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) From ca8ddb355e8c3a0bb659df43dbc647b9005ff5b1 Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 6 Oct 2019 09:20:22 +0200 Subject: [PATCH 010/211] add a title tag to golem_add_external_resources --- inst/shinyexample/R/app_ui.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 75b064ac..7e74949e 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -19,10 +19,11 @@ golem_add_external_resources <- function(){ tags$head( golem::activate_js(), - golem::favicon() + golem::favicon(), # Add here all the external resources # If you have a custom.css in the inst/app/www # Or for example, you can add shinyalert::useShinyalert() here #tags$link(rel="stylesheet", type="text/css", href="www/custom.css") + tags$title("shinyexample") ) } \ No newline at end of file From c03578a68d8197d9bac17970dfb399f844060647 Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 6 Oct 2019 22:15:07 +0200 Subject: [PATCH 011/211] Document that you can use `{golem}` outside of a `{golem}` project --- R/add_files.R | 1 + R/add_modules.R | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/R/add_files.R b/R/add_files.R index 2d3fd5a4..78862531 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -1,6 +1,7 @@ #' Create Files #' #' These functions create files inside the `inst/app` folder. +#' These functions can be used outside of a {golem} package. #' #' @inheritParams add_module #' @param dir Path to the dir where the file while be created. diff --git a/R/add_modules.R b/R/add_modules.R index 62dfe497..fd594a52 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -1,7 +1,8 @@ #' Create a module #' #' This function creates a module inside the `R/` folder, based -#' on a specific module structure. +#' on a specific module structure. This function can be used outside +#' of a {golem} package. #' #' @param name The name of the module #' @param pkg Path to the root of the package. Default is `"."`. From 84798cf5a89ce1a2e6eb853e9c713b6f3f23acd6 Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 6 Oct 2019 22:15:41 +0200 Subject: [PATCH 012/211] Document that you can use `{golem}` outside of a `{golem}` project --- R/add_files.R | 2 +- R/add_modules.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 78862531..36caa8cb 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -1,7 +1,7 @@ #' Create Files #' #' These functions create files inside the `inst/app` folder. -#' These functions can be used outside of a {golem} package. +#' These functions can be used outside of a {golem} project. #' #' @inheritParams add_module #' @param dir Path to the dir where the file while be created. diff --git a/R/add_modules.R b/R/add_modules.R index fd594a52..fc853a2f 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -2,7 +2,7 @@ #' #' This function creates a module inside the `R/` folder, based #' on a specific module structure. This function can be used outside -#' of a {golem} package. +#' of a {golem} project. #' #' @param name The name of the module #' @param pkg Path to the root of the package. Default is `"."`. From a614c641b5c1b0276adc6c0213a48cf51679b278 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Mon, 7 Oct 2019 16:33:32 +0900 Subject: [PATCH 013/211] Added add_external_js_file and add_external_css_file function Added functions add_external_js_file and add_external_css_file that download .js and .css file off the web to the appropriate directory. --- NAMESPACE | 2 + R/add_files.R | 118 +++++++++++++++++++++++++++++ man/add_files.Rd | 8 ++ tests/testthat/test-add_function.R | 26 +++++++ 4 files changed, 154 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index f1a3eeb9..41a30ed8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,8 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) +export(add_external_css_file) +export(add_external_js_file) export(add_fct) export(add_js_file) export(add_js_handler) diff --git a/R/add_files.R b/R/add_files.R index 2d3fd5a4..4962eff7 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -57,6 +57,65 @@ add_js_file <- function( } } +#' @export +#' @rdname add_files +add_external_js_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.js") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "js") { + cat_red_bullet( + "File not added (URL must end with .js extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} + #' @export #' @rdname add_files add_js_handler <- function( @@ -169,6 +228,65 @@ add_css_file <- function( } +#' @export +#' @rdname add_files +add_external_css_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.css") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "css") { + cat_red_bullet( + "File not added (URL must end with .css extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} + #' @export #' @rdname add_files #' @importFrom glue glue diff --git a/man/add_files.Rd b/man/add_files.Rd index b6099368..61122496 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -2,20 +2,28 @@ % Please edit documentation in R/add_files.R \name{add_js_file} \alias{add_js_file} +\alias{add_external_js_file} \alias{add_js_handler} \alias{add_css_file} +\alias{add_external_css_file} \alias{add_ui_server_files} \title{Create Files} \usage{ add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) +add_external_js_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) + add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) add_css_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) +add_external_css_file(url, name, 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) } diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index cd455d69..f20937d0 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -18,6 +18,18 @@ test_that("add_css_file", { }) }) +test_that("add_external_css_file", { + with_dir(pkg, { + remove_file("inst/app/www/style.css") + add_external_css_file("https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "style", open = FALSE) + expect_true(file.exists("inst/app/www/style.css")) + + style <-list.files("inst/app/www/", pattern = "style") + expect_equal(tools::file_ext(style), "css") + remove_file("inst/app/www/style.css") + }) +}) + test_that("add_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") @@ -38,6 +50,20 @@ test_that("add_js_file", { }) }) +test_that("add_external_js_file", { + with_dir(pkg, { + remove_file("inst/app/www/script.js") + add_external_js_file("https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js", "script", open = FALSE) + expect_true(file.exists("inst/app/www/script.js")) + + script <- + list.files("inst/app/www/", pattern = "script") + expect_equal(tools::file_ext(script), + "js") + remove_file("inst/app/www/script.js") + }) +}) + test_that("add_js_handler", { with_dir(pkg, { remove_file("inst/app/www/handler.js") From 67ed010861f54e15fe9ae3b73e5c977929709a20 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Mon, 7 Oct 2019 16:57:49 +0900 Subject: [PATCH 014/211] Documentation fix for URL parameter Added parameter documentation to fix warning --- R/add_files.R | 1 + man/add_files.Rd | 2 ++ 2 files changed, 3 insertions(+) diff --git a/R/add_files.R b/R/add_files.R index 4962eff7..d63faeea 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -3,6 +3,7 @@ #' These functions create files inside the `inst/app` folder. #' #' @inheritParams add_module +#' @param url String representation of URL for the file to be downloaded #' @param dir Path to the dir where the file while be created. #' @export #' @rdname add_files diff --git a/man/add_files.Rd b/man/add_files.Rd index 61122496..61be8c88 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -37,6 +37,8 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \item{open}{Should the file be opened?} \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} + +\item{url}{String representation of URL for the file to be downloaded} } \description{ These functions create files inside the \code{inst/app} folder. From ffc8e3d5badae912a7d7af9e50d2aada8dcfab88 Mon Sep 17 00:00:00 2001 From: Kok Ben Toh Date: Mon, 7 Oct 2019 14:45:42 -0400 Subject: [PATCH 015/211] Create Rstudio addin to wrap ns() around selection --- R/addin_functions.R | 13 +++++++++++++ inst/addins.dcf | 4 ++++ 2 files changed, 17 insertions(+) create mode 100644 R/addin_functions.R create mode 100644 inst/addins.dcf diff --git a/R/addin_functions.R b/R/addin_functions.R new file mode 100644 index 00000000..046fccb5 --- /dev/null +++ b/R/addin_functions.R @@ -0,0 +1,13 @@ +#' @importFrom rstudioapi getSourceEditorContext +#' @importFrom rstudioapi modifyRange +insert_ns <- function () { + curr_editor <- rstudioapi::getSourceEditorContext() + + id <- curr_editor$id + sel_rng <- curr_editor$selection[[1]]$range + sel_text <- curr_editor$selection[[1]]$text + + mod_text <- paste0("ns(", sel_text, ")") + + rstudioapi::modifyRange(sel_rng, mod_text, id = id) +} \ No newline at end of file diff --git a/inst/addins.dcf b/inst/addins.dcf new file mode 100644 index 00000000..676d0f44 --- /dev/null +++ b/inst/addins.dcf @@ -0,0 +1,4 @@ +Name: Put selected text into between ns() +Description: add 'ns(' in front of selected text, and add ')' behind. +Binding: insert_ns +Interactive: false \ No newline at end of file From 23804f82aa97b7accc2277822ca8f850601c23c0 Mon Sep 17 00:00:00 2001 From: Kok Ben Toh Date: Mon, 7 Oct 2019 14:53:14 -0400 Subject: [PATCH 016/211] export insert_ns()? --- R/addin_functions.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/addin_functions.R b/R/addin_functions.R index 046fccb5..f331e326 100644 --- a/R/addin_functions.R +++ b/R/addin_functions.R @@ -1,5 +1,7 @@ #' @importFrom rstudioapi getSourceEditorContext #' @importFrom rstudioapi modifyRange +#' +#' @export insert_ns <- function () { curr_editor <- rstudioapi::getSourceEditorContext() From 22e271dc88f81945d0c5913d2b052b0de0392c34 Mon Sep 17 00:00:00 2001 From: Kok Ben Toh Date: Mon, 7 Oct 2019 14:59:10 -0400 Subject: [PATCH 017/211] Move addins.dcf into subfolder rstudio Created addins.dcf at wrong place... --- inst/{ => rstudio}/addins.dcf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename inst/{ => rstudio}/addins.dcf (100%) diff --git a/inst/addins.dcf b/inst/rstudio/addins.dcf similarity index 100% rename from inst/addins.dcf rename to inst/rstudio/addins.dcf From 21206f7615178917d308af1be4b46fc6f7961241 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 7 Oct 2019 22:43:09 +0200 Subject: [PATCH 018/211] remove NA from sysreqs --- R/get_sysreqs.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 18d9b528..965d6ab4 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -15,5 +15,5 @@ path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) out <- jsonlite::fromJSON(path) unlink(path) -out +sort(unique(out[!is.na(out)])) } From c777d0587a15ae6c8047b2f502a5ec6a4dcaf0bd Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 7 Oct 2019 23:15:03 +0200 Subject: [PATCH 019/211] fix #197 --- R/get_sysreqs.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 965d6ab4..e990bafe 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -10,7 +10,8 @@ get_sysreqs <- function(packages, quiet = TRUE){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") all_deps <- paste(unique(unlist(tools::package_dependencies(packages))), collapse = ",") -url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) +all_deps <- c(packages,all_deps) + url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) out <- jsonlite::fromJSON(path) From 6d48dd5e605629ce501486752b62d29f1ee30975 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 7 Oct 2019 23:24:36 +0200 Subject: [PATCH 020/211] use miniCRAN to get all pacakges_dependencies --- DESCRIPTION | 153 ++++++++++++++++++++++++------------------------ NAMESPACE | 2 +- R/get_sysreqs.R | 15 +++-- 3 files changed, 85 insertions(+), 85 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a9556b90..3a7619b0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,76 +1,77 @@ -Package: golem -Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9001 -Authors@R: - c(person(given = "Vincent", - family = "Guyader", - role = c("cre", "aut"), - email = "vincent@thinkr.fr", - comment = c(ORCID = "0000-0003-0671-9270")), - person(given = "Colin", - family = "Fay", - role = "aut", - email = "contact@colinfay.me", - comment = c(ORCID = "0000-0001-7343-1846")), - 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 = "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. -License: MIT + file LICENSE -URL: https://github.com/ThinkR-open/golem -BugReports: https://github.com/ThinkR-open/golem/issues -Depends: - R (>= 3.0) -Imports: - attempt (>= 0.3.0), - cli, - crayon, - desc, - dockerfiler, - DT, - glue, - htmltools, - pkgload, - processx, - remotes, - rlang, - roxygen2, - rsconnect, - rstudioapi, - shiny, - stats, - stringr, - testthat, - tools, - usethis, - utils, - yesno, - jsonlite -Suggests: - spelling, - covr, - knitr, - pkgdown, - purrr, - rcmdcheck, - rmarkdown, - withr -VignetteBuilder: - knitr -Encoding: UTF-8 -LazyData: true -Roxygen: list(markdown = TRUE) -RoxygenNote: 6.1.1 -Language: en-US +Package: golem +Title: A Framework for Robust Shiny Applications +Version: 0.1.0.9001 +Authors@R: + c(person(given = "Vincent", + family = "Guyader", + role = c("cre", "aut"), + email = "vincent@thinkr.fr", + comment = c(ORCID = "0000-0003-0671-9270")), + person(given = "Colin", + family = "Fay", + role = "aut", + email = "contact@colinfay.me", + comment = c(ORCID = "0000-0001-7343-1846")), + 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 = "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. +License: MIT + file LICENSE +URL: https://github.com/ThinkR-open/golem +BugReports: https://github.com/ThinkR-open/golem/issues +Depends: + R (>= 3.0) +Imports: + attempt (>= 0.3.0), + cli, + crayon, + desc, + dockerfiler, + DT, + glue, + htmltools, + pkgload, + processx, + remotes, + rlang, + roxygen2, + rsconnect, + rstudioapi, + shiny, + stats, + stringr, + testthat, + tools, + usethis, + utils, + yesno, + jsonlite, + miniCRAN +Suggests: + spelling, + covr, + knitr, + pkgdown, + purrr, + rcmdcheck, + rmarkdown, + withr +VignetteBuilder: + knitr +Encoding: UTF-8 +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 6.1.1 +Language: en-US diff --git a/NAMESPACE b/NAMESPACE index 190ec5ab..badc3aa2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -58,6 +58,7 @@ importFrom(htmltools,includeScript) importFrom(htmltools,save_html) importFrom(htmltools,tags) importFrom(jsonlite,fromJSON) +importFrom(miniCRAN,pkgDep) importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(rlang,enquo) @@ -70,7 +71,6 @@ importFrom(stringr,str_remove_all) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) -importFrom(tools,package_dependencies) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) importFrom(usethis,use_testthat) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index e990bafe..ebea5f39 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -5,16 +5,15 @@ #' @param quiet boolean if TRUE the function is quiet #' @importFrom utils download.file #' @importFrom jsonlite fromJSON -#' @importFrom tools package_dependencies +#' @importFrom miniCRAN pkgDep get_sysreqs <- function(packages, quiet = TRUE){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") - all_deps <- paste(unique(unlist(tools::package_dependencies(packages))), collapse = ",") -all_deps <- c(packages,all_deps) + all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages)))), collapse = ",") url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) -path <- tempfile() -utils::download.file(url, path,mode = "wb",quiet = quiet) -out <- jsonlite::fromJSON(path) -unlink(path) -sort(unique(out[!is.na(out)])) + path <- tempfile() + utils::download.file(url, path,mode = "wb",quiet = quiet) + out <- jsonlite::fromJSON(path) + unlink(path) + sort(unique(out[!is.na(out)])) } From 9b9cd55d493436604cb4c467a14416513b3fa03c Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 7 Oct 2019 23:27:43 +0200 Subject: [PATCH 021/211] suggests = FALSE for miniCRAN::pkgDep --- R/get_sysreqs.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index ebea5f39..3e3bae9a 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -9,7 +9,7 @@ get_sysreqs <- function(packages, quiet = TRUE){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") - all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages)))), collapse = ",") + all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages, suggests = FALSE)))), collapse = ",") url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) From 9b5e1a9d57b6d1807b771fc50c26d3d84aef5e94 Mon Sep 17 00:00:00 2001 From: statnmap Date: Tue, 8 Oct 2019 11:17:20 +0200 Subject: [PATCH 022/211] Add cheatshette in vignette closes issue #132 --- DESCRIPTION | 1 - vignettes/e_golem_cheatsheet.Rmd | 27 +++++++++++++++++++++++++++ vignettes/golem_cheatsheet_V0.1.jpg | Bin 0 -> 655070 bytes 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 vignettes/e_golem_cheatsheet.Rmd create mode 100644 vignettes/golem_cheatsheet_V0.1.jpg diff --git a/DESCRIPTION b/DESCRIPTION index 5b13e861..46c76f8b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -59,7 +59,6 @@ Imports: yesno Suggests: spelling, - yesno, covr, knitr, pkgdown, diff --git a/vignettes/e_golem_cheatsheet.Rmd b/vignettes/e_golem_cheatsheet.Rmd new file mode 100644 index 00000000..4c1c9199 --- /dev/null +++ b/vignettes/e_golem_cheatsheet.Rmd @@ -0,0 +1,27 @@ +--- +title: "{golem} cheatsheet" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{e_golem_cheatsheet} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +**[The high-resolution {golem} cheatsheet is available to download here](https://thinkr.fr/golem_cheatsheet_V0.1.pdf)** + + +```{r, echo=FALSE, out.width="100%"} +knitr::include_graphics("golem_cheatsheet_V0.1.jpg") +``` + + + + + diff --git a/vignettes/golem_cheatsheet_V0.1.jpg b/vignettes/golem_cheatsheet_V0.1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c3f38a9541291e3a665c488c11b0f2d568b795a GIT binary patch literal 655070 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�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 z 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�cI#NoZ*`}Y@m`4Fj#t?2 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%0A~5`{vCDQzT+a2F+%~py{$c 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%0Df_<^Af* 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 z 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

~<4z=K|rcqF`DE2&lD%l`kWg4}&6P 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 Date: Wed, 9 Oct 2019 08:17:39 +0900 Subject: [PATCH 023/211] Modified `invoke_js` such that it can take in lists and multiple values --- R/js.R | 19 ++++++++++--------- man/golem_js.Rd | 4 +--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/R/js.R b/R/js.R index 7f1f73ff..86ceab20 100644 --- a/R/js.R +++ b/R/js.R @@ -6,7 +6,6 @@ #' to launch any JS function created inside a Shiny JavaScript handler. #' #' @param fun JS function to be invoked. -#' @param ui_ref The UI reference to call the JS function on. #' @param session The shiny session within which to call \code{sendCustomMessage}. #' #' @@ -38,13 +37,15 @@ activate_js <- function(){ #' @rdname golem_js invoke_js <- function( fun, - ui_ref, - session = shiny::getDefaultReactiveDomain() + ..., + session = shiny::getDefaultReactiveDomain() ){ - res <- mapply(function(x, y){ - session$sendCustomMessage(x, y) - }, x = fun, y = ui_ref) + messages <- list(...) + res <- lapply( + messages, + function(message, fun){ + session$sendCustomMessage(fun, message) + }, + fun=fun) invisible(res) - #session$sendCustomMessage(fun, ui_ref) -} - +} \ No newline at end of file diff --git a/man/golem_js.Rd b/man/golem_js.Rd index 85a0f3c2..7b3c6b06 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -7,13 +7,11 @@ \usage{ activate_js() -invoke_js(fun, ui_ref, session = shiny::getDefaultReactiveDomain()) +invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) } \arguments{ \item{fun}{JS function to be invoked.} -\item{ui_ref}{The UI reference to call the JS function on.} - \item{session}{The shiny session within which to call \code{sendCustomMessage}. \describe{ From 4def455a494f534334d04eea084a354c636813db Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 08:32:12 +0900 Subject: [PATCH 024/211] Revert "Added add_external_js_file and add_external_css_file function" This reverts commit a614c641b5c1b0276adc6c0213a48cf51679b278. --- NAMESPACE | 2 - R/add_files.R | 118 ----------------------------- man/add_files.Rd | 8 -- tests/testthat/test-add_function.R | 26 ------- 4 files changed, 154 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 41a30ed8..f1a3eeb9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,8 +5,6 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) -export(add_external_css_file) -export(add_external_js_file) export(add_fct) export(add_js_file) export(add_js_handler) diff --git a/R/add_files.R b/R/add_files.R index d63faeea..6e19442c 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -58,65 +58,6 @@ add_js_file <- function( } } -#' @export -#' @rdname add_files -add_external_js_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.js") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "js") { - cat_red_bullet( - "File not added (URL must end with .js extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} - #' @export #' @rdname add_files add_js_handler <- function( @@ -229,65 +170,6 @@ add_css_file <- function( } -#' @export -#' @rdname add_files -add_external_css_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.css") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "css") { - cat_red_bullet( - "File not added (URL must end with .css extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} - #' @export #' @rdname add_files #' @importFrom glue glue diff --git a/man/add_files.Rd b/man/add_files.Rd index 61be8c88..75643bbe 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -2,28 +2,20 @@ % Please edit documentation in R/add_files.R \name{add_js_file} \alias{add_js_file} -\alias{add_external_js_file} \alias{add_js_handler} \alias{add_css_file} -\alias{add_external_css_file} \alias{add_ui_server_files} \title{Create Files} \usage{ add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) -add_external_js_file(url, name, pkg = get_golem_wd(), - dir = "inst/app/www", open = TRUE, dir_create = TRUE) - add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) add_css_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) -add_external_css_file(url, name, 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) } diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index f20937d0..cd455d69 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -18,18 +18,6 @@ test_that("add_css_file", { }) }) -test_that("add_external_css_file", { - with_dir(pkg, { - remove_file("inst/app/www/style.css") - add_external_css_file("https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "style", open = FALSE) - expect_true(file.exists("inst/app/www/style.css")) - - style <-list.files("inst/app/www/", pattern = "style") - expect_equal(tools::file_ext(style), "css") - remove_file("inst/app/www/style.css") - }) -}) - test_that("add_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") @@ -50,20 +38,6 @@ test_that("add_js_file", { }) }) -test_that("add_external_js_file", { - with_dir(pkg, { - remove_file("inst/app/www/script.js") - add_external_js_file("https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js", "script", open = FALSE) - expect_true(file.exists("inst/app/www/script.js")) - - script <- - list.files("inst/app/www/", pattern = "script") - expect_equal(tools::file_ext(script), - "js") - remove_file("inst/app/www/script.js") - }) -}) - test_that("add_js_handler", { with_dir(pkg, { remove_file("inst/app/www/handler.js") From fcbc387516f608959acd191a6f777d999c8c9684 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 08:32:18 +0900 Subject: [PATCH 025/211] Revert "Documentation fix for URL parameter" This reverts commit 67ed010861f54e15fe9ae3b73e5c977929709a20. --- R/add_files.R | 1 - man/add_files.Rd | 2 -- 2 files changed, 3 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 6e19442c..2d3fd5a4 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -3,7 +3,6 @@ #' These functions create files inside the `inst/app` folder. #' #' @inheritParams add_module -#' @param url String representation of URL for the file to be downloaded #' @param dir Path to the dir where the file while be created. #' @export #' @rdname add_files diff --git a/man/add_files.Rd b/man/add_files.Rd index 75643bbe..b6099368 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -29,8 +29,6 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \item{open}{Should the file be opened?} \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} - -\item{url}{String representation of URL for the file to be downloaded} } \description{ These functions create files inside the \code{inst/app} folder. From a00f302a9c2e9546fe4fe3a0598ee6647bb4bf43 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 09:05:32 +0900 Subject: [PATCH 026/211] Update js.R Error for edge case --- R/js.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/js.R b/R/js.R index 86ceab20..bd7af999 100644 --- a/R/js.R +++ b/R/js.R @@ -40,6 +40,8 @@ invoke_js <- function( ..., session = shiny::getDefaultReactiveDomain() ){ + if (fun == "") + stop("Error: invoke_js must be called with a valid JS handler name!") messages <- list(...) res <- lapply( messages, From 95e9138aee2daa633b6203b9ebdf0c8632de3561 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 09:24:29 +0900 Subject: [PATCH 027/211] Description for ... --- R/js.R | 1 + man/golem_js.Rd | 2 ++ 2 files changed, 3 insertions(+) diff --git a/R/js.R b/R/js.R index bd7af999..97601335 100644 --- a/R/js.R +++ b/R/js.R @@ -6,6 +6,7 @@ #' to launch any JS function created inside a Shiny JavaScript handler. #' #' @param fun JS function to be invoked. +#' @param ... atomics or JSON-like vectors to be sent to the triggered JS function #' @param session The shiny session within which to call \code{sendCustomMessage}. #' #' diff --git a/man/golem_js.Rd b/man/golem_js.Rd index 7b3c6b06..bd1fd3ba 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -12,6 +12,8 @@ invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) \arguments{ \item{fun}{JS function to be invoked.} +\item{...}{atomics or JSON-like vectors to be sent to the triggered JS function} + \item{session}{The shiny session within which to call \code{sendCustomMessage}. \describe{ From 1ea95500ddebdc0e22740ac25c8e9957ac3bda03 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 19:25:02 +0900 Subject: [PATCH 028/211] Added new file use_files.R Added new file use_files.R, containing two functions, `use_external_js_file` and `use_external_css_file` --- NAMESPACE | 4 +- R/add_files.R | 120 --------------------------- R/use_files.R | 127 +++++++++++++++++++++++++++++ man/add_files.Rd | 10 --- man/use_files.Rd | 29 +++++++ tests/testthat/test-add_function.R | 28 +++---- 6 files changed, 172 insertions(+), 146 deletions(-) create mode 100644 R/use_files.R create mode 100644 man/use_files.Rd diff --git a/NAMESPACE b/NAMESPACE index 41a30ed8..dc436510 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,8 +5,6 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) -export(add_external_css_file) -export(add_external_js_file) export(add_fct) export(add_js_file) export(add_js_handler) @@ -39,6 +37,8 @@ export(print_dev) export(remove_favicon) export(set_golem_options) export(set_golem_wd) +export(use_external_css_file) +export(use_external_js_file) export(use_favicon) export(use_recommended_deps) export(use_recommended_tests) diff --git a/R/add_files.R b/R/add_files.R index d63faeea..a07877a8 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -3,7 +3,6 @@ #' These functions create files inside the `inst/app` folder. #' #' @inheritParams add_module -#' @param url String representation of URL for the file to be downloaded #' @param dir Path to the dir where the file while be created. #' @export #' @rdname add_files @@ -58,65 +57,6 @@ add_js_file <- function( } } -#' @export -#' @rdname add_files -add_external_js_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.js") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "js") { - cat_red_bullet( - "File not added (URL must end with .js extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} - #' @export #' @rdname add_files add_js_handler <- function( @@ -228,66 +168,6 @@ add_css_file <- function( } } - -#' @export -#' @rdname add_files -add_external_css_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.css") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "css") { - cat_red_bullet( - "File not added (URL must end with .css extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} - #' @export #' @rdname add_files #' @importFrom glue glue diff --git a/R/use_files.R b/R/use_files.R new file mode 100644 index 00000000..af02752a --- /dev/null +++ b/R/use_files.R @@ -0,0 +1,127 @@ +#' Use Files +#' +#' These functions download files from external sources and install them inside the approriate directory. +#' +#' @inheritParams add_module +#' @param url String representation of URL for the file to be downloaded +#' @param dir Path to the dir where the file while be created. +#' @export +#' @rdname use_files +#' @importFrom glue glue +#' @importFrom cli cat_bullet + +use_external_js_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.js") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "js") { + cat_red_bullet( + "File not added (URL must end with .js extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} + +#' @export +#' @rdname use_files +use_external_css_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.css") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "css") { + cat_red_bullet( + "File not added (URL must end with .css extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} diff --git a/man/add_files.Rd b/man/add_files.Rd index 61be8c88..b6099368 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -2,28 +2,20 @@ % Please edit documentation in R/add_files.R \name{add_js_file} \alias{add_js_file} -\alias{add_external_js_file} \alias{add_js_handler} \alias{add_css_file} -\alias{add_external_css_file} \alias{add_ui_server_files} \title{Create Files} \usage{ add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) -add_external_js_file(url, name, pkg = get_golem_wd(), - dir = "inst/app/www", open = TRUE, dir_create = TRUE) - add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) add_css_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) -add_external_css_file(url, name, 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) } @@ -37,8 +29,6 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \item{open}{Should the file be opened?} \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} - -\item{url}{String representation of URL for the file to be downloaded} } \description{ These functions create files inside the \code{inst/app} folder. diff --git a/man/use_files.Rd b/man/use_files.Rd new file mode 100644 index 00000000..b6e3d472 --- /dev/null +++ b/man/use_files.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_files.R +\name{use_external_js_file} +\alias{use_external_js_file} +\alias{use_external_css_file} +\title{Use Files} +\usage{ +use_external_js_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) + +use_external_css_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) +} +\arguments{ +\item{url}{String representation of URL for the file to be downloaded} + +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dir}{Path to the dir where the file while be created.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} +} +\description{ +These functions download files from external sources and install them inside the approriate directory. +} diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index f20937d0..cd0821d2 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -18,18 +18,6 @@ test_that("add_css_file", { }) }) -test_that("add_external_css_file", { - with_dir(pkg, { - remove_file("inst/app/www/style.css") - add_external_css_file("https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "style", open = FALSE) - expect_true(file.exists("inst/app/www/style.css")) - - style <-list.files("inst/app/www/", pattern = "style") - expect_equal(tools::file_ext(style), "css") - remove_file("inst/app/www/style.css") - }) -}) - test_that("add_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") @@ -50,10 +38,10 @@ test_that("add_js_file", { }) }) -test_that("add_external_js_file", { +test_that("use_external_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") - add_external_js_file("https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js", "script", open = FALSE) + use_external_js_file("https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js", "script", open = FALSE) expect_true(file.exists("inst/app/www/script.js")) script <- @@ -64,6 +52,18 @@ test_that("add_external_js_file", { }) }) +test_that("use_external_css_file", { + with_dir(pkg, { + remove_file("inst/app/www/style.css") + use_external_css_file("https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "style", open = FALSE) + expect_true(file.exists("inst/app/www/style.css")) + + style <-list.files("inst/app/www/", pattern = "style") + expect_equal(tools::file_ext(style), "css") + remove_file("inst/app/www/style.css") + }) +}) + test_that("add_js_handler", { with_dir(pkg, { remove_file("inst/app/www/handler.js") From f1bc45a3fc818a3499742cc24416cebe939430df Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 19:36:01 +0900 Subject: [PATCH 029/211] Revert "Added new file use_files.R" This reverts commit 1ea95500ddebdc0e22740ac25c8e9957ac3bda03. --- NAMESPACE | 4 +- R/add_files.R | 120 +++++++++++++++++++++++++++ R/use_files.R | 127 ----------------------------- man/add_files.Rd | 10 +++ man/use_files.Rd | 29 ------- tests/testthat/test-add_function.R | 28 +++---- 6 files changed, 146 insertions(+), 172 deletions(-) delete mode 100644 R/use_files.R delete mode 100644 man/use_files.Rd diff --git a/NAMESPACE b/NAMESPACE index dc436510..41a30ed8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,8 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) +export(add_external_css_file) +export(add_external_js_file) export(add_fct) export(add_js_file) export(add_js_handler) @@ -37,8 +39,6 @@ export(print_dev) export(remove_favicon) export(set_golem_options) export(set_golem_wd) -export(use_external_css_file) -export(use_external_js_file) export(use_favicon) export(use_recommended_deps) export(use_recommended_tests) diff --git a/R/add_files.R b/R/add_files.R index a07877a8..d63faeea 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -3,6 +3,7 @@ #' These functions create files inside the `inst/app` folder. #' #' @inheritParams add_module +#' @param url String representation of URL for the file to be downloaded #' @param dir Path to the dir where the file while be created. #' @export #' @rdname add_files @@ -57,6 +58,65 @@ add_js_file <- function( } } +#' @export +#' @rdname add_files +add_external_js_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.js") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "js") { + cat_red_bullet( + "File not added (URL must end with .js extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} + #' @export #' @rdname add_files add_js_handler <- function( @@ -168,6 +228,66 @@ add_css_file <- function( } } + +#' @export +#' @rdname add_files +add_external_css_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.css") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "css") { + cat_red_bullet( + "File not added (URL must end with .css extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} + #' @export #' @rdname add_files #' @importFrom glue glue diff --git a/R/use_files.R b/R/use_files.R deleted file mode 100644 index af02752a..00000000 --- a/R/use_files.R +++ /dev/null @@ -1,127 +0,0 @@ -#' Use Files -#' -#' These functions download files from external sources and install them inside the approriate directory. -#' -#' @inheritParams add_module -#' @param url String representation of URL for the file to be downloaded -#' @param dir Path to the dir where the file while be created. -#' @export -#' @rdname use_files -#' @importFrom glue glue -#' @importFrom cli cat_bullet - -use_external_js_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.js") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "js") { - cat_red_bullet( - "File not added (URL must end with .js extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} - -#' @export -#' @rdname use_files -use_external_css_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.css") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "css") { - cat_red_bullet( - "File not added (URL must end with .css extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} diff --git a/man/add_files.Rd b/man/add_files.Rd index b6099368..61be8c88 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -2,20 +2,28 @@ % Please edit documentation in R/add_files.R \name{add_js_file} \alias{add_js_file} +\alias{add_external_js_file} \alias{add_js_handler} \alias{add_css_file} +\alias{add_external_css_file} \alias{add_ui_server_files} \title{Create Files} \usage{ add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) +add_external_js_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) + add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) add_css_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) +add_external_css_file(url, name, 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) } @@ -29,6 +37,8 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \item{open}{Should the file be opened?} \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} + +\item{url}{String representation of URL for the file to be downloaded} } \description{ These functions create files inside the \code{inst/app} folder. diff --git a/man/use_files.Rd b/man/use_files.Rd deleted file mode 100644 index b6e3d472..00000000 --- a/man/use_files.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_files.R -\name{use_external_js_file} -\alias{use_external_js_file} -\alias{use_external_css_file} -\title{Use Files} -\usage{ -use_external_js_file(url, name, pkg = get_golem_wd(), - dir = "inst/app/www", open = TRUE, dir_create = TRUE) - -use_external_css_file(url, name, pkg = get_golem_wd(), - dir = "inst/app/www", open = TRUE, dir_create = TRUE) -} -\arguments{ -\item{url}{String representation of URL for the file to be downloaded} - -\item{name}{The name of the module} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dir}{Path to the dir where the file while be created.} - -\item{open}{Should the file be opened?} - -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} -} -\description{ -These functions download files from external sources and install them inside the approriate directory. -} diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index cd0821d2..f20937d0 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -18,6 +18,18 @@ test_that("add_css_file", { }) }) +test_that("add_external_css_file", { + with_dir(pkg, { + remove_file("inst/app/www/style.css") + add_external_css_file("https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "style", open = FALSE) + expect_true(file.exists("inst/app/www/style.css")) + + style <-list.files("inst/app/www/", pattern = "style") + expect_equal(tools::file_ext(style), "css") + remove_file("inst/app/www/style.css") + }) +}) + test_that("add_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") @@ -38,10 +50,10 @@ test_that("add_js_file", { }) }) -test_that("use_external_js_file", { +test_that("add_external_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") - use_external_js_file("https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js", "script", open = FALSE) + add_external_js_file("https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js", "script", open = FALSE) expect_true(file.exists("inst/app/www/script.js")) script <- @@ -52,18 +64,6 @@ test_that("use_external_js_file", { }) }) -test_that("use_external_css_file", { - with_dir(pkg, { - remove_file("inst/app/www/style.css") - use_external_css_file("https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "style", open = FALSE) - expect_true(file.exists("inst/app/www/style.css")) - - style <-list.files("inst/app/www/", pattern = "style") - expect_equal(tools::file_ext(style), "css") - remove_file("inst/app/www/style.css") - }) -}) - test_that("add_js_handler", { with_dir(pkg, { remove_file("inst/app/www/handler.js") From 881d5d8c9486fcd34f237a5de4dc4e9913314b02 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 19:36:09 +0900 Subject: [PATCH 030/211] Revert "Documentation fix for URL parameter" This reverts commit 67ed010861f54e15fe9ae3b73e5c977929709a20. --- R/add_files.R | 1 - man/add_files.Rd | 2 -- 2 files changed, 3 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index d63faeea..4962eff7 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -3,7 +3,6 @@ #' These functions create files inside the `inst/app` folder. #' #' @inheritParams add_module -#' @param url String representation of URL for the file to be downloaded #' @param dir Path to the dir where the file while be created. #' @export #' @rdname add_files diff --git a/man/add_files.Rd b/man/add_files.Rd index 61be8c88..61122496 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -37,8 +37,6 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \item{open}{Should the file be opened?} \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} - -\item{url}{String representation of URL for the file to be downloaded} } \description{ These functions create files inside the \code{inst/app} folder. From b3d73d9d1b3cf4ed901edc3837060b71d8603f22 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 19:36:12 +0900 Subject: [PATCH 031/211] Revert "Added add_external_js_file and add_external_css_file function" This reverts commit a614c641b5c1b0276adc6c0213a48cf51679b278. --- NAMESPACE | 2 - R/add_files.R | 118 ----------------------------- man/add_files.Rd | 8 -- tests/testthat/test-add_function.R | 26 ------- 4 files changed, 154 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 41a30ed8..f1a3eeb9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,8 +5,6 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) -export(add_external_css_file) -export(add_external_js_file) export(add_fct) export(add_js_file) export(add_js_handler) diff --git a/R/add_files.R b/R/add_files.R index 4962eff7..2d3fd5a4 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -57,65 +57,6 @@ add_js_file <- function( } } -#' @export -#' @rdname add_files -add_external_js_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.js") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "js") { - cat_red_bullet( - "File not added (URL must end with .js extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} - #' @export #' @rdname add_files add_js_handler <- function( @@ -228,65 +169,6 @@ add_css_file <- function( } -#' @export -#' @rdname add_files -add_external_css_file <- function( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -){ - - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - new_file <- glue::glue("{name}.css") - - dir_created <- create_dir_if_needed( - dir, - dir_create - ) - - if (!dir_created){ - cat_red_bullet( - "File not added (needs a valid directory)" - ) - return(invisible(FALSE)) - } - - dir <- normalizePath(dir) - - where <- file.path( - dir, new_file - ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - if ( tools::file_ext(url) != "css") { - cat_red_bullet( - "File not added (URL must end with .css extension)" - ) - return(invisible(FALSE)) - } - - utils::download.file(url, where) - - cat_green_tick(glue::glue("File created at {where}")) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' - ) - ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } -} - #' @export #' @rdname add_files #' @importFrom glue glue diff --git a/man/add_files.Rd b/man/add_files.Rd index 61122496..b6099368 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -2,28 +2,20 @@ % Please edit documentation in R/add_files.R \name{add_js_file} \alias{add_js_file} -\alias{add_external_js_file} \alias{add_js_handler} \alias{add_css_file} -\alias{add_external_css_file} \alias{add_ui_server_files} \title{Create Files} \usage{ add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) -add_external_js_file(url, name, pkg = get_golem_wd(), - dir = "inst/app/www", open = TRUE, dir_create = TRUE) - add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) add_css_file(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) -add_external_css_file(url, name, 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) } diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index f20937d0..cd455d69 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -18,18 +18,6 @@ test_that("add_css_file", { }) }) -test_that("add_external_css_file", { - with_dir(pkg, { - remove_file("inst/app/www/style.css") - add_external_css_file("https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "style", open = FALSE) - expect_true(file.exists("inst/app/www/style.css")) - - style <-list.files("inst/app/www/", pattern = "style") - expect_equal(tools::file_ext(style), "css") - remove_file("inst/app/www/style.css") - }) -}) - test_that("add_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") @@ -50,20 +38,6 @@ test_that("add_js_file", { }) }) -test_that("add_external_js_file", { - with_dir(pkg, { - remove_file("inst/app/www/script.js") - add_external_js_file("https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js", "script", open = FALSE) - expect_true(file.exists("inst/app/www/script.js")) - - script <- - list.files("inst/app/www/", pattern = "script") - expect_equal(tools::file_ext(script), - "js") - remove_file("inst/app/www/script.js") - }) -}) - test_that("add_js_handler", { with_dir(pkg, { remove_file("inst/app/www/handler.js") From bb0efbe3e3b66ee4c1b546ebf451b80a8fa592e0 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 20:20:53 +0900 Subject: [PATCH 032/211] Update golem-js to include handlers for `alert`, `prompt`, `confirm` --- R/js.R | 3 +++ inst/utils/golem-js.js | 37 +++++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/R/js.R b/R/js.R index 7f1f73ff..79ca7096 100644 --- a/R/js.R +++ b/R/js.R @@ -22,6 +22,9 @@ #' \item{clickon}{Click on an element. The full jQuery selector has to be used.} #' \item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} #' \item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} +#' \item{alert}{Open an alert box with the message provided} +#' \item{prompt}{Open a prompt box with the message provided} +#' \item{confirm}{Open a confirm box with the message provided} #' } #' #' @export diff --git a/inst/utils/golem-js.js b/inst/utils/golem-js.js index 1a3ae259..c5c0261c 100644 --- a/inst/utils/golem-js.js +++ b/inst/utils/golem-js.js @@ -1,47 +1,60 @@ $( document ).ready(function() { Shiny.addCustomMessageHandler('show', function(what) { - $(what).show() + $(what).show(); }); Shiny.addCustomMessageHandler('hide', function(what) { - $(what).hide() + $(what).hide(); }); Shiny.addCustomMessageHandler('showid', function(what) { - $("#" + what).show() + $("#" + what).show(); }); Shiny.addCustomMessageHandler('hideid', function(what) { - $("#" + what).hide() + $("#" + what).hide(); }); Shiny.addCustomMessageHandler('showclass', function(what) { - $("." + what).show() + $("." + what).show(); }); Shiny.addCustomMessageHandler('hideclass', function(what) { - $("." + what).hide() + $("." + what).hide(); }); Shiny.addCustomMessageHandler('showhref', function(what) { - $("a[href*=" + what).show() + $("a[href*=" + what).show(); }); Shiny.addCustomMessageHandler('hidehref', function(what) { - $("a[href*=" + what).hide() + $("a[href*=" + what).hide(); }); Shiny.addCustomMessageHandler('clickon', function(what) { - $(what).click() + $(what).click(); }); - Shiny.addCustomMessageHandler('disable', function(what) { - $(what).attr('disabled', 'disabled') + $(what).attr('disabled', 'disabled'); }); Shiny.addCustomMessageHandler('reable', function(what) { - $(what).removeAttr('disabled') + $(what).removeAttr('disabled'); + }); + + Shiny.addCustomMessageHandler('alert', function(message) { + alert(message); + }); + + Shiny.addCustomMessageHandler('prompt', function(message) { + var input = prompt(message); + return input; + }); + + Shiny.addCustomMessageHandler('confirm', function(message) { + var input = confirm(message); + return input; }); }); From 52dca809e36291552f148867ccff58f7328494a5 Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Wed, 9 Oct 2019 20:24:36 +0900 Subject: [PATCH 033/211] Update js.R --- R/js.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/js.R b/R/js.R index 79ca7096..be7068d0 100644 --- a/R/js.R +++ b/R/js.R @@ -22,9 +22,9 @@ #' \item{clickon}{Click on an element. The full jQuery selector has to be used.} #' \item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} #' \item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} -#' \item{alert}{Open an alert box with the message provided} -#' \item{prompt}{Open a prompt box with the message provided} -#' \item{confirm}{Open a confirm box with the message provided} +#' \item{alert}{Open an alert box with the message provided.} +#' \item{prompt}{Open a prompt box with the message provided.} +#' \item{confirm}{Open a confirm box with the message provided.} #' } #' #' @export From 9b4e52568e7cdffbc987bcb48a726c65ef769476 Mon Sep 17 00:00:00 2001 From: ArthurData Date: Wed, 9 Oct 2019 13:38:36 +0200 Subject: [PATCH 034/211] remove formatR package --- DESCRIPTION | 3 +-- NAMESPACE | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 74fcb7b5..4f7585fe 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -56,8 +56,7 @@ Imports: tools, usethis, utils, - yesno, - formatR + yesno Suggests: covr, knitr, diff --git a/NAMESPACE b/NAMESPACE index fd7d5f61..f1a3eeb9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -51,7 +51,6 @@ importFrom(cli,cat_bullet) importFrom(cli,cat_line) importFrom(cli,cat_rule) importFrom(desc,description) -importFrom(formatR,tidy_dir) importFrom(glue,glue) importFrom(htmltools,includeScript) importFrom(htmltools,save_html) From da4443bdc40f01a2ae27cafba324c98beb13b2dc Mon Sep 17 00:00:00 2001 From: ArthurData Date: Wed, 9 Oct 2019 14:30:13 +0200 Subject: [PATCH 035/211] remove_comments function --- R/utils.R | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/R/utils.R b/R/utils.R index 53a7b837..8fb2802c 100644 --- a/R/utils.R +++ b/R/utils.R @@ -63,6 +63,19 @@ replace_word <- function(file,pattern, replace){ writeLines(tx2, con=file) } +remove_comments <- function(file) { + lines <- readLines(file) + lines_without_comment <- c() + for ( line in lines ) { + lines_without_comment <- append( + lines_without_comment, + gsub("(\\s*#+[^'@].*$| #+[^#].*$)", "", line) + ) + } + lines_without_comment <- lines_without_comment[lines_without_comment != ""] + writeLines(text = lines_without_comment, con = file) +} + cat_green_tick <- function(...){ cat_bullet( ..., From 997eaba7de0c378bb6108c4c68e55e761b615264 Mon Sep 17 00:00:00 2001 From: ArthurData Date: Wed, 9 Oct 2019 14:40:59 +0200 Subject: [PATCH 036/211] add whitespace to keep format --- inst/shinyexample/R/app_ui.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 75b064ac..2372d404 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -9,7 +9,7 @@ app_ui <- function() { ) ) } - + #' @import shiny golem_add_external_resources <- function(){ From 0203cef91691fad0317f37f6df63e1ff45ab22cf Mon Sep 17 00:00:00 2001 From: ArthurData Date: Wed, 9 Oct 2019 14:44:41 +0200 Subject: [PATCH 037/211] create golem project without comments #171 --- R/create_golem.R | 15 ++++++++------- man/create_golem.Rd | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/R/create_golem.R b/R/create_golem.R index 39adb9d6..5fecdaae 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -4,7 +4,7 @@ #' used as the package name. #' @param check_name When using this function in the console, you can prevent #' the package name from being checked. -#' @param open booleen open the created project +#' @param open boolean open the created project #' @param package_name package name to use #' @param without_comments boolean start project without golem comments #' @param ... not used @@ -15,7 +15,6 @@ #' @importFrom stringr str_remove_all #' @importFrom rstudioapi isAvailable #' @importFrom rstudioapi openProject -#' @importFrom formatR tidy_dir #' @export create_golem <- function( path, @@ -78,14 +77,16 @@ create_golem <- function( if ( without_comments == TRUE ) { - formatR::tidy_dir( + files <- list.files( path = c( file.path(path, "dev"), file.path(path, "R") - ), - comment = FALSE, - blank = FALSE + ), + full.names = TRUE ) + for ( file in files ) { + remove_comments(file) + } } @@ -104,7 +105,7 @@ create_golem <- function( ) } -# to be used in Rstudio "new project" GUI +# to be used in RStudio "new project" GUI create_golem_gui <- function(path,...){ dots <- list(...) create_golem( diff --git a/man/create_golem.Rd b/man/create_golem.Rd index 7d204a51..865a122a 100644 --- a/man/create_golem.Rd +++ b/man/create_golem.Rd @@ -5,7 +5,7 @@ \title{Create a package for Shiny App using golem} \usage{ create_golem(path, check_name = TRUE, open = TRUE, - package_name = basename(path), ...) + package_name = basename(path), without_comments = FALSE, ...) } \arguments{ \item{path}{Name of the folder to create the package in. This will also be @@ -18,6 +18,8 @@ the package name from being checked.} \item{package_name}{package name to use} +\item{without_comments}{boolean start project without golem comments} + \item{...}{not used} } \description{ From c7e7bdbe6c9373692235a9d417ae4abb0de72e73 Mon Sep 17 00:00:00 2001 From: novica Date: Wed, 9 Oct 2019 19:15:38 +0200 Subject: [PATCH 038/211] Better error on missing name in add_* --- R/add_files.R | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/R/add_files.R b/R/add_files.R index 2d3fd5a4..7636c6ad 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -17,6 +17,9 @@ add_js_file <- function( open = TRUE, dir_create = TRUE ){ + attempt::stop_if(rlang::is_missing(name), + msg = "Name is required") + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) @@ -66,6 +69,9 @@ add_js_handler <- function( open = TRUE, dir_create = TRUE ){ + attempt::stop_if(rlang::is_missing(name), + msg = "Name is required") + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) @@ -127,6 +133,9 @@ add_css_file <- function( open = TRUE, dir_create = TRUE ){ + attempt::stop_if(rlang::is_missing(name), + msg = "Name is required") + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) @@ -177,6 +186,9 @@ add_ui_server_files <- function( dir = "inst/app", dir_create = TRUE ){ + attempt::stop_if(rlang::is_missing(name), + msg = "Name is required") + #browser() old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) From aa566c263a0d331586f77d94bacc296c08c26338 Mon Sep 17 00:00:00 2001 From: novica Date: Wed, 9 Oct 2019 22:06:05 +0200 Subject: [PATCH 039/211] Document that you can use `{golem}` outside of a `{golem}` project --- vignettes/b_dev.Rmd | 1 + 1 file changed, 1 insertion(+) diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index 4b0371e4..30009180 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -94,6 +94,7 @@ golem::add_js_handler("script") golem::add_css_file("custom") ``` +Note: While the general philosophy of {golem} is being based on the idea that you're building a package, these functions can be used outside of a {golem} project. ## Adding these external resources to your app From 289ec9c703014ad007700f5e6286720e07a1a38f Mon Sep 17 00:00:00 2001 From: novica Date: Wed, 9 Oct 2019 23:27:46 +0200 Subject: [PATCH 040/211] Message to link to `golem_add_external_resources()` should be conditional to the package structure --- R/add_files.R | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 2d3fd5a4..98782ad1 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -44,11 +44,13 @@ add_js_file <- function( file.create(where) cat_green_tick(glue::glue("File created at {where}")) + + if (check_file_exist(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' ) - ) + )} if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) @@ -105,11 +107,12 @@ add_js_handler <- function( write_there("});") cat_green_tick(glue::glue("File created at {where}")) + if (check_file_exist(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' ) - ) + )} if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) @@ -155,11 +158,12 @@ add_css_file <- function( file.create(where) cat_green_tick(glue::glue("File created at {where}")) + if (check_file_exist(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' ) - ) + )} if (rstudioapi::isAvailable() & open ){ rstudioapi::navigateToFile(where) From 15a9ee828f2a17603a79d0a0a1f69d05b9a4dcba Mon Sep 17 00:00:00 2001 From: cervangirard Date: Thu, 10 Oct 2019 08:48:35 +0200 Subject: [PATCH 041/211] Add request parameter to bookmarking According to this link https://shiny.rstudio.com/articles/bookmarking-modules.html, we need nothing for modules --- inst/shinyexample/R/app_ui.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 75b064ac..dbdeec97 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,5 +1,5 @@ #' @import shiny -app_ui <- function() { +app_ui <- function(request) { tagList( # Leave this function for adding external resources golem_add_external_resources(), From 99343b3960fd47b41f8528502ee29ff89e202a1b Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Thu, 10 Oct 2019 18:11:37 +0900 Subject: [PATCH 042/211] Change of wording of documentation --- NAMESPACE | 3 +++ R/add_files.R | 4 ---- R/js.R | 6 +++--- man/add_files.Rd | 1 + man/add_module.Rd | 3 ++- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index b9924c3e..3f7541a1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,6 +30,7 @@ export(fill_desc) export(get_dependencies) export(get_golem_options) export(get_golem_wd) +export(insert_ns) export(invoke_js) export(make_dev) export(message_dev) @@ -61,7 +62,9 @@ importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(rlang,enquo) importFrom(roxygen2,roxygenise) +importFrom(rstudioapi,getSourceEditorContext) importFrom(rstudioapi,isAvailable) +importFrom(rstudioapi,modifyRange) importFrom(rstudioapi,openProject) importFrom(shiny,getShinyOption) importFrom(stats,setNames) diff --git a/R/add_files.R b/R/add_files.R index f6560d3e..896accb1 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -186,10 +186,6 @@ add_ui_server_files <- function( dir = "inst/app", dir_create = TRUE ){ - attempt::stop_if(rlang::is_missing(name), - msg = "Name is required") - - #browser() old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) diff --git a/R/js.R b/R/js.R index 97601335..7cd2aac5 100644 --- a/R/js.R +++ b/R/js.R @@ -6,7 +6,7 @@ #' to launch any JS function created inside a Shiny JavaScript handler. #' #' @param fun JS function to be invoked. -#' @param ... atomics or JSON-like vectors to be sent to the triggered JS function +#' @param ... JSON-like messages to be sent to the triggered JS function #' @param session The shiny session within which to call \code{sendCustomMessage}. #' #' @@ -41,8 +41,8 @@ invoke_js <- function( ..., session = shiny::getDefaultReactiveDomain() ){ - if (fun == "") - stop("Error: invoke_js must be called with a valid JS handler name!") + attempt::stop_if(fun == "", + "Error: Empty string is not a valid JS handler name") messages <- list(...) res <- lapply( messages, diff --git a/man/add_files.Rd b/man/add_files.Rd index b6099368..a6daf534 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -32,4 +32,5 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", } \description{ These functions create files inside the \code{inst/app} folder. +These functions can be used outside of a {golem} project. } diff --git a/man/add_module.Rd b/man/add_module.Rd index 63d8434c..a6b8342a 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -22,7 +22,8 @@ add_module(name, pkg = get_golem_wd(), open = TRUE, } \description{ This function creates a module inside the \code{R/} folder, based -on a specific module structure. +on a specific module structure. This function can be used outside +of a {golem} project. } \note{ This function will prefix the \code{name} argument with \code{mod_}. From 4aab92a5ca6720f69f810848466d901722282354 Mon Sep 17 00:00:00 2001 From: novica Date: Thu, 10 Oct 2019 16:16:05 +0200 Subject: [PATCH 043/211] change check_file_exist to file.exists --- R/add_files.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 98782ad1..daf35aa2 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -45,7 +45,7 @@ add_js_file <- function( cat_green_tick(glue::glue("File created at {where}")) - if (check_file_exist(paste0(pkg, "/DESCRIPTION"))) { + if (file.exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' @@ -107,7 +107,8 @@ add_js_handler <- function( write_there("});") cat_green_tick(glue::glue("File created at {where}")) - if (check_file_exist(paste0(pkg, "/DESCRIPTION"))) { + + if (file.exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' @@ -158,7 +159,8 @@ add_css_file <- function( file.create(where) cat_green_tick(glue::glue("File created at {where}")) - if (check_file_exist(paste0(pkg, "/DESCRIPTION"))) { + + if (file.exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' From 97bd1d2d08b622372c37a94aae20e58aeff4cd57 Mon Sep 17 00:00:00 2001 From: novica Date: Thu, 10 Oct 2019 16:25:32 +0200 Subject: [PATCH 044/211] remove lines 189-191 as per @zwycl comment --- R/add_files.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 7636c6ad..43b71693 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -186,8 +186,6 @@ add_ui_server_files <- function( dir = "inst/app", dir_create = TRUE ){ - attempt::stop_if(rlang::is_missing(name), - msg = "Name is required") #browser() old <- setwd(normalizePath(pkg)) From ea0d0d07e7e22e0aec5c33462446259aca9b1c84 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 10 Oct 2019 22:11:10 +0200 Subject: [PATCH 045/211] fixed small typo in attempt usage --- R/js.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/js.R b/R/js.R index 7cd2aac5..68aba7a3 100644 --- a/R/js.R +++ b/R/js.R @@ -41,8 +41,10 @@ invoke_js <- function( ..., session = shiny::getDefaultReactiveDomain() ){ - attempt::stop_if(fun == "", - "Error: Empty string is not a valid JS handler name") + attempt::stop_if( + fun == "", + msg = "Error: Empty string is not a valid JS handler name" + ) messages <- list(...) res <- lapply( messages, From 14a736bb15c0c9c32a039d27af7eb68fc095372b Mon Sep 17 00:00:00 2001 From: Ziwei Ye Date: Fri, 11 Oct 2019 13:42:25 +0900 Subject: [PATCH 046/211] Update golem-js.js --- inst/utils/golem-js.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/inst/utils/golem-js.js b/inst/utils/golem-js.js index c5c0261c..7f890ee8 100644 --- a/inst/utils/golem-js.js +++ b/inst/utils/golem-js.js @@ -43,17 +43,19 @@ $( document ).ready(function() { $(what).removeAttr('disabled'); }); - Shiny.addCustomMessageHandler('alert', function(message) { - alert(message); + Shiny.addCustomMessageHandler('alert', function(args) { + alert(args.message); }); - Shiny.addCustomMessageHandler('prompt', function(message) { - var input = prompt(message); + Shiny.addCustomMessageHandler('prompt', function(args) { + var input = prompt(args.message); + Shiny.setInputValues(args.id, input); return input; }); - Shiny.addCustomMessageHandler('confirm', function(message) { - var input = confirm(message); + Shiny.addCustomMessageHandler('confirm', function(args) { + var input = confirm(args.message); + Shiny.setInputValues(args.id, input); return input; }); From 3591537539ac775466aeba128b9ee3b057f1bef4 Mon Sep 17 00:00:00 2001 From: Jas Sohi Date: Fri, 11 Oct 2019 02:21:41 -0700 Subject: [PATCH 047/211] Use user set get_golem_wd() instead of . to be consistent with rest of package. Resolves https://github.com/ThinkR-open/golem/issues/219 --- R/reload.R | 2 +- man/document_and_reload.Rd | 2 +- man/golem_js.Rd | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/R/reload.R b/R/reload.R index 2664132b..a566a997 100644 --- a/R/reload.R +++ b/R/reload.R @@ -33,7 +33,7 @@ detach_all_attached <- function(){ #' @importFrom pkgload load_all #' @export document_and_reload <- function( - pkg = "." + pkg = get_golem_wd() ){ if (rstudioapi::hasFun("documentSaveAll")) { rstudioapi::documentSaveAll() diff --git a/man/document_and_reload.Rd b/man/document_and_reload.Rd index 198041f0..d6d11d24 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -4,7 +4,7 @@ \alias{document_and_reload} \title{Document and reload your package} \usage{ -document_and_reload(pkg = ".") +document_and_reload(pkg = get_golem_wd()) } \arguments{ \item{pkg}{Path to the root of the package. Default is \code{"."}.} diff --git a/man/golem_js.Rd b/man/golem_js.Rd index bd1fd3ba..f1c87382 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -12,7 +12,7 @@ invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) \arguments{ \item{fun}{JS function to be invoked.} -\item{...}{atomics or JSON-like vectors to be sent to the triggered JS function} +\item{...}{JSON-like messages to be sent to the triggered JS function} \item{session}{The shiny session within which to call \code{sendCustomMessage}. @@ -28,6 +28,9 @@ invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) \item{clickon}{Click on an element. The full jQuery selector has to be used.} \item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} \item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} +\item{alert}{Open an alert box with the message provided.} +\item{prompt}{Open a prompt box with the message provided.} +\item{confirm}{Open a confirm box with the message provided.} }} } \description{ From 4760b8f295fdbd78da58612688370d42be9601fd Mon Sep 17 00:00:00 2001 From: novica Date: Fri, 11 Oct 2019 22:35:01 +0200 Subject: [PATCH 048/211] RStudio Shortcuts for opening files #212 --- R/addins.R | 41 +++++++++++++++++++++++++++++++++++++++++ inst/rstudio/addins.dcf | 19 +++++++++++++++++++ man/go_to.Rd | 16 ++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 R/addins.R create mode 100644 inst/rstudio/addins.dcf create mode 100644 man/go_to.Rd diff --git a/R/addins.R b/R/addins.R new file mode 100644 index 00000000..c485e4ef --- /dev/null +++ b/R/addins.R @@ -0,0 +1,41 @@ +#' {golem} addins +#' +#' Addins to go to common files used in developing a `{golem}` package. +#' +#' @param file Name of the file to be loaded from the `dev` directory. +#' @param wd Working directory of the current golem package. + +go_to <- function( + file, + wd = golem::get_golem_wd() +){ + file <- file.path( + wd, file + ) + if (! file.exists(file)){ + message(file, "not found.") + } + if (rstudioapi::hasFun("navigateToFile") ){ + rstudioapi::navigateToFile( + file + ) + } else { + message("Your version of RStudio does not support `navigateToFile`") + } +} + +go_to_start <- function(){ + go_to("dev/01_start.R") +} + +go_to_dev <- function(){ + go_to("dev/02_dev.R") +} + +go_to_deploy <- function(){ + go_to("dev/03_deploy.R") +} + +go_to_run_dev <- function(){ + go_to("dev/run_dev.R") +} \ No newline at end of file diff --git a/inst/rstudio/addins.dcf b/inst/rstudio/addins.dcf new file mode 100644 index 00000000..5e3089a7 --- /dev/null +++ b/inst/rstudio/addins.dcf @@ -0,0 +1,19 @@ +Name: Open start.R +Description: Opens the 01_start.R file. +Binding: go_to_start +Interactive: false + +Name: Open dev.R +Description: Opens the 02_dev.R file. +Binding: go_to_dev +Interactive: false + +Name: Open deploy.R +Description: Opens the 03_deploy.R file. +Binding: go_to_deploy +Interactive: false + +Name: Open run_dev.R +Description: Opens the run_dev.R file. +Binding: go_to_run_dev +Interactive: false \ No newline at end of file diff --git a/man/go_to.Rd b/man/go_to.Rd new file mode 100644 index 00000000..903fddc1 --- /dev/null +++ b/man/go_to.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addins.R +\name{go_to} +\alias{go_to} +\title{{golem} addins} +\usage{ +go_to(file, wd = golem::get_golem_wd()) +} +\arguments{ +\item{file}{Name of the file to be loaded from the \code{dev} directory.} + +\item{wd}{Working directory of the current golem package.} +} +\description{ +Addins to go to common files used in developing a \code{{golem}} package. +} From 96faf02008bb3b511f8cc9a30472f7c9a59fbdb5 Mon Sep 17 00:00:00 2001 From: cervangirard Date: Sat, 12 Oct 2019 09:11:45 +0200 Subject: [PATCH 049/211] To pass tests and check issue#220 --- R/addin_functions.R | 2 ++ tests/testthat/test-zreload.R | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/R/addin_functions.R b/R/addin_functions.R index f331e326..c0da7ac9 100644 --- a/R/addin_functions.R +++ b/R/addin_functions.R @@ -1,3 +1,5 @@ +#' Shorcut to insert NS +#' #' @importFrom rstudioapi getSourceEditorContext #' @importFrom rstudioapi modifyRange #' diff --git a/tests/testthat/test-zreload.R b/tests/testthat/test-zreload.R index b916e3f5..bb3ad9e7 100644 --- a/tests/testthat/test-zreload.R +++ b/tests/testthat/test-zreload.R @@ -14,7 +14,7 @@ test_that("test document_and_reload",{ cat( "#' @export sum_golem <- function(a,b){a + b}",file ="R/sum.R") - document_and_reload() + document_and_reload(pkg = ".") expect_equal(sum_golem(1,2),3) }) }) From 42b6f601058406d8f2bb1e0c6b99a16836f0a05e Mon Sep 17 00:00:00 2001 From: cervangirard Date: Sat, 12 Oct 2019 09:18:31 +0200 Subject: [PATCH 050/211] Add function add_resource_path * if directory empty, put message in your console when you'r open your app --- NAMESPACE | 3 +++ R/add_ressource_path.R | 20 ++++++++++++++++++++ inst/shinyexample/R/app_ui.R | 2 +- man/add_resource_path.Rd | 16 ++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 R/add_ressource_path.R create mode 100644 man/add_resource_path.Rd diff --git a/NAMESPACE b/NAMESPACE index f2197d0e..c4d32969 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,6 +9,7 @@ export(add_fct) export(add_js_file) export(add_js_handler) export(add_module) +export(add_resource_path) export(add_rstudioconnect_file) export(add_shinyappsio_file) export(add_shinyserver_file) @@ -59,11 +60,13 @@ importFrom(htmltools,tags) importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(rlang,enquo) +importFrom(rlang,is_empty) importFrom(roxygen2,roxygenise) importFrom(rstudioapi,getSourceEditorContext) importFrom(rstudioapi,isAvailable) importFrom(rstudioapi,modifyRange) importFrom(rstudioapi,openProject) +importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) importFrom(stats,setNames) importFrom(testthat,expect) diff --git a/R/add_ressource_path.R b/R/add_ressource_path.R new file mode 100644 index 00000000..5f29d78b --- /dev/null +++ b/R/add_ressource_path.R @@ -0,0 +1,20 @@ +#' Add resource path +#' +#' @param prefix The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory. +#' @param directory_path The directory that contains the static resources to be served. +#' +#' @importFrom rlang is_empty +#' @importFrom shiny addResourcePath +#' +#' @export +#' +add_resource_path <- function(prefix, directory_path){ + + list_f <- is_empty(list.files(path = directory_path)) + + if(list_f){ + message("Unable to add your directory because it is empty") + }else{ + addResourcePath(prefix, directory_path) + } +} \ No newline at end of file diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index a4c83795..3765130e 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -13,7 +13,7 @@ app_ui <- function(request) { #' @import shiny golem_add_external_resources <- function(){ - addResourcePath( + golem::add_resource_path( 'www', system.file('app/www', package = 'shinyexample') ) diff --git a/man/add_resource_path.Rd b/man/add_resource_path.Rd new file mode 100644 index 00000000..3974c504 --- /dev/null +++ b/man/add_resource_path.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_ressource_path.R +\name{add_resource_path} +\alias{add_resource_path} +\title{Add resource path} +\usage{ +add_resource_path(prefix, directory_path) +} +\arguments{ +\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory.} + +\item{directory_path}{The directory that contains the static resources to be served.} +} +\description{ +Add resource path +} From 611224b593c4bb1437631ddaa8aae3be5433dfeb Mon Sep 17 00:00:00 2001 From: novica Date: Sat, 12 Oct 2019 22:30:54 +0200 Subject: [PATCH 051/211] Add more info on package creation #63 --- R/create_golem.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/create_golem.R b/R/create_golem.R index 50c0e413..464f5d94 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -71,7 +71,8 @@ create_golem <- function( }, silent=TRUE) } - cat_rule("Created") + cat_rule(paste0("A new golem package ", package_name, " was created.")) + cat_rule("To continue work on your package start editing the 01_start.R file") if ( open & rstudioapi::isAvailable() ) { From 8f095094de5924cbe731bb27cb9218dcfabfd2e1 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 13 Oct 2019 11:23:49 +0200 Subject: [PATCH 052/211] from miniCRAN::pkgDep to remotes::packages_deps --- R/get_sysreqs.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 3e3bae9a..b000ce31 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -5,11 +5,12 @@ #' @param quiet boolean if TRUE the function is quiet #' @importFrom utils download.file #' @importFrom jsonlite fromJSON -#' @importFrom miniCRAN pkgDep +#' @importFrom remotes package_deps get_sysreqs <- function(packages, quiet = TRUE){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") - all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages, suggests = FALSE)))), collapse = ",") + # all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages, suggests = FALSE)))), collapse = ",") + all_deps <- paste(unique(c(packages, unlist(remotes::package_deps(packages)))), collapse = ",") url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) From 19e1a0a45c3e69a26e7fa9817d8df40d3a202650 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 13 Oct 2019 12:16:07 +0200 Subject: [PATCH 053/211] WIP --- DESCRIPTION | 153 ++++++++++++++++++++--------------------- NAMESPACE | 3 +- R/add_deploy_helpers.R | 47 ++++++++++++- R/get_sysreqs.R | 2 +- 4 files changed, 123 insertions(+), 82 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3a7619b0..a9556b90 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,77 +1,76 @@ -Package: golem -Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9001 -Authors@R: - c(person(given = "Vincent", - family = "Guyader", - role = c("cre", "aut"), - email = "vincent@thinkr.fr", - comment = c(ORCID = "0000-0003-0671-9270")), - person(given = "Colin", - family = "Fay", - role = "aut", - email = "contact@colinfay.me", - comment = c(ORCID = "0000-0001-7343-1846")), - 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 = "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. -License: MIT + file LICENSE -URL: https://github.com/ThinkR-open/golem -BugReports: https://github.com/ThinkR-open/golem/issues -Depends: - R (>= 3.0) -Imports: - attempt (>= 0.3.0), - cli, - crayon, - desc, - dockerfiler, - DT, - glue, - htmltools, - pkgload, - processx, - remotes, - rlang, - roxygen2, - rsconnect, - rstudioapi, - shiny, - stats, - stringr, - testthat, - tools, - usethis, - utils, - yesno, - jsonlite, - miniCRAN -Suggests: - spelling, - covr, - knitr, - pkgdown, - purrr, - rcmdcheck, - rmarkdown, - withr -VignetteBuilder: - knitr -Encoding: UTF-8 -LazyData: true -Roxygen: list(markdown = TRUE) -RoxygenNote: 6.1.1 -Language: en-US +Package: golem +Title: A Framework for Robust Shiny Applications +Version: 0.1.0.9001 +Authors@R: + c(person(given = "Vincent", + family = "Guyader", + role = c("cre", "aut"), + email = "vincent@thinkr.fr", + comment = c(ORCID = "0000-0003-0671-9270")), + person(given = "Colin", + family = "Fay", + role = "aut", + email = "contact@colinfay.me", + comment = c(ORCID = "0000-0001-7343-1846")), + 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 = "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. +License: MIT + file LICENSE +URL: https://github.com/ThinkR-open/golem +BugReports: https://github.com/ThinkR-open/golem/issues +Depends: + R (>= 3.0) +Imports: + attempt (>= 0.3.0), + cli, + crayon, + desc, + dockerfiler, + DT, + glue, + htmltools, + pkgload, + processx, + remotes, + rlang, + roxygen2, + rsconnect, + rstudioapi, + shiny, + stats, + stringr, + testthat, + tools, + usethis, + utils, + yesno, + jsonlite +Suggests: + spelling, + covr, + knitr, + pkgdown, + purrr, + rcmdcheck, + rmarkdown, + withr +VignetteBuilder: + knitr +Encoding: UTF-8 +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 6.1.1 +Language: en-US diff --git a/NAMESPACE b/NAMESPACE index badc3aa2..766afe1c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -58,9 +58,10 @@ importFrom(htmltools,includeScript) importFrom(htmltools,save_html) importFrom(htmltools,tags) importFrom(jsonlite,fromJSON) -importFrom(miniCRAN,pkgDep) importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) +importFrom(remotes,dev_package_deps) +importFrom(remotes,package_deps) importFrom(rlang,enquo) importFrom(roxygen2,roxygenise) importFrom(rstudioapi,isAvailable) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index f4ee2af2..44af4bbf 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -287,6 +287,8 @@ alert_build <- function(path, output){ # From {dockerfiler}, in wait for the version to be on CRAN #' @importFrom utils installed.packages packageVersion +#' @importFrom remotes dev_package_deps +#' @importFrom desc desc_get_deps #' dock_from_desc <- function( path = "DESCRIPTION", @@ -309,6 +311,8 @@ dock_from_desc <- function( "tcltk", "tools", "utils")] # remove base and recommended + + if (sysreqs){ # please wait during system requirement calculation cat_bullet("Please wait during system requirements calculation...",bullet = "info",bullet_col = "green") # TODO animated version ? @@ -319,8 +323,17 @@ dock_from_desc <- function( system_requirement <- NULL } + remotes_deps <- remotes::package_deps(packages) + packages_on_cran <- remotes_deps$package[remotes_deps$is_cran]%>% + intersect(packages) + + packages_not_on_cran <- packages %>% + setdiff(packages_on_cran) + + + packages_on_cran <- setNames(lapply(packages_on_cran, packageVersion), packages_on_cran) + - pkg <- setNames(lapply(packages, packageVersion), packages) dock <- dockerfiler::Dockerfile$new(FROM = FROM) if (length(system_requirement)>0){ @@ -335,7 +348,7 @@ dock_from_desc <- function( dock$RUN("R -e 'remotes::install_github(\"r-lib/remotes\", ref = \"97bbf81\")'") - + if ( length(packages_on_cran>0)){ ping <- mapply(function(dock, ver, nm){ res <- dock$RUN( sprintf( @@ -343,7 +356,35 @@ dock_from_desc <- function( nm, ver ) ) - }, ver = pkg, nm = names(pkg), MoreArgs = list(dock = dock)) + }, ver = packages_on_cran, nm = names(packages_on_cran), MoreArgs = list(dock = dock)) + } + + if ( length(packages_not_on_cran>0)){ + + + # prepare the install_github + nn <- remotes_deps %>% + filter(!is_cran) %>% + pull(remote)%>% + map_df(~.x[c('repo','username','sha')]) %>% + mutate(remote = glue::glue("{username}/{repo}@{sha}")) %>% + pull(remote) + + + pong <- mapply(function(dock, ver, nm){ + res <- dock$RUN( + sprintf( + "Rscript -e 'remotes::install_github(\"%s\")'", + ver + ) + ) + }, ver = nn, MoreArgs = list(dock = dock)) + + + } + + + dock diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index b000ce31..721b3854 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -10,7 +10,7 @@ get_sysreqs <- function(packages, quiet = TRUE){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") # all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages, suggests = FALSE)))), collapse = ",") - all_deps <- paste(unique(c(packages, unlist(remotes::package_deps(packages)))), collapse = ",") + all_deps <- paste(unique(c(packages, unlist(remotes::package_deps(packages)$package))), collapse = ",") url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) From c7d3ab01c1ada52de2a9a6d097bfe906b21c6d1c Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 13 Oct 2019 21:01:41 +0200 Subject: [PATCH 054/211] add %>% depends --- NAMESPACE | 1 + R/add_deploy_helpers.R | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 766afe1c..adcf95d2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -58,6 +58,7 @@ importFrom(htmltools,includeScript) importFrom(htmltools,save_html) importFrom(htmltools,tags) importFrom(jsonlite,fromJSON) +importFrom(magrittr,"%>%") importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(remotes,dev_package_deps) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 44af4bbf..69ab6515 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -289,6 +289,7 @@ alert_build <- function(path, output){ #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps +#' @importFrom magrittr %>% #' dock_from_desc <- function( path = "DESCRIPTION", @@ -324,7 +325,7 @@ dock_from_desc <- function( } remotes_deps <- remotes::package_deps(packages) - packages_on_cran <- remotes_deps$package[remotes_deps$is_cran]%>% + packages_on_cran <- remotes_deps$package[remotes_deps$is_cran] %>% intersect(packages) packages_not_on_cran <- packages %>% @@ -361,15 +362,18 @@ dock_from_desc <- function( if ( length(packages_not_on_cran>0)){ - # prepare the install_github - nn <- remotes_deps %>% - filter(!is_cran) %>% - pull(remote)%>% - map_df(~.x[c('repo','username','sha')]) %>% - mutate(remote = glue::glue("{username}/{repo}@{sha}")) %>% - pull(remote) + # + nn<- lapply( + remotes_deps$remote[!remotes_deps$is_cran], + function(.){ .[c('repo','username','sha')] + }) %>% do.call(rbind,.) %>% as.data.frame() + # nn <- remotes_deps$remote[!remotes_deps$is_cran]%>% + # map_df(~.x[c('repo','username','sha')]) %>% + # mutate(remote = glue::glue("{username}/{repo}@{sha}")) %>% + # pull(remote) + nn<- glue::glue("{nn$username}/{nn$repo}@{nn$sha}") pong <- mapply(function(dock, ver, nm){ res <- dock$RUN( From cc7f24ff469bb43258c7ca83bb34c164013aea70 Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 13 Oct 2019 22:02:59 +0200 Subject: [PATCH 055/211] Add more info on package creation #63 --- R/create_golem.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/create_golem.R b/R/create_golem.R index 464f5d94..c4cd46b0 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -10,6 +10,7 @@ #' #' @importFrom yesno yesno #' @importFrom cli cat_rule +#' @importFrom cli cat_line #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable #' @importFrom rstudioapi openProject @@ -71,9 +72,9 @@ create_golem <- function( }, silent=TRUE) } - cat_rule(paste0("A new golem package ", package_name, " was created.")) - cat_rule("To continue work on your package start editing the 01_start.R file") - + cat_line(paste0("A new golem package ", package_name, " was created in ", get_golem_wd(), "/", package_name, + " directory.\n", + "To continue work on your package start editing the 01_start.R file")) if ( open & rstudioapi::isAvailable() ) { rstudioapi::openProject(path = path) From ee33beed4a11d6c3a010ad3e2cef8f773d4b3a10 Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 13 Oct 2019 22:34:31 +0200 Subject: [PATCH 056/211] RStudio Shortcuts for opening files #212 --- R/addins.R | 12 ++++++++++++ inst/rstudio/addins.dcf | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/R/addins.R b/R/addins.R index c485e4ef..0eb845e8 100644 --- a/R/addins.R +++ b/R/addins.R @@ -38,4 +38,16 @@ go_to_deploy <- function(){ go_to_run_dev <- function(){ go_to("dev/run_dev.R") +} + +go_to_app_ui <- function(){ + go_to("R/app_ui.R") +} + +go_to_app_server <- function(){ + go_to("R/app_server.R") +} + +go_to_run_app <- function(){ + go_to("R/run_app.R") } \ No newline at end of file diff --git a/inst/rstudio/addins.dcf b/inst/rstudio/addins.dcf index 5e3089a7..7a0d1976 100644 --- a/inst/rstudio/addins.dcf +++ b/inst/rstudio/addins.dcf @@ -16,4 +16,19 @@ Interactive: false Name: Open run_dev.R Description: Opens the run_dev.R file. Binding: go_to_run_dev +Interactive: false + +Name: Open app_ui.R +Description: Opens the app_ui.R file. +Binding: go_to_app_ui +Interactive: false + +Name: Open app_server.R +Description: Opens the app_server.R file. +Binding: go_to_app_server +Interactive: false + +Name: Open run_app.R +Description: Opens the run_app.R file. +Binding: go_to_run_app Interactive: false \ No newline at end of file From 2a0010bbe930d19f444e793f4ab16d88d4a50ddb Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 13 Oct 2019 23:04:02 +0200 Subject: [PATCH 057/211] add globalVariables(.") --- DESCRIPTION | 153 ++++++++++++++++++++++++------------------------ NAMESPACE | 1 + R/get_sysreqs.R | 1 + 3 files changed, 79 insertions(+), 76 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a9556b90..9d743917 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,76 +1,77 @@ -Package: golem -Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9001 -Authors@R: - c(person(given = "Vincent", - family = "Guyader", - role = c("cre", "aut"), - email = "vincent@thinkr.fr", - comment = c(ORCID = "0000-0003-0671-9270")), - person(given = "Colin", - family = "Fay", - role = "aut", - email = "contact@colinfay.me", - comment = c(ORCID = "0000-0001-7343-1846")), - 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 = "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. -License: MIT + file LICENSE -URL: https://github.com/ThinkR-open/golem -BugReports: https://github.com/ThinkR-open/golem/issues -Depends: - R (>= 3.0) -Imports: - attempt (>= 0.3.0), - cli, - crayon, - desc, - dockerfiler, - DT, - glue, - htmltools, - pkgload, - processx, - remotes, - rlang, - roxygen2, - rsconnect, - rstudioapi, - shiny, - stats, - stringr, - testthat, - tools, - usethis, - utils, - yesno, - jsonlite -Suggests: - spelling, - covr, - knitr, - pkgdown, - purrr, - rcmdcheck, - rmarkdown, - withr -VignetteBuilder: - knitr -Encoding: UTF-8 -LazyData: true -Roxygen: list(markdown = TRUE) -RoxygenNote: 6.1.1 -Language: en-US +Package: golem +Title: A Framework for Robust Shiny Applications +Version: 0.1.0.9001 +Authors@R: + c(person(given = "Vincent", + family = "Guyader", + role = c("cre", "aut"), + email = "vincent@thinkr.fr", + comment = c(ORCID = "0000-0003-0671-9270")), + person(given = "Colin", + family = "Fay", + role = "aut", + email = "contact@colinfay.me", + comment = c(ORCID = "0000-0001-7343-1846")), + 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 = "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. +License: MIT + file LICENSE +URL: https://github.com/ThinkR-open/golem +BugReports: https://github.com/ThinkR-open/golem/issues +Depends: + R (>= 3.0) +Imports: + attempt (>= 0.3.0), + cli, + crayon, + desc, + dockerfiler, + DT, + glue, + htmltools, + pkgload, + processx, + remotes, + rlang, + roxygen2, + rsconnect, + rstudioapi, + shiny, + stats, + stringr, + testthat, + tools, + usethis, + utils, + yesno, + jsonlite, + magrittr +Suggests: + spelling, + covr, + knitr, + pkgdown, + purrr, + rcmdcheck, + rmarkdown, + withr +VignetteBuilder: + knitr +Encoding: UTF-8 +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 6.1.1 +Language: en-US diff --git a/NAMESPACE b/NAMESPACE index adcf95d2..6c2cfc7e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,6 +30,7 @@ export(fill_desc) export(get_dependencies) export(get_golem_options) export(get_golem_wd) +export(get_sysreqs) export(invoke_js) export(make_dev) export(message_dev) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 721b3854..1549d19d 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -6,6 +6,7 @@ #' @importFrom utils download.file #' @importFrom jsonlite fromJSON #' @importFrom remotes package_deps +#' @export get_sysreqs <- function(packages, quiet = TRUE){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") From 0ae1468f96e03ee3dede8323404cc24966186407 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 13 Oct 2019 23:17:39 +0200 Subject: [PATCH 058/211] add expand parameteres to have 1 sysreq per row --- R/add_deploy_helpers.R | 41 ++++++++++++++++++++++++++++++++--------- man/dockerfiles.Rd | 12 ++++++++---- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 69ab6515..85e37ebe 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -118,8 +118,9 @@ add_shinyserver_file <- function( #' Default is 80. #' @param host The `options('shiny.host')` on which to run the Shiny App. #' Default is 0.0.0.0. -#' @param sysreqs boolean to check the System Requirements +#' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories +#' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line #' @export #' @rdname dockerfiles #' @importFrom desc desc_get_deps @@ -153,7 +154,8 @@ add_dockerfile <- function( port = 80, host = "0.0.0.0", sysreqs = TRUE, - repos = "https://cran.rstudio.com/" + repos = "https://cran.rstudio.com/", + expand = FALSE # , function_to_launch = "run_app" ) { @@ -165,7 +167,7 @@ add_dockerfile <- function( - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos,expand = expand) dock$EXPOSE(port) dock$CMD( glue::glue( @@ -190,14 +192,16 @@ add_dockerfile_shinyproxy <- function( ), as = NULL, sysreqs = TRUE, - repos = "https://cran.rstudio.com/" + repos = "https://cran.rstudio.com/", + expand = FALSE ){ where <- file.path(pkg, output) if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = path, FROM = from, AS = as, + sysreqs = sysreqs, repos = repos, expand = expand) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -226,7 +230,8 @@ add_dockerfile_heroku <- function( ), as = NULL, sysreqs = TRUE, - repos = "https://cran.rstudio.com/" + repos = "https://cran.rstudio.com/", + expand = FALSE ){ where <- file.path(pkg, output) @@ -234,7 +239,7 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, expand = expand) dock$CMD( glue::glue( @@ -286,6 +291,14 @@ alert_build <- function(path, output){ } # From {dockerfiler}, in wait for the version to be on CRAN +#' @param path +#' +#' @param FROM +#' @param AS +#' @param sysreqs +#' @param repos +#' @param expand +#' #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps @@ -296,7 +309,8 @@ dock_from_desc <- function( FROM = "rocker/r-ver", AS = NULL, sysreqs = TRUE, - repos = "https://cran.rstudio.com/" + repos = "https://cran.rstudio.com/", + expand = FALSE ){ @@ -338,8 +352,17 @@ dock_from_desc <- function( dock <- dockerfiler::Dockerfile$new(FROM = FROM) if (length(system_requirement)>0){ + + if ( !expand){ dock$RUN(paste("apt-get update && apt-get install -y ",paste(system_requirement,collapse = " "))) - } + } else{ + dock$RUN("apt-get update" ) + for ( sr in system_requirement){ + dock$RUN(paste("apt-get install -y ",sr)) + } + } + + } dock$RUN( sprintf("echo \"options(repos = c(CRAN = '%s'), download.file.method = 'libcurl')\" >> /usr/local/lib/R/etc/Rprofile.site",repos)) diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index e2a4e99c..643d4921 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -10,17 +10,19 @@ add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", sysreqs = TRUE, - repos = "https://cran.rstudio.com/") + repos = "https://cran.rstudio.com/", expand = FALSE) add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", R.Version()$major, ".", R.Version()$minor), as = NULL, - sysreqs = TRUE, repos = "https://cran.rstudio.com/") + sysreqs = TRUE, repos = "https://cran.rstudio.com/", + expand = FALSE) add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", R.Version()$major, ".", R.Version()$minor), as = NULL, - sysreqs = TRUE, repos = "https://cran.rstudio.com/") + sysreqs = TRUE, repos = "https://cran.rstudio.com/", + expand = FALSE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -40,9 +42,11 @@ Default is 80.} \item{host}{The \code{options('shiny.host')} on which to run the Shiny App. Default is 0.0.0.0.} -\item{sysreqs}{boolean to check the System Requirements} +\item{sysreqs}{boolean to check the system requirements} \item{repos}{character vector, the base URL of the repositories} + +\item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} } \description{ Build a container containing your Shiny App. \code{add_dockerfile()} creates From 250b26c81904efc8e86ee4d6d3425db7565d53e9 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 13 Oct 2019 23:20:37 +0200 Subject: [PATCH 059/211] fix #190 --- R/zzz.R | 1 + 1 file changed, 1 insertion(+) create mode 100644 R/zzz.R diff --git a/R/zzz.R b/R/zzz.R new file mode 100644 index 00000000..15e1e28d --- /dev/null +++ b/R/zzz.R @@ -0,0 +1 @@ +globalVariables(".") \ No newline at end of file From dd7bbcc67e136fc8e6a488f38a99622e5b61a2a1 Mon Sep 17 00:00:00 2001 From: cervangirard Date: Mon, 14 Oct 2019 13:01:29 +0200 Subject: [PATCH 060/211] Add warn_empty parameter --- R/add_ressource_path.R | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/R/add_ressource_path.R b/R/add_ressource_path.R index 5f29d78b..7f8365aa 100644 --- a/R/add_ressource_path.R +++ b/R/add_ressource_path.R @@ -2,17 +2,20 @@ #' #' @param prefix The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory. #' @param directory_path The directory that contains the static resources to be served. +#' @param warn_empty Boolean. By default FALSE, if TRUE diplay message if directory empty #' #' @importFrom rlang is_empty #' @importFrom shiny addResourcePath #' #' @export #' -add_resource_path <- function(prefix, directory_path){ +add_resource_path <- function(prefix, + directory_path, + warn_empty = FALSE){ list_f <- is_empty(list.files(path = directory_path)) - if(list_f){ + if(list_f & warn_empty ){ message("Unable to add your directory because it is empty") }else{ addResourcePath(prefix, directory_path) From d6aed6414f27f1b71791e5fe7062ffebd598f94e Mon Sep 17 00:00:00 2001 From: novica Date: Tue, 15 Oct 2019 19:39:25 +0200 Subject: [PATCH 061/211] update description --- DESCRIPTION | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index 46c76f8b..7cf86eae 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,6 +22,10 @@ Authors@R: 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 = "ThinkR", role = "cph")) Description: An opinionated framework for building a From 9c602827c728d169a491b12b1514912fe665740c Mon Sep 17 00:00:00 2001 From: fmmattioni Date: Wed, 16 Oct 2019 11:46:50 +0200 Subject: [PATCH 062/211] added note on favicon extension when deplyoing in shinyproxy --- vignettes/a_start.Rmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 7a424f45..f8850fc4 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -193,6 +193,8 @@ 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**: 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. + + Utils These two functions add a file with various functions that can be used along the process of building your app. From 0619469eac73bbf00a1c16fba8705c423dfbc8c8 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Wed, 16 Oct 2019 21:43:25 +0200 Subject: [PATCH 063/211] Update add_ressource_path.R --- R/add_ressource_path.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/add_ressource_path.R b/R/add_ressource_path.R index 7f8365aa..835b0e2b 100644 --- a/R/add_ressource_path.R +++ b/R/add_ressource_path.R @@ -15,9 +15,9 @@ add_resource_path <- function(prefix, list_f <- is_empty(list.files(path = directory_path)) - if(list_f & warn_empty ){ + if( list_f & warn_empty ){ message("Unable to add your directory because it is empty") - }else{ + } else { addResourcePath(prefix, directory_path) } -} \ No newline at end of file +} From 0acbfe715eb59ca637458f7ee7b5f228edd14274 Mon Sep 17 00:00:00 2001 From: Colin Fay Date: Wed, 16 Oct 2019 21:45:29 +0200 Subject: [PATCH 064/211] Update addins.dcf --- inst/rstudio/addins.dcf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/inst/rstudio/addins.dcf b/inst/rstudio/addins.dcf index d41a5311..25023e4b 100644 --- a/inst/rstudio/addins.dcf +++ b/inst/rstudio/addins.dcf @@ -35,5 +35,4 @@ Binding: go_to_run_app Name: Put selected text into between ns() Description: add 'ns(' in front of selected text, and add ')' behind. Binding: insert_ns - -Interactive: false \ No newline at end of file +Interactive: false From 84e8c707ce291a9b3f5d2705e7824ca83e2aa024 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 16 Oct 2019 22:10:22 +0200 Subject: [PATCH 065/211] NEWS update --- NEWS.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2df98188..d792511a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,19 +1,39 @@ +> Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. + # golem 0.1.9000+ ## New functions + `add_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). ++ We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). + ++ New JavaScript functions to use alert, prompt and confirm (#108, @zwycl) + ++ `add_external_js_file` and `add_external_css_file` are designed to download .js and .css file off the web to the appropriate directory (#130, @zwycl) + ## New features -+ `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) + ++ 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) + `add_module` now allows to create and `fct_` and an `utils_` file (#154, @novica) + `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) + ++ 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) + ## Breaking changes ++ `invoke_js()` now takes a list of elements to send to JS (through `...`) instead of a vector (#155, @zwycl) + ## Bug fix + The Dockerfile is now correctly added to .Rbuildignore (#81) @@ -24,8 +44,14 @@ + spellcheck in files (@privefl) ++ 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) + ## Internal changes ++ We no longer depend on `{stringr}` (#201, @TomerPacific) + # golem 0.1.0 - CRAN release candidate, v2 ## New Functions From 93ef64d18152a0f30cfdba1a3d4047c9eea5eb60 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 16 Oct 2019 23:14:25 +0200 Subject: [PATCH 066/211] fixed tests --- tests/testthat/helper-config.R | 5 ++- tests/testthat/test-add_dockerfile.R | 6 +-- tests/testthat/test-add_function.R | 40 +++++++------------ tests/testthat/test-add_module.R | 6 ++- tests/testthat/test-add_rstudioconnect_file.R | 6 +-- tests/testthat/test-use_recomended.R | 10 ++--- 6 files changed, 33 insertions(+), 40 deletions(-) diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index d3605349..818b2d88 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -8,6 +8,7 @@ remove_file <- function(path){ ## fake package fakename <- paste0(sample(letters, 10, TRUE), collapse = "") +#unlink(list.files(tempdir()), recursive = TRUE) tpdir <- tempdir() if(!dir.exists(file.path(tpdir,fakename))){ create_golem(file.path(tpdir, fakename), open = FALSE) @@ -21,4 +22,6 @@ dir.create(file.path(pkg, fp), recursive = TRUE) rand_name <- function(){ paste0(sample(letters, 10, TRUE), collapse = "") -} \ No newline at end of file +} + +set_golem_wd(pkg) diff --git a/tests/testthat/test-add_dockerfile.R b/tests/testthat/test-add_dockerfile.R index e38490b6..41e385b3 100644 --- a/tests/testthat/test-add_dockerfile.R +++ b/tests/testthat/test-add_dockerfile.R @@ -3,7 +3,7 @@ context("function add_dockerfile") test_that("add_dockerfile", { with_dir(pkg, { remove_file("Dockerfile") - output <- testthat::capture_output(add_dockerfile()) + output <- testthat::capture_output(add_dockerfile(pkg = pkg)) expect_true(file.exists("Dockerfile")) test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") @@ -16,7 +16,7 @@ test_that("add_dockerfile", { test_that("add_dockerfile_heroku", { with_dir(pkg, { remove_file("Dockerfile") - output <- testthat::capture_output(add_dockerfile_heroku()) + output <- testthat::capture_output(add_dockerfile_heroku(pkg = pkg)) expect_true(file.exists("Dockerfile")) test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") @@ -28,7 +28,7 @@ test_that("add_dockerfile_heroku", { test_that("add_dockerfile_shinyproxy", { with_dir(pkg, { remove_file("Dockerfile") - output <- testthat::capture_output(add_dockerfile_shinyproxy()) + output <- testthat::capture_output(add_dockerfile_shinyproxy(pkg = pkg)) expect_true(file.exists("Dockerfile")) test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index cd455d69..a205c33c 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -1,30 +1,32 @@ context("test-add_file function") test_that("add_css_file", { + with_dir(pkg, { remove_file("inst/app/www/style.css") - add_css_file("style", open = FALSE) + add_css_file("style", pkg = pkg, open = FALSE) expect_true(file.exists("inst/app/www/style.css")) - add_css_file("style", open = FALSE, dir = normalizePath(fp)) + add_css_file("stylebis", pkg = pkg, open = FALSE, dir = normalizePath(fp)) expect_true( file.exists( - file.path(fp, "style.css") + file.path(fp, "stylebis.css") ) ) style <-list.files("inst/app/www/", pattern = "style") expect_equal(tools::file_ext(style), "css") remove_file("inst/app/www/style.css") + remove_file("inst/app/www/stylebis.css") }) }) test_that("add_js_file", { with_dir(pkg, { remove_file("inst/app/www/script.js") - add_js_file("script", open = FALSE) + add_js_file("script", pkg = pkg, open = FALSE) expect_true(file.exists("inst/app/www/script.js")) - add_js_file("script", open = FALSE, dir = normalizePath(fp)) + add_js_file("script", pkg = pkg, open = FALSE, dir = normalizePath(fp)) expect_true( file.exists( file.path(fp, "script.js") @@ -41,10 +43,10 @@ test_that("add_js_file", { test_that("add_js_handler", { with_dir(pkg, { remove_file("inst/app/www/handler.js") - add_js_handler("handler", open = FALSE) + add_js_handler("handler", pkg = pkg, open = FALSE) expect_true(file.exists("inst/app/www/handler.js")) - add_js_handler("handler", open = FALSE, dir = normalizePath(fp)) + add_js_handler("handler",pkg = pkg, open = FALSE, dir = normalizePath(fp)) expect_true( file.exists( file.path(fp, "handler.js") @@ -62,21 +64,9 @@ test_that("add_ui_server_files", { with_dir(pkg, { remove_file("inst/app/ui.R") remove_file("inst/app/server.R") - add_ui_server_files() + add_ui_server_files(pkg = pkg) expect_true(file.exists("inst/app/ui.R")) expect_true(file.exists("inst/app/server.R")) - - add_ui_server_files(dir = normalizePath(fp)) - expect_true( - file.exists( - file.path(fp, "ui.R") - ) - ) - expect_true( - file.exists( - file.path(fp, "server.R") - ) - ) script <- list.files("inst/app/", pattern = "ui") expect_equal(tools::file_ext(script), "R") script <- list.files("inst/app/", pattern = "server") @@ -94,15 +84,15 @@ test_that("add_fct and add_utils", { remove_file(util_file) remove_file(fct_file) remove_file(mod) - add_fct("ui", open = FALSE) - add_utils("ui", open = FALSE) + add_fct("ui", pkg = pkg, open = FALSE) + add_utils("ui", pkg = pkg, open = FALSE) expect_true(file.exists(util_file)) expect_true(file.exists(fct_file)) rand <- rand_name() - add_module(rand) - add_fct("ui", rand, open = FALSE) - add_utils("ui", rand, open = FALSE) + add_module(rand, pkg = pkg, open = FALSE) + add_fct("ui", rand, pkg = pkg, open = FALSE) + add_utils("ui", rand, pkg = pkg, open = FALSE) expect_true(file.exists(sprintf("R/mod_%s_fct_ui.R", rand))) expect_true(file.exists(sprintf("R/mod_%s_utils_ui.R", rand))) diff --git a/tests/testthat/test-add_module.R b/tests/testthat/test-add_module.R index 26f95477..d3944b8d 100644 --- a/tests/testthat/test-add_module.R +++ b/tests/testthat/test-add_module.R @@ -4,7 +4,11 @@ context("function add_module") test_that("add_module", { with_dir(pkg, { remove_file("R/mod_test.R") - add_module("test", open = FALSE, fct = "ftest", utils = "utest") + withr::with_options( + c("golem.wd" = pkg), { + add_module("test", open = FALSE, pkg = pkg, fct = "ftest", utils = "utest") + } + ) expect_true(file.exists("R/mod_test.R")) expect_true(file.exists("R/mod_test_fct_ftest.R")) expect_true(file.exists("R/mod_test_utils_utest.R")) diff --git a/tests/testthat/test-add_rstudioconnect_file.R b/tests/testthat/test-add_rstudioconnect_file.R index 3f82e57c..3c69dfaa 100644 --- a/tests/testthat/test-add_rstudioconnect_file.R +++ b/tests/testthat/test-add_rstudioconnect_file.R @@ -3,7 +3,7 @@ context("function rstudio products") test_that("add_rstudioconnect_file", { with_dir(pkg, { remove_file("app.R") - output <- testthat::capture_output(add_rstudioconnect_file(open = FALSE)) + output <- testthat::capture_output(add_rstudioconnect_file(pkg = pkg, open = FALSE)) expect_true(file.exists("app.R")) test <- stringr::str_detect(output, "ile created at .*/app.R") @@ -12,7 +12,7 @@ test_that("add_rstudioconnect_file", { }) with_dir(pkg, { remove_file("app.R") - output <- testthat::capture_output(add_shinyappsio_file(open = FALSE)) + output <- testthat::capture_output(add_shinyappsio_file(pkg = pkg,open = FALSE)) expect_true(file.exists("app.R")) test <- stringr::str_detect(output, "ile created at .*/app.R") @@ -21,7 +21,7 @@ test_that("add_rstudioconnect_file", { }) with_dir(pkg, { remove_file("app.R") - output <- testthat::capture_output(add_shinyserver_file(open = FALSE)) + output <- testthat::capture_output(add_shinyserver_file(pkg = pkg,open = FALSE)) expect_true(file.exists("app.R")) test <- stringr::str_detect(output, "ile created at .*/app.R") diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 59cd1303..7e838fa0 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -2,14 +2,11 @@ context("tests use_recomended functions") test_that("test use_recommended_deps",{ with_dir(pkg,{ - #browser() - #browseURL(pkg) - use_recommended_deps() + use_recommended_deps(pkg = pkg) packages <- c('shiny', 'DT', 'attempt', 'glue', 'golem', 'htmltools') desc <- readLines("DESCRIPTION") - #browseURL("DESCRIPTION") start <- grep("Imports:", desc) + 1 - desc <- desc[start:length(desc)] + desc <- desc[start:length(desc)] test <- all(purrr::map_lgl(packages,function(x){any(grepl(x,desc))})) expect_true(test) }) @@ -18,8 +15,7 @@ test_that("test use_recommended_deps",{ test_that("test use_recommended_tests",{ with_dir(pkg,{ - #browser() - use_recommended_tests() + use_recommended_tests(pkg = pkg) expect_true(dir.exists("tests")) expect_true(file.exists("tests/testthat/test-golem-recommended.R")) }) From 836c3f66a0eed68e4e53f7cfce16de5c98c60343 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 16 Oct 2019 23:14:51 +0200 Subject: [PATCH 067/211] bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7cf86eae..b6c80371 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9001 +Version: 0.1.0.9500 Authors@R: c(person(given = "Vincent", family = "Guyader", From 188df210ad52647d448324af901459c72da1f2c0 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 17 Oct 2019 08:20:54 +0200 Subject: [PATCH 068/211] test refactoring --- tests/testthat/helper-config.R | 2 ++ tests/testthat/test-zzzzzzzzzz.R | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 tests/testthat/test-zzzzzzzzzz.R diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 818b2d88..39394cdd 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -25,3 +25,5 @@ rand_name <- function(){ } set_golem_wd(pkg) +orig_test <- usethis::proj_get() +usethis::proj_set(pkg) \ No newline at end of file diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R new file mode 100644 index 00000000..579775d3 --- /dev/null +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -0,0 +1,2 @@ +# For setting back old usethis settings +usethis::proj_set(orig_test) From 51e1ba3c640fa3bb18cdf791140a0b18d2ed8433 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 17 Oct 2019 13:16:16 +0200 Subject: [PATCH 069/211] rebuilt doc --- man/add_resource_path.Rd | 4 +++- man/golem.Rd | 1 + man/insert_ns.Rd | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 man/insert_ns.Rd diff --git a/man/add_resource_path.Rd b/man/add_resource_path.Rd index 3974c504..061a62a3 100644 --- a/man/add_resource_path.Rd +++ b/man/add_resource_path.Rd @@ -4,12 +4,14 @@ \alias{add_resource_path} \title{Add resource path} \usage{ -add_resource_path(prefix, directory_path) +add_resource_path(prefix, directory_path, warn_empty = FALSE) } \arguments{ \item{prefix}{The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory.} \item{directory_path}{The directory that contains the static resources to be served.} + +\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if directory empty} } \description{ Add resource path diff --git a/man/golem.Rd b/man/golem.Rd index 4c82de3e..f41867c2 100644 --- a/man/golem.Rd +++ b/man/golem.Rd @@ -28,6 +28,7 @@ Authors: Other contributors: \itemize{ + \item Novica Nakov \email{nnovica@gmail.com} [contributor] \item ThinkR [copyright holder] } diff --git a/man/insert_ns.Rd b/man/insert_ns.Rd new file mode 100644 index 00000000..d7d0f9f6 --- /dev/null +++ b/man/insert_ns.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addin_functions.R +\name{insert_ns} +\alias{insert_ns} +\title{Shorcut to insert NS} +\usage{ +insert_ns() +} +\description{ +Shorcut to insert NS +} From 23a48ede6a7b3a5c371bd3c64d720f801a924d0e Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 18 Oct 2019 14:37:56 +0200 Subject: [PATCH 070/211] close issue 231 --- R/add_files.R | 7 +++++++ R/add_modules.R | 4 ++++ R/add_r_files.R | 4 ++++ man/add_resource_path.Rd | 11 ++++++++--- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index 5a3607b6..e20e0251 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -10,6 +10,7 @@ #' @importFrom glue glue #' @importFrom cli cat_bullet #' @importFrom utils file.edit +#' @importFrom tools file_path_sans_ext add_js_file <- function( name, @@ -21,6 +22,8 @@ add_js_file <- function( attempt::stop_if(rlang::is_missing(name), msg = "Name is required") + name <- file_path_sans_ext(name) + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) @@ -75,6 +78,8 @@ add_js_handler <- function( attempt::stop_if(rlang::is_missing(name), msg = "Name is required") + name <- file_path_sans_ext(name) + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) @@ -141,6 +146,8 @@ add_css_file <- function( attempt::stop_if(rlang::is_missing(name), msg = "Name is required") + name <- file_path_sans_ext(name) + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) diff --git a/R/add_modules.R b/R/add_modules.R index 82814b71..8f0a7545 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -15,6 +15,7 @@ #' @importFrom glue glue #' @importFrom cli cat_bullet #' @importFrom utils file.edit +#' @importFrom tools file_path_sans_ext add_module <- function( name, pkg = get_golem_wd(), @@ -23,6 +24,9 @@ add_module <- function( fct = NULL, utils = NULL ){ + + name <- file_path_sans_ext(name) + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) diff --git a/R/add_r_files.R b/R/add_r_files.R index edc9c933..5c1c4b0b 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -1,3 +1,4 @@ +#' @importFrom tools file_path_sans_ext add_r_files <- function( name, ext = c("fct", "utils"), @@ -6,6 +7,9 @@ add_r_files <- function( open = TRUE, dir_create = TRUE ){ + + name <- file_path_sans_ext(name) + old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) diff --git a/man/add_resource_path.Rd b/man/add_resource_path.Rd index 061a62a3..c3502c66 100644 --- a/man/add_resource_path.Rd +++ b/man/add_resource_path.Rd @@ -7,11 +7,16 @@ add_resource_path(prefix, directory_path, warn_empty = FALSE) } \arguments{ -\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory.} +\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, +A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' +means that any request paths that begin with '/foo' will be mapped to the +given directory.} -\item{directory_path}{The directory that contains the static resources to be served.} +\item{directory_path}{The directory that contains the static resources to be +served.} -\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if directory empty} +\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if +directory empty} } \description{ Add resource path From 24aecae3945326c9854c3711f84760f276b32d11 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 18 Oct 2019 14:42:23 +0200 Subject: [PATCH 071/211] added test --- tests/testthat/test-add_function.R | 16 ++++++++++++++++ tests/testthat/test-add_module.R | 16 +++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index a205c33c..ac273727 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -15,6 +15,12 @@ test_that("add_css_file", { ) style <-list.files("inst/app/www/", pattern = "style") expect_equal(tools::file_ext(style), "css") + + # Test that extension is removed + remove_file("inst/app/www/style.css") + add_css_file("style.css", pkg = pkg, open = FALSE) + expect_true(file.exists("inst/app/www/style.css")) + remove_file("inst/app/www/style.css") remove_file("inst/app/www/stylebis.css") }) @@ -37,6 +43,11 @@ test_that("add_js_file", { expect_equal(tools::file_ext(script), "js") remove_file("inst/app/www/script.js") + + # Test that extension is removed + add_js_file("script.js", pkg = pkg, open = FALSE) + expect_true(file.exists("inst/app/www/script.js")) + remove_file("inst/app/www/script.js") }) }) @@ -57,6 +68,11 @@ test_that("add_js_handler", { expect_equal(tools::file_ext(script), "js") remove_file("inst/app/www/handler.js") + + # Test that extension is removed + add_js_handler("handler.js", pkg = pkg, open = FALSE) + expect_true(file.exists("inst/app/www/handler.js")) + remove_file("inst/app/www/handler.js") }) }) diff --git a/tests/testthat/test-add_module.R b/tests/testthat/test-add_module.R index d3944b8d..fb397a2f 100644 --- a/tests/testthat/test-add_module.R +++ b/tests/testthat/test-add_module.R @@ -4,11 +4,7 @@ context("function add_module") test_that("add_module", { with_dir(pkg, { remove_file("R/mod_test.R") - withr::with_options( - c("golem.wd" = pkg), { - add_module("test", open = FALSE, pkg = pkg, fct = "ftest", utils = "utest") - } - ) + add_module("test", open = FALSE, pkg = pkg, fct = "ftest", utils = "utest") expect_true(file.exists("R/mod_test.R")) expect_true(file.exists("R/mod_test_fct_ftest.R")) expect_true(file.exists("R/mod_test_utils_utest.R")) @@ -23,5 +19,15 @@ test_that("add_module", { stringr::str_detect(output, "File created at R/mod_output.R") ) remove_file("R/mod_test.R") + + # Test ext + add_module("test.R", open = FALSE, pkg = pkg, fct = "ftest", utils = "utest") + expect_true(file.exists("R/mod_test.R")) + expect_true(file.exists("R/mod_test_fct_ftest.R")) + expect_true(file.exists("R/mod_test_utils_utest.R")) + remove_file("R/mod_test.R") + remove_file("R/mod_test_fct_ftest.R") + remove_file("R/mod_test_utils_utest.R") + }) }) From 02a0f1ce07dc055444155a620b66730d96182a2f Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 18 Oct 2019 15:31:24 +0200 Subject: [PATCH 072/211] NAMESPACE --- NAMESPACE | 2 ++ man/golem_wd.Rd | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index c4d32969..ba138b1f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -72,6 +72,8 @@ importFrom(stats,setNames) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) +importFrom(tools,file_path_sans_ext) +importFrom(usethis,proj_set) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) importFrom(usethis,use_testthat) diff --git a/man/golem_wd.Rd b/man/golem_wd.Rd index e4bd279d..f58fa22a 100644 --- a/man/golem_wd.Rd +++ b/man/golem_wd.Rd @@ -27,3 +27,7 @@ It default to \code{"."}, the current directory when starting a golem. You can use these two functions if you need to manipulate this directory. } +\details{ +NOTE that this will also affect the behavior of the \code{{usethis}} +functions. +} From 9fad04cc757fb326417e95da476f8bc454f69f79 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 18 Oct 2019 15:35:30 +0200 Subject: [PATCH 073/211] news update --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index d792511a..7a2a2743 100644 --- a/NEWS.md +++ b/NEWS.md @@ -48,6 +48,8 @@ + Better error on missing name in add_*, (#120, @novica) ++ When adding file, the extension is now ignored if provided by the user (#231) + ## Internal changes + We no longer depend on `{stringr}` (#201, @TomerPacific) From 23326223a1aee03a9b8b25c35bdd382bdabd89be Mon Sep 17 00:00:00 2001 From: felixgolcher Date: Sat, 19 Oct 2019 12:01:52 +0200 Subject: [PATCH 074/211] replace all default argument defs of pkg to use get_golem_wd() --- R/reload.R | 2 +- R/use_favicon.R | 2 +- R/use_recommended.R | 4 ++-- R/use_utils.R | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/R/reload.R b/R/reload.R index 2664132b..a566a997 100644 --- a/R/reload.R +++ b/R/reload.R @@ -33,7 +33,7 @@ detach_all_attached <- function(){ #' @importFrom pkgload load_all #' @export document_and_reload <- function( - pkg = "." + pkg = get_golem_wd() ){ if (rstudioapi::hasFun("documentSaveAll")) { rstudioapi::documentSaveAll() diff --git a/R/use_favicon.R b/R/use_favicon.R index 04d36a8c..a4954b56 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -14,7 +14,7 @@ #' use_favicon(path='path/to/your/favicon.ico') #' } #' } -use_favicon <- function(path, pkg = "."){ +use_favicon <- function(path, pkg = get_golem_wd()){ if (missing(path)){ path <- golem_sys("shinyexample/inst/app/www", "favicon.ico") diff --git a/R/use_recommended.R b/R/use_recommended.R index b9b028fc..523de329 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -13,7 +13,7 @@ #' #' @export use_recommended_deps <- function( - pkg = ".", + pkg = get_golem_wd(), recommended = c("shiny","DT","attempt","glue","htmltools","golem") ){ old <- setwd(normalizePath(pkg)) @@ -31,7 +31,7 @@ use_recommended_deps <- function( #' @importFrom utils capture.output #' @importFrom attempt without_warning use_recommended_tests <- function -(pkg = "." +(pkg = get_golem_wd() ){ old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) diff --git a/R/use_utils.R b/R/use_utils.R index 2e508574..9a8ad651 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -13,7 +13,7 @@ #' @importFrom cli cat_bullet #' @importFrom glue glue #' @importFrom utils capture.output -use_utils_ui <- function(pkg = "."){ +use_utils_ui <- function(pkg = get_golem_wd()){ use_utils(file_name = "golem_utils_ui.R", pkg=pkg) capture.output( usethis::use_package("htmltools") @@ -23,12 +23,12 @@ use_utils_ui <- function(pkg = "."){ } #' @export #' @rdname utils_files -use_utils_server <- function(pkg = "."){ +use_utils_server <- function(pkg = get_golem_wd()){ use_utils(file_name = "golem_utils_server.R", pkg=pkg) cat_bullet("Utils server added", bullet = "tick", bullet_col = "green") } -use_utils <- function(file_name, pkg = "."){ +use_utils <- function(file_name, pkg = get_golem_wd()){ old <- setwd(normalizePath(pkg)) on.exit(setwd(old)) where <- file.path(normalizePath(pkg), "R", file_name) From b7a0aff06ab7a95d769177558ddd34b8d4f608a5 Mon Sep 17 00:00:00 2001 From: novica Date: Sat, 19 Oct 2019 22:24:14 +0200 Subject: [PATCH 075/211] Issue 207 --- R/options.R | 14 ++++++++++++++ R/utils.R | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/R/options.R b/R/options.R index ae43d209..d06756e0 100644 --- a/R/options.R +++ b/R/options.R @@ -53,6 +53,7 @@ get_golem_wd <- function(){ getOption("golem.wd") } + #' @export #' @rdname golem_wd set_golem_wd <- function( @@ -68,3 +69,16 @@ set_golem_wd <- function( options("golem.wd" = path) invisible(path) } + + +#' A function to return the golem name to be used elsewhere +#' in the package. +#' +#' @return The name of the golem. +#' @export +#' @rdname golem_name + + +get_golem_name <- function(){ + getOption("golem_name") +} \ No newline at end of file diff --git a/R/utils.R b/R/utils.R index 8fb2802c..84486901 100644 --- a/R/utils.R +++ b/R/utils.R @@ -2,6 +2,11 @@ golem_sys <- function(..., lib.loc = NULL, mustWork = FALSE){ system.file(..., package = "golem", lib.loc = lib.loc, mustWork = mustWork) } +app_sys <- function() { + system.file(..., package = get_golem_name()) +} + + # from usethis https://github.com/r-lib/usethis/ darkgrey <- function(x) { x <- crayon::make_style("darkgrey")(x) From f7ae27d5639656d1decce0409f56185a86cfc72a Mon Sep 17 00:00:00 2001 From: novica Date: Mon, 21 Oct 2019 21:49:15 +0200 Subject: [PATCH 076/211] add export to app_sys function --- R/utils.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/utils.R b/R/utils.R index 84486901..29351a89 100644 --- a/R/utils.R +++ b/R/utils.R @@ -2,6 +2,8 @@ golem_sys <- function(..., lib.loc = NULL, mustWork = FALSE){ system.file(..., package = "golem", lib.loc = lib.loc, mustWork = mustWork) } +#' @export +#' @rdname app_sys app_sys <- function() { system.file(..., package = get_golem_name()) } From d9408666f3e3f36f447e4cdd0e0ecf0489e3ac43 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 22 Oct 2019 23:23:04 +0200 Subject: [PATCH 077/211] update Rd & NAMESPACE --- NAMESPACE | 169 ++++++++++++++++++------------------- golem.Rproj | 36 ++++---- man/add_files.Rd | 72 ++++++++-------- man/add_module.Rd | 60 ++++++------- man/add_resource_path.Rd | 41 ++++----- man/browser_button.Rd | 28 +++--- man/create_golem.Rd | 54 ++++++------ man/detach_all_attached.Rd | 22 ++--- man/dockerfiles.Rd | 120 +++++++++++++------------- man/document_and_reload.Rd | 30 +++---- man/favicon.Rd | 68 +++++++-------- man/file_creation.Rd | 56 ++++++------ man/fill_desc.Rd | 60 ++++++------- man/get_dependencies.Rd | 56 ++++++------ man/get_golem_options.Rd | 32 +++---- man/go_to.Rd | 32 +++---- man/golem.Rd | 70 +++++++-------- man/golem_js.Rd | 82 +++++++++--------- man/golem_wd.Rd | 62 +++++++------- man/insert_ns.Rd | 22 ++--- man/made_dev.Rd | 56 ++++++------ man/make_dev.Rd | 30 +++---- man/prod.Rd | 34 ++++---- man/rstudio_deploy.Rd | 84 +++++++++--------- man/set_golem_options.Rd | 44 +++++----- man/testhelpers.Rd | 64 +++++++------- man/use_recommended.Rd | 46 +++++----- man/utils_files.Rd | 40 ++++----- man/with_golem_options.Rd | 48 +++++------ 29 files changed, 804 insertions(+), 814 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index ba138b1f..7b552258 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,85 +1,84 @@ -# Generated by roxygen2: do not edit by hand - -export(activate_js) -export(add_css_file) -export(add_dockerfile) -export(add_dockerfile_heroku) -export(add_dockerfile_shinyproxy) -export(add_fct) -export(add_js_file) -export(add_js_handler) -export(add_module) -export(add_resource_path) -export(add_rstudioconnect_file) -export(add_shinyappsio_file) -export(add_shinyserver_file) -export(add_ui_server_files) -export(add_utils) -export(app_dev) -export(app_prod) -export(browser_button) -export(browser_dev) -export(cat_dev) -export(create_golem) -export(detach_all_attached) -export(document_and_reload) -export(expect_html_equal) -export(expect_shinytag) -export(expect_shinytaglist) -export(favicon) -export(fill_desc) -export(get_dependencies) -export(get_golem_options) -export(get_golem_wd) -export(insert_ns) -export(invoke_js) -export(make_dev) -export(message_dev) -export(print_dev) -export(remove_favicon) -export(set_golem_options) -export(set_golem_wd) -export(use_favicon) -export(use_recommended_deps) -export(use_recommended_tests) -export(use_utils_server) -export(use_utils_ui) -export(warning_dev) -export(with_golem_options) -importFrom(attempt,attempt) -importFrom(attempt,stop_if_not) -importFrom(attempt,without_warning) -importFrom(cli,cat_bullet) -importFrom(cli,cat_line) -importFrom(cli,cat_rule) -importFrom(desc,description) -importFrom(glue,glue) -importFrom(htmltools,includeScript) -importFrom(htmltools,save_html) -importFrom(htmltools,tags) -importFrom(pkgload,load_all) -importFrom(pkgload,pkg_name) -importFrom(rlang,enquo) -importFrom(rlang,is_empty) -importFrom(roxygen2,roxygenise) -importFrom(rstudioapi,getSourceEditorContext) -importFrom(rstudioapi,isAvailable) -importFrom(rstudioapi,modifyRange) -importFrom(rstudioapi,openProject) -importFrom(shiny,addResourcePath) -importFrom(shiny,getShinyOption) -importFrom(stats,setNames) -importFrom(testthat,expect) -importFrom(testthat,expect_equal) -importFrom(testthat,quasi_label) -importFrom(tools,file_path_sans_ext) -importFrom(usethis,proj_set) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_package) -importFrom(usethis,use_testthat) -importFrom(utils,capture.output) -importFrom(utils,file.edit) -importFrom(utils,getFromNamespace) -importFrom(utils,installed.packages) -importFrom(utils,sessionInfo) -importFrom(yesno,yesno) +# Generated by roxygen2: do not edit by hand + +export(activate_js) +export(add_css_file) +export(add_dockerfile) +export(add_dockerfile_heroku) +export(add_dockerfile_shinyproxy) +export(add_fct) +export(add_js_file) +export(add_js_handler) +export(add_module) +export(add_resource_path) +export(add_rstudioconnect_file) +export(add_shinyappsio_file) +export(add_shinyserver_file) +export(add_ui_server_files) +export(add_utils) +export(app_dev) +export(app_prod) +export(browser_button) +export(browser_dev) +export(cat_dev) +export(create_golem) +export(detach_all_attached) +export(document_and_reload) +export(expect_html_equal) +export(expect_shinytag) +export(expect_shinytaglist) +export(favicon) +export(fill_desc) +export(get_dependencies) +export(get_golem_options) +export(get_golem_wd) +export(insert_ns) +export(invoke_js) +export(make_dev) +export(message_dev) +export(print_dev) +export(remove_favicon) +export(set_golem_options) +export(set_golem_wd) +export(use_favicon) +export(use_recommended_deps) +export(use_recommended_tests) +export(use_utils_server) +export(use_utils_ui) +export(warning_dev) +export(with_golem_options) +importFrom(attempt,attempt) +importFrom(attempt,stop_if_not) +importFrom(attempt,without_warning) +importFrom(cli,cat_bullet) +importFrom(cli,cat_line) +importFrom(cli,cat_rule) +importFrom(desc,description) +importFrom(glue,glue) +importFrom(htmltools,includeScript) +importFrom(htmltools,save_html) +importFrom(htmltools,tags) +importFrom(pkgload,load_all) +importFrom(pkgload,pkg_name) +importFrom(rlang,enquo) +importFrom(rlang,is_empty) +importFrom(roxygen2,roxygenise) +importFrom(rstudioapi,getSourceEditorContext) +importFrom(rstudioapi,isAvailable) +importFrom(rstudioapi,modifyRange) +importFrom(rstudioapi,openProject) +importFrom(shiny,addResourcePath) +importFrom(shiny,getShinyOption) +importFrom(stats,setNames) +importFrom(testthat,expect) +importFrom(testthat,expect_equal) +importFrom(testthat,quasi_label) +importFrom(tools,file_path_sans_ext) +importFrom(usethis,use_build_ignore) +importFrom(usethis,use_package) +importFrom(usethis,use_testthat) +importFrom(utils,capture.output) +importFrom(utils,file.edit) +importFrom(utils,getFromNamespace) +importFrom(utils,installed.packages) +importFrom(utils,sessionInfo) +importFrom(yesno,yesno) diff --git a/golem.Rproj b/golem.Rproj index 1788e686..8cc08ce6 100644 --- a/golem.Rproj +++ b/golem.Rproj @@ -1,18 +1,18 @@ -Version: 1.0 - -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -BuildType: Package -PackageUseDevtools: Yes -PackageInstallArgs: --no-multiarch --with-keep.source -PackageRoxygenize: rd,collate,namespace,vignette +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace,vignette diff --git a/man/add_files.Rd b/man/add_files.Rd index a6daf534..f6b42646 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -1,36 +1,36 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_files.R -\name{add_js_file} -\alias{add_js_file} -\alias{add_js_handler} -\alias{add_css_file} -\alias{add_ui_server_files} -\title{Create Files} -\usage{ -add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) - -add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) - -add_css_file(name, 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{ -\item{name}{The name of the module} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dir}{Path to the dir where the file while be created.} - -\item{open}{Should the file be opened?} - -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} -} -\description{ -These functions create files inside the \code{inst/app} folder. -These functions can be used outside of a {golem} project. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_files.R +\name{add_js_file} +\alias{add_js_file} +\alias{add_js_handler} +\alias{add_css_file} +\alias{add_ui_server_files} +\title{Create Files} +\usage{ +add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE) + +add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE) + +add_css_file(name, 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{ +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dir}{Path to the dir where the file while be created.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} +} +\description{ +These functions create files inside the \code{inst/app} folder. +These functions can be used outside of a {golem} project. +} diff --git a/man/add_module.Rd b/man/add_module.Rd index a6b8342a..b00c70f8 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -1,30 +1,30 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_modules.R -\name{add_module} -\alias{add_module} -\title{Create a module} -\usage{ -add_module(name, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE, fct = NULL, utils = NULL) -} -\arguments{ -\item{name}{The name of the module} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{open}{Should the file be opened?} - -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} - -\item{fct}{The name of the fct file.} - -\item{utils}{The name of the utils file.} -} -\description{ -This function creates a module inside the \code{R/} folder, based -on a specific module structure. This function can be used outside -of a {golem} project. -} -\note{ -This function will prefix the \code{name} argument with \code{mod_}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_modules.R +\name{add_module} +\alias{add_module} +\title{Create a module} +\usage{ +add_module(name, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE, fct = NULL, utils = NULL) +} +\arguments{ +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} + +\item{fct}{The name of the fct file.} + +\item{utils}{The name of the utils file.} +} +\description{ +This function creates a module inside the \code{R/} folder, based +on a specific module structure. This function can be used outside +of a {golem} project. +} +\note{ +This function will prefix the \code{name} argument with \code{mod_}. +} diff --git a/man/add_resource_path.Rd b/man/add_resource_path.Rd index c3502c66..d0bc958e 100644 --- a/man/add_resource_path.Rd +++ b/man/add_resource_path.Rd @@ -1,23 +1,18 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_ressource_path.R -\name{add_resource_path} -\alias{add_resource_path} -\title{Add resource path} -\usage{ -add_resource_path(prefix, directory_path, warn_empty = FALSE) -} -\arguments{ -\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, -A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' -means that any request paths that begin with '/foo' will be mapped to the -given directory.} - -\item{directory_path}{The directory that contains the static resources to be -served.} - -\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if -directory empty} -} -\description{ -Add resource path -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_ressource_path.R +\name{add_resource_path} +\alias{add_resource_path} +\title{Add resource path} +\usage{ +add_resource_path(prefix, directory_path, warn_empty = FALSE) +} +\arguments{ +\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory.} + +\item{directory_path}{The directory that contains the static resources to be served.} + +\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if directory empty} +} +\description{ +Add resource path +} diff --git a/man/browser_button.Rd b/man/browser_button.Rd index 91498f39..7c858744 100644 --- a/man/browser_button.Rd +++ b/man/browser_button.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/browser_button.R -\name{browser_button} -\alias{browser_button} -\title{Insert an hidden browser button} -\usage{ -browser_button() -} -\value{ -Prints the code to the console. -} -\description{ -See \url{https://rtask.thinkr.fr/blog/a-little-trick-for-debugging-shiny/} for more context. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/browser_button.R +\name{browser_button} +\alias{browser_button} +\title{Insert an hidden browser button} +\usage{ +browser_button() +} +\value{ +Prints the code to the console. +} +\description{ +See \url{https://rtask.thinkr.fr/blog/a-little-trick-for-debugging-shiny/} for more context. +} diff --git a/man/create_golem.Rd b/man/create_golem.Rd index 865a122a..a59c31b9 100644 --- a/man/create_golem.Rd +++ b/man/create_golem.Rd @@ -1,27 +1,27 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/create_golem.R -\name{create_golem} -\alias{create_golem} -\title{Create a package for Shiny App using golem} -\usage{ -create_golem(path, check_name = TRUE, open = TRUE, - package_name = basename(path), without_comments = FALSE, ...) -} -\arguments{ -\item{path}{Name of the folder to create the package in. This will also be -used as the package name.} - -\item{check_name}{When using this function in the console, you can prevent -the package name from being checked.} - -\item{open}{boolean open the created project} - -\item{package_name}{package name to use} - -\item{without_comments}{boolean start project without golem comments} - -\item{...}{not used} -} -\description{ -Create a package for Shiny App using golem -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/create_golem.R +\name{create_golem} +\alias{create_golem} +\title{Create a package for Shiny App using golem} +\usage{ +create_golem(path, check_name = TRUE, open = TRUE, + package_name = basename(path), without_comments = FALSE, ...) +} +\arguments{ +\item{path}{Name of the folder to create the package in. This will also be +used as the package name.} + +\item{check_name}{When using this function in the console, you can prevent +the package name from being checked.} + +\item{open}{boolean open the created project} + +\item{package_name}{package name to use} + +\item{without_comments}{boolean start project without golem comments} + +\item{...}{not used} +} +\description{ +Create a package for Shiny App using golem +} diff --git a/man/detach_all_attached.Rd b/man/detach_all_attached.Rd index 3e12756f..9e9ce540 100644 --- a/man/detach_all_attached.Rd +++ b/man/detach_all_attached.Rd @@ -1,11 +1,11 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reload.R -\name{detach_all_attached} -\alias{detach_all_attached} -\title{Detach all attached package} -\usage{ -detach_all_attached() -} -\description{ -Detach all attached package -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reload.R +\name{detach_all_attached} +\alias{detach_all_attached} +\title{Detach all attached package} +\usage{ +detach_all_attached() +} +\description{ +Detach all attached package +} diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 3cb7c0e6..d59eab69 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,60 +1,60 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{add_dockerfile} -\alias{add_dockerfile} -\alias{add_dockerfile_shinyproxy} -\alias{add_dockerfile_heroku} -\title{Create a Dockerfile for Shiny App} -\usage{ -add_dockerfile(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, - host = "0.0.0.0") - -add_dockerfile_shinyproxy(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) - -add_dockerfile_heroku(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) -} -\arguments{ -\item{input}{path to the DESCRIPTION file to use as an input.} - -\item{output}{name of the Dockerfile output.} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: -with \code{R.Version()$major} and \code{R.Version()$minor}.} - -\item{as}{The AS of the Dockerfile. Default it NULL.} - -\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. -Default is 80.} - -\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. -Default is 0.0.0.0.} -} -\description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and -\code{add_dockerfile_heroku()} creates platform specific Dockerfile. -} -\examples{ -\donttest{ -# Add a standard Dockerfile -if (interactive()){ - add_dockerfile() -} -# Add a Dockerfile for ShinyProxy -if (interactive()){ - add_dockerfile_shinyproxy() -} -# Add a Dockerfile for Heroku -if (interactive()){ - add_dockerfile_heroku() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{add_dockerfile} +\alias{add_dockerfile} +\alias{add_dockerfile_shinyproxy} +\alias{add_dockerfile_heroku} +\title{Create a Dockerfile for Shiny App} +\usage{ +add_dockerfile(input = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, + host = "0.0.0.0") + +add_dockerfile_shinyproxy(input = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL) + +add_dockerfile_heroku(input = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL) +} +\arguments{ +\item{input}{path to the DESCRIPTION file to use as an input.} + +\item{output}{name of the Dockerfile output.} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: +with \code{R.Version()$major} and \code{R.Version()$minor}.} + +\item{as}{The AS of the Dockerfile. Default it NULL.} + +\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. +Default is 80.} + +\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. +Default is 0.0.0.0.} +} +\description{ +Build a container containing your Shiny App. \code{add_dockerfile()} creates +a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and +\code{add_dockerfile_heroku()} creates platform specific Dockerfile. +} +\examples{ +\donttest{ +# Add a standard Dockerfile +if (interactive()){ + add_dockerfile() +} +# Add a Dockerfile for ShinyProxy +if (interactive()){ + add_dockerfile_shinyproxy() +} +# 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 d6d11d24..62ec1136 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -1,15 +1,15 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reload.R -\name{document_and_reload} -\alias{document_and_reload} -\title{Document and reload your package} -\usage{ -document_and_reload(pkg = get_golem_wd()) -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} -} -\description{ -This function calls \code{rstudioapi::documentSaveAll()}, -\code{roxygen2::roxygenise()} and \code{pkgload::load_all()}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reload.R +\name{document_and_reload} +\alias{document_and_reload} +\title{Document and reload your package} +\usage{ +document_and_reload(pkg = get_golem_wd()) +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} +} +\description{ +This function calls \code{rstudioapi::documentSaveAll()}, +\code{roxygen2::roxygenise()} and \code{pkgload::load_all()}. +} diff --git a/man/favicon.Rd b/man/favicon.Rd index e330df84..80839ef5 100644 --- a/man/favicon.Rd +++ b/man/favicon.Rd @@ -1,34 +1,34 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_favicon.R -\name{use_favicon} -\alias{use_favicon} -\alias{remove_favicon} -\alias{favicon} -\title{Add a favicon to your shinyapp} -\usage{ -use_favicon(path, pkg = ".") - -remove_favicon(path = "inst/app/www/favicon.ico") - -favicon(ico = "www/favicon.ico", rel = "shortcut icon") -} -\arguments{ -\item{path}{Path to your favicon file (.ico or .png)} - -\item{pkg}{Path to the root of the package. Default is \code{"."}} - -\item{ico}{path to favicon file} - -\item{rel}{rel} -} -\description{ -This function adds the favicon from \code{ico} to your shiny app. -} -\examples{ -\donttest{ -if (interactive()){ - use_favicon() - use_favicon(path='path/to/your/favicon.ico') -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_favicon.R +\name{use_favicon} +\alias{use_favicon} +\alias{remove_favicon} +\alias{favicon} +\title{Add a favicon to your shinyapp} +\usage{ +use_favicon(path, pkg = get_golem_wd()) + +remove_favicon(path = "inst/app/www/favicon.ico") + +favicon(ico = "www/favicon.ico", rel = "shortcut icon") +} +\arguments{ +\item{path}{Path to your favicon file (.ico or .png)} + +\item{pkg}{Path to the root of the package. Default is \code{"."}} + +\item{ico}{path to favicon file} + +\item{rel}{rel} +} +\description{ +This function adds the favicon from \code{ico} to your shiny app. +} +\examples{ +\donttest{ +if (interactive()){ + use_favicon() + use_favicon(path='path/to/your/favicon.ico') +} +} +} diff --git a/man/file_creation.Rd b/man/file_creation.Rd index dea0be83..d0835101 100644 --- a/man/file_creation.Rd +++ b/man/file_creation.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_r_files.R -\name{add_fct} -\alias{add_fct} -\alias{add_utils} -\title{Add fct_ and utils_ files} -\usage{ -add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) - -add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) -} -\arguments{ -\item{name}{The name of the file} - -\item{module}{If not NULL, the file will be module specific in the naming (you don't need to add the leading \code{mod_})} - -\item{pkg}{The working directory} - -\item{open}{Should the file be opened once created?} - -\item{dir_create}{Should the folder be created if it doesn't exist?} -} -\description{ -These function adds files in the R/ folder -that starts either with fct_ or with utils_ -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_r_files.R +\name{add_fct} +\alias{add_fct} +\alias{add_utils} +\title{Add fct_ and utils_ files} +\usage{ +add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) + +add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) +} +\arguments{ +\item{name}{The name of the file} + +\item{module}{If not NULL, the file will be module specific in the naming (you don't need to add the leading \code{mod_})} + +\item{pkg}{The working directory} + +\item{open}{Should the file be opened once created?} + +\item{dir_create}{Should the folder be created if it doesn't exist?} +} +\description{ +These function adds files in the R/ folder +that starts either with fct_ or with utils_ +} diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index 8a6e3407..23cb45f5 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -1,30 +1,30 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/desc.R -\name{fill_desc} -\alias{fill_desc} -\title{Fill your description} -\usage{ -fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, - author_last_name, author_email, repo_url = NULL, - pkg = get_golem_wd()) -} -\arguments{ -\item{pkg_name}{The name of the package} - -\item{pkg_title}{The title of the package} - -\item{pkg_description}{Description of the package} - -\item{author_first_name}{First Name of the author} - -\item{author_last_name}{Last Name of the author} - -\item{author_email}{Email of the author} - -\item{repo_url}{URL (if needed)} - -\item{pkg}{Path to look for the DESCRIPTION} -} -\description{ -Fill your description -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/desc.R +\name{fill_desc} +\alias{fill_desc} +\title{Fill your description} +\usage{ +fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, + author_last_name, author_email, repo_url = NULL, + pkg = get_golem_wd()) +} +\arguments{ +\item{pkg_name}{The name of the package} + +\item{pkg_title}{The title of the package} + +\item{pkg_description}{Description of the package} + +\item{author_first_name}{First Name of the author} + +\item{author_last_name}{Last Name of the author} + +\item{author_email}{Email of the author} + +\item{repo_url}{URL (if needed)} + +\item{pkg}{Path to look for the DESCRIPTION} +} +\description{ +Fill your description +} diff --git a/man/get_dependencies.Rd b/man/get_dependencies.Rd index bbd90470..82c933ff 100644 --- a/man/get_dependencies.Rd +++ b/man/get_dependencies.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_dependencies.R -\name{get_dependencies} -\alias{get_dependencies} -\title{Return all package dependencies from current package} -\usage{ -get_dependencies(path = "DESCRIPTION", pkg = get_golem_wd(), - dput = FALSE, field = c("Imports")) -} -\arguments{ -\item{path}{path to the DESCRIPTION file} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dput}{if TRUE return a dput output instead of character vector} - -\item{field}{DESCRIPTION fields to parse. Default is Import} -} -\description{ -Return all package dependencies from current package -} -\examples{ -\donttest{ -if (interactive()){ - get_dependencies() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_dependencies.R +\name{get_dependencies} +\alias{get_dependencies} +\title{Return all package dependencies from current package} +\usage{ +get_dependencies(path = "DESCRIPTION", pkg = get_golem_wd(), + dput = FALSE, field = c("Imports")) +} +\arguments{ +\item{path}{path to the DESCRIPTION file} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dput}{if TRUE return a dput output instead of character vector} + +\item{field}{DESCRIPTION fields to parse. Default is Import} +} +\description{ +Return all package dependencies from current package +} +\examples{ +\donttest{ +if (interactive()){ + get_dependencies() +} +} +} diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index d580f9c9..1676a32c 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -1,16 +1,16 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/with_opt.R -\name{get_golem_options} -\alias{get_golem_options} -\title{Get all or one golem options} -\usage{ -get_golem_options(which = NULL) -} -\arguments{ -\item{which}{NULL (default), or the name of an option} -} -\description{ -This function is to be used inside the -server and UI from your app, in order to call the -parameters passed to \code{run_app()}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{get_golem_options} +\alias{get_golem_options} +\title{Get all or one golem options} +\usage{ +get_golem_options(which = NULL) +} +\arguments{ +\item{which}{NULL (default), or the name of an option} +} +\description{ +This function is to be used inside the +server and UI from your app, in order to call the +parameters passed to \code{run_app()}. +} diff --git a/man/go_to.Rd b/man/go_to.Rd index 903fddc1..4849316e 100644 --- a/man/go_to.Rd +++ b/man/go_to.Rd @@ -1,16 +1,16 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addins.R -\name{go_to} -\alias{go_to} -\title{{golem} addins} -\usage{ -go_to(file, wd = golem::get_golem_wd()) -} -\arguments{ -\item{file}{Name of the file to be loaded from the \code{dev} directory.} - -\item{wd}{Working directory of the current golem package.} -} -\description{ -Addins to go to common files used in developing a \code{{golem}} package. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addins.R +\name{go_to} +\alias{go_to} +\title{{golem} addins} +\usage{ +go_to(file, wd = golem::get_golem_wd()) +} +\arguments{ +\item{file}{Name of the file to be loaded from the \code{dev} directory.} + +\item{wd}{Working directory of the current golem package.} +} +\description{ +Addins to go to common files used in developing a \code{{golem}} package. +} diff --git a/man/golem.Rd b/man/golem.Rd index f41867c2..d55a1d1f 100644 --- a/man/golem.Rd +++ b/man/golem.Rd @@ -1,35 +1,35 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/golem-package.R -\docType{package} -\name{golem} -\alias{golem} -\alias{golem-package} -\title{A package for building Shiny App} -\description{ -Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. -} -\seealso{ -Useful links: -\itemize{ - \item \url{https://github.com/ThinkR-open/golem} - \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} -} - -} -\author{ -\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) - -Authors: -\itemize{ - \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) - \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) - \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) -} - -Other contributors: -\itemize{ - \item Novica Nakov \email{nnovica@gmail.com} [contributor] - \item ThinkR [copyright holder] -} - -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/golem-package.R +\docType{package} +\name{golem} +\alias{golem} +\alias{golem-package} +\title{A package for building Shiny App} +\description{ +Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/ThinkR-open/golem} + \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} +} + +} +\author{ +\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) + +Authors: +\itemize{ + \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) + \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) + \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) +} + +Other contributors: +\itemize{ + \item Novica Nakov \email{nnovica@gmail.com} [contributor] + \item ThinkR [copyright holder] +} + +} diff --git a/man/golem_js.Rd b/man/golem_js.Rd index f1c87382..fbce52fc 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -1,41 +1,41 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/js.R -\name{activate_js} -\alias{activate_js} -\alias{invoke_js} -\title{Interact with JavaScript built-in Functions} -\usage{ -activate_js() - -invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) -} -\arguments{ -\item{fun}{JS function to be invoked.} - -\item{...}{JSON-like messages to be sent to the triggered JS function} - -\item{session}{The shiny session within which to call \code{sendCustomMessage}. - -\describe{ -\item{show}{Show an element with the jQuery selector provided.} -\item{hide}{Hide an element with the jQuery selector provided.} -\item{showid}{Show an element with the id provided.} -\item{hideid}{Hide an element with the id provided.} -\item{showclass}{Same as showid, but with class.} -\item{hideclass}{Same as hideid, but with class.} -\item{showhref}{Same as showid, but with \code{a[href*=}} -\item{hidehref}{Same as hideid, but with \code{a[href*=}} -\item{clickon}{Click on an element. The full jQuery selector has to be used.} -\item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} -\item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} -\item{alert}{Open an alert box with the message provided.} -\item{prompt}{Open a prompt box with the message provided.} -\item{confirm}{Open a confirm box with the message provided.} -}} -} -\description{ -\code{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 \code{invoke_js}. \code{invoke_js} can also be used -to launch any JS function created inside a Shiny JavaScript handler. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/js.R +\name{activate_js} +\alias{activate_js} +\alias{invoke_js} +\title{Interact with JavaScript built-in Functions} +\usage{ +activate_js() + +invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) +} +\arguments{ +\item{fun}{JS function to be invoked.} + +\item{...}{JSON-like messages to be sent to the triggered JS function} + +\item{session}{The shiny session within which to call \code{sendCustomMessage}. + +\describe{ +\item{show}{Show an element with the jQuery selector provided.} +\item{hide}{Hide an element with the jQuery selector provided.} +\item{showid}{Show an element with the id provided.} +\item{hideid}{Hide an element with the id provided.} +\item{showclass}{Same as showid, but with class.} +\item{hideclass}{Same as hideid, but with class.} +\item{showhref}{Same as showid, but with \code{a[href*=}} +\item{hidehref}{Same as hideid, but with \code{a[href*=}} +\item{clickon}{Click on an element. The full jQuery selector has to be used.} +\item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} +\item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} +\item{alert}{Open an alert box with the message provided.} +\item{prompt}{Open a prompt box with the message provided.} +\item{confirm}{Open a confirm box with the message provided.} +}} +} +\description{ +\code{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 \code{invoke_js}. \code{invoke_js} can also be used +to launch any JS function created inside a Shiny JavaScript handler. +} diff --git a/man/golem_wd.Rd b/man/golem_wd.Rd index f58fa22a..92655d8f 100644 --- a/man/golem_wd.Rd +++ b/man/golem_wd.Rd @@ -1,33 +1,29 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{get_golem_wd} -\alias{get_golem_wd} -\alias{set_golem_wd} -\title{Get and set \code{{golem}} working directory} -\usage{ -get_golem_wd() - -set_golem_wd(path, talkative = TRUE) -} -\arguments{ -\item{path}{The path to set the golem working directory. -Note that it will be passed to \code{normalizePath}.} - -\item{talkative}{Should the function print where the -new path is defined?} -} -\value{ -The path to the working directory. -} -\description{ -Many \code{{golem}} functions rely on a specific working directory, -most of the time the root of the package. This working directory -is set by \code{set_golem_options} or the first time you create a file. -It default to \code{"."}, the current directory when starting a golem. -You can use these two functions if you need to manipulate this -directory. -} -\details{ -NOTE that this will also affect the behavior of the \code{{usethis}} -functions. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{get_golem_wd} +\alias{get_golem_wd} +\alias{set_golem_wd} +\title{Get and set \code{{golem}} working directory} +\usage{ +get_golem_wd() + +set_golem_wd(path, talkative = TRUE) +} +\arguments{ +\item{path}{The path to set the golem working directory. +Note that it will be passed to \code{normalizePath}.} + +\item{talkative}{Should the function print where the +new path is defined?} +} +\value{ +The path to the working directory. +} +\description{ +Many \code{{golem}} functions rely on a specific working directory, +most of the time the root of the package. This working directory +is set by \code{set_golem_options} or the first time you create a file. +It default to \code{"."}, the current directory when starting a golem. +You can use these two functions if you need to manipulate this +directory. +} diff --git a/man/insert_ns.Rd b/man/insert_ns.Rd index d7d0f9f6..01cb8e51 100644 --- a/man/insert_ns.Rd +++ b/man/insert_ns.Rd @@ -1,11 +1,11 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addin_functions.R -\name{insert_ns} -\alias{insert_ns} -\title{Shorcut to insert NS} -\usage{ -insert_ns() -} -\description{ -Shorcut to insert NS -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addin_functions.R +\name{insert_ns} +\alias{insert_ns} +\title{Shorcut to insert NS} +\usage{ +insert_ns() +} +\description{ +Shorcut to insert NS +} diff --git a/man/made_dev.Rd b/man/made_dev.Rd index 43a56371..b04e97ed 100644 --- a/man/made_dev.Rd +++ b/man/made_dev.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{cat_dev} -\alias{cat_dev} -\alias{print_dev} -\alias{message_dev} -\alias{warning_dev} -\alias{browser_dev} -\title{Functions already made dev dependent} -\usage{ -cat_dev(...) - -print_dev(...) - -message_dev(...) - -warning_dev(...) - -browser_dev(...) -} -\arguments{ -\item{...}{\R objects (see \sQuote{Details} for the types of objects - allowed).} -} -\description{ -This functions will be run only if \code{golem::app_dev()} -returns TRUE. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{cat_dev} +\alias{cat_dev} +\alias{print_dev} +\alias{message_dev} +\alias{warning_dev} +\alias{browser_dev} +\title{Functions already made dev dependent} +\usage{ +cat_dev(...) + +print_dev(...) + +message_dev(...) + +warning_dev(...) + +browser_dev(...) +} +\arguments{ +\item{...}{\R objects (see \sQuote{Details} for the types of objects + allowed).} +} +\description{ +This functions will be run only if \code{golem::app_dev()} +returns TRUE. +} diff --git a/man/make_dev.Rd b/man/make_dev.Rd index 487ca1c9..c9bcfd5f 100644 --- a/man/make_dev.Rd +++ b/man/make_dev.Rd @@ -1,15 +1,15 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{make_dev} -\alias{make_dev} -\title{Make a function dependent to dev mode} -\usage{ -make_dev(fun) -} -\arguments{ -\item{fun}{A function} -} -\description{ -The function returned will be run only if \code{golem::app_dev()} -returns TRUE. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{make_dev} +\alias{make_dev} +\title{Make a function dependent to dev mode} +\usage{ +make_dev(fun) +} +\arguments{ +\item{fun}{A function} +} +\description{ +The function returned will be run only if \code{golem::app_dev()} +returns TRUE. +} diff --git a/man/prod.Rd b/man/prod.Rd index 9255c15b..428ecb0b 100644 --- a/man/prod.Rd +++ b/man/prod.Rd @@ -1,17 +1,17 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{app_prod} -\alias{app_prod} -\alias{app_dev} -\title{Is the app in dev mode or prod mode?} -\usage{ -app_prod() - -app_dev() -} -\value{ -\code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} -} -\description{ -Is the app in dev mode or prod mode? -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{app_prod} +\alias{app_prod} +\alias{app_dev} +\title{Is the app in dev mode or prod mode?} +\usage{ +app_prod() + +app_dev() +} +\value{ +\code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} +} +\description{ +Is the app in dev mode or prod mode? +} diff --git a/man/rstudio_deploy.Rd b/man/rstudio_deploy.Rd index e848f0d4..e0c2189c 100644 --- a/man/rstudio_deploy.Rd +++ b/man/rstudio_deploy.Rd @@ -1,42 +1,42 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{add_rstudioconnect_file} -\alias{add_rstudioconnect_file} -\alias{add_rconnect_file} -\alias{add_shinyappsio_file} -\alias{add_shinyserver_file} -\title{Add an app.R at the root of your package to deploy on RStudio Connect} -\usage{ -add_rstudioconnect_file(pkg = get_golem_wd(), open = TRUE) - -add_shinyappsio_file(pkg = get_golem_wd(), open = TRUE) - -add_shinyserver_file(pkg = get_golem_wd(), open = TRUE) -} -\arguments{ -\item{pkg}{Where to put the app.R.} - -\item{open}{Open the file} -} -\description{ -Add an app.R at the root of your package to deploy on RStudio Connect -} -\note{ -In previous versions, this function was called add_rconnect_file. -} -\examples{ -\donttest{ -# Add a file for Connect -if (interactive()){ - add_rstudioconnect_file() -} -# Add a file for Shiny Server -if (interactive()){ - add_shinyserver_file() -} -# Add a file for Shinyapps.io -if (interactive()){ - add_shinyappsio_file() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{add_rstudioconnect_file} +\alias{add_rstudioconnect_file} +\alias{add_rconnect_file} +\alias{add_shinyappsio_file} +\alias{add_shinyserver_file} +\title{Add an app.R at the root of your package to deploy on RStudio Connect} +\usage{ +add_rstudioconnect_file(pkg = get_golem_wd(), open = TRUE) + +add_shinyappsio_file(pkg = get_golem_wd(), open = TRUE) + +add_shinyserver_file(pkg = get_golem_wd(), open = TRUE) +} +\arguments{ +\item{pkg}{Where to put the app.R.} + +\item{open}{Open the file} +} +\description{ +Add an app.R at the root of your package to deploy on RStudio Connect +} +\note{ +In previous versions, this function was called add_rconnect_file. +} +\examples{ +\donttest{ +# Add a file for Connect +if (interactive()){ + add_rstudioconnect_file() +} +# Add a file for Shiny Server +if (interactive()){ + add_shinyserver_file() +} +# Add a file for Shinyapps.io +if (interactive()){ + add_shinyappsio_file() +} +} +} diff --git a/man/set_golem_options.Rd b/man/set_golem_options.Rd index 5942ae8f..9a7e37fb 100644 --- a/man/set_golem_options.Rd +++ b/man/set_golem_options.Rd @@ -1,22 +1,22 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\title{{golem} options} -\usage{ -set_golem_options(golem_name = pkgload::pkg_name(), - golem_version = pkgload::pkg_version(), - golem_wd = pkgload::pkg_path(), app_prod = FALSE) -} -\arguments{ -\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?} -} -\description{ -Set a series of options to be used internally by \code{{golem}}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{set_golem_options} +\alias{set_golem_options} +\title{{golem} options} +\usage{ +set_golem_options(golem_name = pkgload::pkg_name(), + golem_version = pkgload::pkg_version(), + golem_wd = pkgload::pkg_path(), app_prod = FALSE) +} +\arguments{ +\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?} +} +\description{ +Set a series of options to be used internally by \code{{golem}}. +} diff --git a/man/testhelpers.Rd b/man/testhelpers.Rd index 2ca76003..eaee8929 100644 --- a/man/testhelpers.Rd +++ b/man/testhelpers.Rd @@ -1,32 +1,32 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/test_helpers.R -\name{expect_shinytag} -\alias{expect_shinytag} -\alias{expect_shinytaglist} -\alias{expect_html_equal} -\title{Test helpers} -\usage{ -expect_shinytag(object) - -expect_shinytaglist(object) - -expect_html_equal(ui, html) -} -\arguments{ -\item{object}{the object to test} - -\item{ui}{output of an UI function} - -\item{html}{html file to compare to ui} -} -\value{ -A testthat result -} -\description{ -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)) -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/test_helpers.R +\name{expect_shinytag} +\alias{expect_shinytag} +\alias{expect_shinytaglist} +\alias{expect_html_equal} +\title{Test helpers} +\usage{ +expect_shinytag(object) + +expect_shinytaglist(object) + +expect_html_equal(ui, html) +} +\arguments{ +\item{object}{the object to test} + +\item{ui}{output of an UI function} + +\item{html}{html file to compare to ui} +} +\value{ +A testthat result +} +\description{ +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/man/use_recommended.Rd b/man/use_recommended.Rd index d9faf102..616891f2 100644 --- a/man/use_recommended.Rd +++ b/man/use_recommended.Rd @@ -1,23 +1,23 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_recommended.R -\name{use_recommended_deps} -\alias{use_recommended_deps} -\alias{use_recommended_tests} -\title{Add recommended elements} -\usage{ -use_recommended_deps(pkg = ".", recommended = c("shiny", "DT", - "attempt", "glue", "htmltools", "golem")) - -use_recommended_tests(pkg = ".") -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{recommended}{A vector of recommended packages.} -} -\description{ -\describe{ -\item{use_recommended_deps}{Adds \code{shiny}, \code{DT}, \code{attempt}, \code{glue}, \code{golem}, \code{htmltools} to dependencies} -\item{use_recommended_tests}{Adds a test folder and copy the golem tests} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_recommended.R +\name{use_recommended_deps} +\alias{use_recommended_deps} +\alias{use_recommended_tests} +\title{Add recommended elements} +\usage{ +use_recommended_deps(pkg = get_golem_wd(), recommended = c("shiny", + "DT", "attempt", "glue", "htmltools", "golem")) + +use_recommended_tests(pkg = get_golem_wd()) +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{recommended}{A vector of recommended packages.} +} +\description{ +\describe{ +\item{use_recommended_deps}{Adds \code{shiny}, \code{DT}, \code{attempt}, \code{glue}, \code{golem}, \code{htmltools} to dependencies} +\item{use_recommended_tests}{Adds a test folder and copy the golem tests} +} +} diff --git a/man/utils_files.Rd b/man/utils_files.Rd index 9d871af3..b533241d 100644 --- a/man/utils_files.Rd +++ b/man/utils_files.Rd @@ -1,20 +1,20 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_utils.R -\name{use_utils_ui} -\alias{use_utils_ui} -\alias{use_utils_server} -\title{Use the utils files} -\usage{ -use_utils_ui(pkg = ".") - -use_utils_server(pkg = ".") -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} -} -\description{ -\describe{ -\item{use_utils_ui}{Copies the golem_utils_ui.R to the R folder.} -\item{use_utils_server}{Copies the golem_utils_server.R to the R folder.} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_utils.R +\name{use_utils_ui} +\alias{use_utils_ui} +\alias{use_utils_server} +\title{Use the utils files} +\usage{ +use_utils_ui(pkg = get_golem_wd()) + +use_utils_server(pkg = get_golem_wd()) +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} +} +\description{ +\describe{ +\item{use_utils_ui}{Copies the golem_utils_ui.R to the R folder.} +\item{use_utils_server}{Copies the golem_utils_server.R to the R folder.} +} +} diff --git a/man/with_golem_options.Rd b/man/with_golem_options.Rd index 83824abf..3ea7adf4 100644 --- a/man/with_golem_options.Rd +++ b/man/with_golem_options.Rd @@ -1,24 +1,24 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/with_opt.R -\name{with_golem_options} -\alias{with_golem_options} -\title{Add Golem options to a Shiny App} -\usage{ -with_golem_options(app, golem_opts) -} -\arguments{ -\item{app}{the app object.} - -\item{golem_opts}{A list of Options to be added to the app} -} -\value{ -a shiny.appObj object -} -\description{ -Add Golem options to a Shiny App -} -\note{ -You'll probably never have to write this function -as it is included in the golem template created on -launch. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{with_golem_options} +\alias{with_golem_options} +\title{Add Golem options to a Shiny App} +\usage{ +with_golem_options(app, golem_opts) +} +\arguments{ +\item{app}{the app object.} + +\item{golem_opts}{A list of Options to be added to the app} +} +\value{ +a shiny.appObj object +} +\description{ +Add Golem options to a Shiny App +} +\note{ +You'll probably never have to write this function +as it is included in the golem template created on +launch. +} From d9fee04e93110f054aa26ccc2d434cbf7ab8d062 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 22 Oct 2019 23:28:46 +0200 Subject: [PATCH 078/211] add param for request #211 --- inst/shinyexample/R/app_ui.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 3765130e..6bee3c90 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,3 +1,5 @@ +#' @param request needed for bookmarking +#' #' @import shiny app_ui <- function(request) { tagList( From 2ca158469697b5aaab1913d7600e4c8cd43734e3 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 22 Oct 2019 23:29:37 +0200 Subject: [PATCH 079/211] fix #243 --- inst/shinyexample/NAMESPACE | 12 ++++++------ inst/shinyexample/R/app_ui.R | 2 ++ inst/shinyexample/R/run_app.R | 2 ++ inst/shinyexample/man/app_ui.Rd | 14 ++++++++++++++ inst/shinyexample/man/run_app.Rd | 25 ++++++++++++++----------- 5 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 inst/shinyexample/man/app_ui.Rd diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index e9569e88..8429ee35 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -1,6 +1,6 @@ -# Generated by roxygen2: do not edit by hand - -export(run_app) -import(shiny) -importFrom(golem,with_golem_options) -importFrom(shiny,shinyApp) +# Generated by roxygen2: do not edit by hand + +export(run_app) +import(shiny) +importFrom(golem,with_golem_options) +importFrom(shiny,shinyApp) diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 6bee3c90..f0ea534d 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,3 +1,5 @@ +#' ui +#' #' @param request needed for bookmarking #' #' @import shiny diff --git a/inst/shinyexample/R/run_app.R b/inst/shinyexample/R/run_app.R index 0c94a70e..743c013e 100644 --- a/inst/shinyexample/R/run_app.R +++ b/inst/shinyexample/R/run_app.R @@ -1,5 +1,7 @@ #' Run the Shiny Application #' +#' @param ... A list of Options to be added to the app +#' #' @export #' @importFrom shiny shinyApp #' @importFrom golem with_golem_options diff --git a/inst/shinyexample/man/app_ui.Rd b/inst/shinyexample/man/app_ui.Rd new file mode 100644 index 00000000..bac6b331 --- /dev/null +++ b/inst/shinyexample/man/app_ui.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_ui.R +\name{app_ui} +\alias{app_ui} +\title{ui} +\usage{ +app_ui(request) +} +\arguments{ +\item{request}{needed for bookmarking} +} +\description{ +ui +} diff --git a/inst/shinyexample/man/run_app.Rd b/inst/shinyexample/man/run_app.Rd index d4ddb3fb..50364079 100644 --- a/inst/shinyexample/man/run_app.Rd +++ b/inst/shinyexample/man/run_app.Rd @@ -1,11 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/run_app.R -\name{run_app} -\alias{run_app} -\title{run the Shiny Application} -\usage{ -run_app() -} -\description{ -run the Shiny Application -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/run_app.R +\name{run_app} +\alias{run_app} +\title{Run the Shiny Application} +\usage{ +run_app(...) +} +\arguments{ +\item{...}{A list of Options to be added to the app} +} +\description{ +Run the Shiny Application +} From a7bb86135a8a2aac6a8527e5a44b60b0878a9315 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 22 Oct 2019 23:37:33 +0200 Subject: [PATCH 080/211] fix #244 --- R/add_deploy_helpers.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 4c02095a..eba82e3a 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -17,6 +17,7 @@ add_rstudio_files <- function( } file.create( 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()") write_there("# Or use the blue button on top of this file") From 9218df3f2cd54def679772a7a6aed6589bd9fa13 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 21:20:38 +0200 Subject: [PATCH 081/211] fix #247 --- tests/testthat/test-add_module.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test-add_module.R b/tests/testthat/test-add_module.R index fb397a2f..0ba170d1 100644 --- a/tests/testthat/test-add_module.R +++ b/tests/testthat/test-add_module.R @@ -21,13 +21,13 @@ test_that("add_module", { remove_file("R/mod_test.R") # Test ext - add_module("test.R", open = FALSE, pkg = pkg, fct = "ftest", utils = "utest") - expect_true(file.exists("R/mod_test.R")) - expect_true(file.exists("R/mod_test_fct_ftest.R")) - expect_true(file.exists("R/mod_test_utils_utest.R")) - remove_file("R/mod_test.R") - remove_file("R/mod_test_fct_ftest.R") - remove_file("R/mod_test_utils_utest.R") + add_module("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 f74af0e18c4e75c058a2829e06973eb1adf3e7e3 Mon Sep 17 00:00:00 2001 From: statnmap Date: Wed, 23 Oct 2019 21:30:32 +0200 Subject: [PATCH 082/211] Saving files before refreshing line endings --- .gitattributes | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..24a50d91 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Declare files that will always have CRLF line endings on checkout. +*.Rd text eol=lf +*.R text eol=lf +*.Rmd text eol=lf +*.md text eol=lf +NAMESPACE text eol=lf +DESCRIPTION text eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary From 9b4c45752313180b22f800bd6120cc6bd2722038 Mon Sep 17 00:00:00 2001 From: statnmap Date: Wed, 23 Oct 2019 21:31:04 +0200 Subject: [PATCH 083/211] Normalize all the line endings --- inst/shinyexample/.gitignore | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/inst/shinyexample/.gitignore b/inst/shinyexample/.gitignore index a7305f83..c833a2c6 100644 --- a/inst/shinyexample/.gitignore +++ b/inst/shinyexample/.gitignore @@ -1,5 +1,5 @@ -.Rproj.user -.Rhistory -.RData -.Ruserdata -inst/doc +.Rproj.user +.Rhistory +.RData +.Ruserdata +inst/doc From 06aca874451cb80d37a343227ea5ef34da4e479f Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 21:34:27 +0200 Subject: [PATCH 084/211] update DESCRIPTION --- DESCRIPTION | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b6c80371..e2fa2e3f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,10 @@ Package: golem Title: A Framework for Robust Shiny Applications Version: 0.1.0.9500 +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. Authors@R: c(person(given = "Vincent", family = "Guyader", @@ -27,11 +31,7 @@ Authors@R: role = "ctb", email = "nnovica@gmail.com"), person(given = "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. + role = "cph")) License: MIT + file LICENSE URL: https://github.com/ThinkR-open/golem BugReports: https://github.com/ThinkR-open/golem/issues @@ -42,12 +42,13 @@ Imports: cli, crayon, desc, + devtools, dockerfiler, - DT, glue, htmltools, + jsonlite, + magrittr, pkgload, - processx, remotes, rlang, roxygen2, @@ -55,25 +56,26 @@ Imports: rstudioapi, shiny, stats, - stringr, testthat, tools, usethis, utils, yesno Suggests: - spelling, covr, + DT, knitr, pkgdown, purrr, rcmdcheck, rmarkdown, + spelling, + stringr, withr VignetteBuilder: knitr Encoding: UTF-8 +Language: en-US LazyData: true Roxygen: list(markdown = TRUE) RoxygenNote: 6.1.1 -Language: en-US From 06ffc8814477ef7d695b49ad7fa67d2f3c49b389 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 21:46:56 +0200 Subject: [PATCH 085/211] close #251 --- NEWS.md | 2 ++ R/get_dependencies.R | 35 ----------------------------------- 2 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 R/get_dependencies.R diff --git a/NEWS.md b/NEWS.md index 7a2a2743..2e0b1e71 100644 --- a/NEWS.md +++ b/NEWS.md @@ -34,6 +34,8 @@ + `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) + ## Bug fix + The Dockerfile is now correctly added to .Rbuildignore (#81) diff --git a/R/get_dependencies.R b/R/get_dependencies.R deleted file mode 100644 index 2d347caf..00000000 --- a/R/get_dependencies.R +++ /dev/null @@ -1,35 +0,0 @@ -#' Return all package dependencies from current package -#' -#' @inheritParams add_module -#' @param path path to the DESCRIPTION file -#' @param dput if TRUE return a dput output instead of character vector -#' @param field DESCRIPTION fields to parse. Default is Import -#' -#' @export -#' -#' @examples -#' \donttest{ -#' if (interactive()){ -#' get_dependencies() -#' } -#' } -#' @importFrom stats setNames -get_dependencies <- function( - path = "DESCRIPTION", - pkg = get_golem_wd(), - dput = FALSE, - field = c('Imports') -){ - path <- file.path(pkg, path) - out <- read.dcf(path)[,field] - out <- gsub(pattern = "\n",replacement = "", out) - out <- unlist(strsplit(out, ",")) - out <- setNames(out, NULL) - - out <- out[!grepl("^R [(]", out)] - - if ( !dput ){ - return(out) - } - dput(out) -} From d18daa895e84cf47c87a64e2619967ce08c66dba Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 21:58:32 +0200 Subject: [PATCH 086/211] removed tests --- tests/testthat/test-aaadependencies.R | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tests/testthat/test-aaadependencies.R diff --git a/tests/testthat/test-aaadependencies.R b/tests/testthat/test-aaadependencies.R deleted file mode 100644 index 49263b8d..00000000 --- a/tests/testthat/test-aaadependencies.R +++ /dev/null @@ -1,10 +0,0 @@ -context("tests get_dependencies") - -test_that("test get_dependencies",{ - with_dir(pkg,{ - test <- get_dependencies() - expect_equal(test, c("shiny","golem")) - test2 <- capture_output(get_dependencies(dput = TRUE)) - expect_equal(test2,"c(\"shiny\", \"golem\")") - }) -}) From c4a2dd7eb713a65a0f22bdcfe9009a2f968ed181 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 22:00:10 +0200 Subject: [PATCH 087/211] remove get_dependencies --- NAMESPACE | 184 ++++++++++++++++++++-------------------- man/get_dependencies.Rd | 28 ------ man/golem.Rd | 70 +++++++-------- 3 files changed, 126 insertions(+), 156 deletions(-) delete mode 100644 man/get_dependencies.Rd diff --git a/NAMESPACE b/NAMESPACE index d3aa1f93..b8677bf5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,93 +1,91 @@ -# Generated by roxygen2: do not edit by hand - -export(activate_js) -export(add_css_file) -export(add_dockerfile) -export(add_dockerfile_heroku) -export(add_dockerfile_shinyproxy) -export(add_fct) -export(add_js_file) -export(add_js_handler) -export(add_module) -export(add_resource_path) -export(add_rstudioconnect_file) -export(add_shinyappsio_file) -export(add_shinyserver_file) -export(add_ui_server_files) -export(add_utils) -export(app_dev) -export(app_prod) -export(browser_button) -export(browser_dev) -export(cat_dev) -export(create_golem) -export(detach_all_attached) -export(document_and_reload) -export(expect_html_equal) -export(expect_shinytag) -export(expect_shinytaglist) -export(favicon) -export(fill_desc) -export(get_dependencies) -export(get_golem_options) -export(get_golem_wd) -export(get_sysreqs) -export(insert_ns) -export(invoke_js) -export(make_dev) -export(message_dev) -export(print_dev) -export(remove_favicon) -export(set_golem_options) -export(set_golem_wd) -export(use_favicon) -export(use_recommended_deps) -export(use_recommended_tests) -export(use_utils_server) -export(use_utils_ui) -export(warning_dev) -export(with_golem_options) -importFrom(attempt,attempt) -importFrom(attempt,stop_if_not) -importFrom(attempt,without_warning) -importFrom(cli,cat_bullet) -importFrom(cli,cat_line) -importFrom(cli,cat_rule) -importFrom(desc,desc_get_deps) -importFrom(desc,description) -importFrom(dockerfiler,Dockerfile) -importFrom(glue,glue) -importFrom(htmltools,includeScript) -importFrom(htmltools,save_html) -importFrom(htmltools,tags) -importFrom(jsonlite,fromJSON) -importFrom(magrittr,"%>%") -importFrom(pkgload,load_all) -importFrom(pkgload,pkg_name) -importFrom(remotes,dev_package_deps) -importFrom(remotes,package_deps) -importFrom(rlang,enquo) -importFrom(rlang,is_empty) -importFrom(roxygen2,roxygenise) -importFrom(rstudioapi,getSourceEditorContext) -importFrom(rstudioapi,isAvailable) -importFrom(rstudioapi,modifyRange) -importFrom(rstudioapi,openProject) -importFrom(shiny,addResourcePath) -importFrom(shiny,getShinyOption) -importFrom(stats,setNames) -importFrom(testthat,expect) -importFrom(testthat,expect_equal) -importFrom(testthat,quasi_label) -importFrom(tools,file_path_sans_ext) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_package) -importFrom(usethis,use_testthat) -importFrom(utils,capture.output) -importFrom(utils,download.file) -importFrom(utils,file.edit) -importFrom(utils,getFromNamespace) -importFrom(utils,installed.packages) -importFrom(utils,packageVersion) -importFrom(utils,sessionInfo) -importFrom(yesno,yesno) +# Generated by roxygen2: do not edit by hand + +export(activate_js) +export(add_css_file) +export(add_dockerfile) +export(add_dockerfile_heroku) +export(add_dockerfile_shinyproxy) +export(add_fct) +export(add_js_file) +export(add_js_handler) +export(add_module) +export(add_resource_path) +export(add_rstudioconnect_file) +export(add_shinyappsio_file) +export(add_shinyserver_file) +export(add_ui_server_files) +export(add_utils) +export(app_dev) +export(app_prod) +export(browser_button) +export(browser_dev) +export(cat_dev) +export(create_golem) +export(detach_all_attached) +export(document_and_reload) +export(expect_html_equal) +export(expect_shinytag) +export(expect_shinytaglist) +export(favicon) +export(fill_desc) +export(get_golem_options) +export(get_golem_wd) +export(get_sysreqs) +export(insert_ns) +export(invoke_js) +export(make_dev) +export(message_dev) +export(print_dev) +export(remove_favicon) +export(set_golem_options) +export(set_golem_wd) +export(use_favicon) +export(use_recommended_deps) +export(use_recommended_tests) +export(use_utils_server) +export(use_utils_ui) +export(warning_dev) +export(with_golem_options) +importFrom(attempt,attempt) +importFrom(attempt,stop_if_not) +importFrom(attempt,without_warning) +importFrom(cli,cat_bullet) +importFrom(cli,cat_line) +importFrom(cli,cat_rule) +importFrom(desc,desc_get_deps) +importFrom(desc,description) +importFrom(dockerfiler,Dockerfile) +importFrom(glue,glue) +importFrom(htmltools,includeScript) +importFrom(htmltools,save_html) +importFrom(htmltools,tags) +importFrom(jsonlite,fromJSON) +importFrom(magrittr,"%>%") +importFrom(pkgload,load_all) +importFrom(pkgload,pkg_name) +importFrom(remotes,dev_package_deps) +importFrom(remotes,package_deps) +importFrom(rlang,enquo) +importFrom(rlang,is_empty) +importFrom(roxygen2,roxygenise) +importFrom(rstudioapi,getSourceEditorContext) +importFrom(rstudioapi,isAvailable) +importFrom(rstudioapi,modifyRange) +importFrom(rstudioapi,openProject) +importFrom(shiny,addResourcePath) +importFrom(shiny,getShinyOption) +importFrom(testthat,expect) +importFrom(testthat,expect_equal) +importFrom(testthat,quasi_label) +importFrom(tools,file_path_sans_ext) +importFrom(usethis,use_build_ignore) +importFrom(usethis,use_package) +importFrom(usethis,use_testthat) +importFrom(utils,capture.output) +importFrom(utils,download.file) +importFrom(utils,file.edit) +importFrom(utils,getFromNamespace) +importFrom(utils,installed.packages) +importFrom(utils,packageVersion) +importFrom(utils,sessionInfo) +importFrom(yesno,yesno) diff --git a/man/get_dependencies.Rd b/man/get_dependencies.Rd deleted file mode 100644 index 82c933ff..00000000 --- a/man/get_dependencies.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_dependencies.R -\name{get_dependencies} -\alias{get_dependencies} -\title{Return all package dependencies from current package} -\usage{ -get_dependencies(path = "DESCRIPTION", pkg = get_golem_wd(), - dput = FALSE, field = c("Imports")) -} -\arguments{ -\item{path}{path to the DESCRIPTION file} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dput}{if TRUE return a dput output instead of character vector} - -\item{field}{DESCRIPTION fields to parse. Default is Import} -} -\description{ -Return all package dependencies from current package -} -\examples{ -\donttest{ -if (interactive()){ - get_dependencies() -} -} -} diff --git a/man/golem.Rd b/man/golem.Rd index d55a1d1f..f41867c2 100644 --- a/man/golem.Rd +++ b/man/golem.Rd @@ -1,35 +1,35 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/golem-package.R -\docType{package} -\name{golem} -\alias{golem} -\alias{golem-package} -\title{A package for building Shiny App} -\description{ -Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. -} -\seealso{ -Useful links: -\itemize{ - \item \url{https://github.com/ThinkR-open/golem} - \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} -} - -} -\author{ -\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) - -Authors: -\itemize{ - \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) - \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) - \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) -} - -Other contributors: -\itemize{ - \item Novica Nakov \email{nnovica@gmail.com} [contributor] - \item ThinkR [copyright holder] -} - -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/golem-package.R +\docType{package} +\name{golem} +\alias{golem} +\alias{golem-package} +\title{A package for building Shiny App} +\description{ +Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/ThinkR-open/golem} + \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} +} + +} +\author{ +\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) + +Authors: +\itemize{ + \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) + \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) + \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) +} + +Other contributors: +\itemize{ + \item Novica Nakov \email{nnovica@gmail.com} [contributor] + \item ThinkR [copyright holder] +} + +} From d26fe24d380f2d9c9bce54b824df41a0f42e3f3e Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 22:06:17 +0200 Subject: [PATCH 088/211] #' @importFrom stats setNames --- NAMESPACE | 1 + R/add_deploy_helpers.R | 1 + 2 files changed, 2 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index b8677bf5..3626bb85 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -74,6 +74,7 @@ importFrom(rstudioapi,modifyRange) importFrom(rstudioapi,openProject) importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) +importFrom(stats,setNames) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index c5a4fff5..056fbd37 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -304,6 +304,7 @@ alert_build <- function(path, output){ #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps #' @importFrom magrittr %>% +#' @importFrom stats setNames #' dock_from_desc <- function( path = "DESCRIPTION", From d0fc87cbc7364fa8e242be1ca522df3dd1b58900 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 22:10:47 +0200 Subject: [PATCH 089/211] remove devtools deps --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index e2fa2e3f..28149a94 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -42,7 +42,6 @@ Imports: cli, crayon, desc, - devtools, dockerfiler, glue, htmltools, @@ -63,6 +62,7 @@ Imports: yesno Suggests: covr, + devtools, DT, knitr, pkgdown, From 1cd9cc53910972546262de6d046e78b9b5b9df41 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 22:14:33 +0200 Subject: [PATCH 090/211] tidyverse -> r-ver in FROM --- R/add_deploy_helpers.R | 25 +++++--- man/dock_from_desc.Rd | 54 ++++++++-------- man/dockerfiles.Rd | 142 ++++++++++++++++++++--------------------- 3 files changed, 114 insertions(+), 107 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 056fbd37..73a1ea0a 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -111,7 +111,7 @@ add_shinyserver_file <- function( #' @inheritParams add_module #' @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/tidyverse: +#' @param from The FROM of the Dockerfile. Default is FROM rocker/r-ver: #' with `R.Version()$major` and `R.Version()$minor`. #' @param as The AS of the Dockerfile. Default it NULL. #' @param port The `options('shiny.port')` on which to run the Shiny App. @@ -186,7 +186,7 @@ add_dockerfile_shinyproxy <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/tidyverse:", + "rocker/r-ver:", R.Version()$major,".", R.Version()$minor ), @@ -224,7 +224,7 @@ add_dockerfile_heroku <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/tidyverse:", + "rocker/r-ver:", R.Version()$major,".", R.Version()$minor ), @@ -292,13 +292,14 @@ alert_build <- function(path, output){ #' Create Dockerfile from DESCRIPTION # -#' @param path +#' @param path path to the DESCRIPTION file to use as an input. #' -#' @param FROM -#' @param AS -#' @param sysreqs -#' @param repos -#' @param expand +#' @param FROM The FROM of the Dockerfile. Default is FROM rocker/r-ver: +#' with `R.Version()$major` and `R.Version()$minor`. +#' @param AS The AS of the Dockerfile. Default it NULL. +#' @param sysreqs boolean to check the system requirements +#' @param repos character vector, the base URL of the repositories +#' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line #' #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps @@ -308,7 +309,11 @@ alert_build <- function(path, output){ #' dock_from_desc <- function( path = "DESCRIPTION", - FROM = "rocker/r-ver", + FROM = paste0( + "rocker/r-ver:", + R.Version()$major,".", + R.Version()$minor + ), AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd index 2c352360..2655fcf5 100644 --- a/man/dock_from_desc.Rd +++ b/man/dock_from_desc.Rd @@ -1,26 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{dock_from_desc} -\alias{dock_from_desc} -\title{Create Dockerfile from DESCRIPTION} -\usage{ -dock_from_desc(path = "DESCRIPTION", FROM = "rocker/r-ver", - AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) -} -\arguments{ -\item{path}{} - -\item{FROM}{} - -\item{AS}{} - -\item{sysreqs}{} - -\item{repos}{} - -\item{expand}{} -} -\description{ -Create Dockerfile from DESCRIPTION -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{dock_from_desc} +\alias{dock_from_desc} +\title{Create Dockerfile from DESCRIPTION} +\usage{ +dock_from_desc(path = "DESCRIPTION", FROM = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), AS = NULL, + sysreqs = TRUE, repos = "https://cran.rstudio.com/", + expand = FALSE) +} +\arguments{ +\item{path}{path to the DESCRIPTION file to use as an input.} + +\item{FROM}{The FROM of the Dockerfile. Default is FROM rocker/r-ver: +with \code{R.Version()$major} and \code{R.Version()$minor}.} + +\item{AS}{The AS of the Dockerfile. Default it NULL.} + +\item{sysreqs}{boolean to check the system requirements} + +\item{repos}{character vector, the base URL of the repositories} + +\item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} +} +\description{ +Create Dockerfile from DESCRIPTION +} diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 643d4921..910db9af 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,71 +1,71 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{add_dockerfile} -\alias{add_dockerfile} -\alias{add_dockerfile_shinyproxy} -\alias{add_dockerfile_heroku} -\title{Create a Dockerfile for Shiny App} -\usage{ -add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/r-ver:", - R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, - host = "0.0.0.0", sysreqs = TRUE, - repos = "https://cran.rstudio.com/", expand = FALSE) - -add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL, - sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) - -add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL, - sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) -} -\arguments{ -\item{path}{path to the DESCRIPTION file to use as an input.} - -\item{output}{name of the Dockerfile output.} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: -with \code{R.Version()$major} and \code{R.Version()$minor}.} - -\item{as}{The AS of the Dockerfile. Default it NULL.} - -\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. -Default is 80.} - -\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. -Default is 0.0.0.0.} - -\item{sysreqs}{boolean to check the system requirements} - -\item{repos}{character vector, the base URL of the repositories} - -\item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} -} -\description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and -\code{add_dockerfile_heroku()} creates platform specific Dockerfile. -} -\examples{ -\donttest{ -# Add a standard Dockerfile -if (interactive()){ - add_dockerfile() -} -# Add a Dockerfile for ShinyProxy -if (interactive()){ - add_dockerfile_shinyproxy() -} -# Add a Dockerfile for Heroku -if (interactive()){ - add_dockerfile_heroku() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{add_dockerfile} +\alias{add_dockerfile} +\alias{add_dockerfile_shinyproxy} +\alias{add_dockerfile_heroku} +\title{Create a Dockerfile for Shiny App} +\usage{ +add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, + host = "0.0.0.0", sysreqs = TRUE, + repos = "https://cran.rstudio.com/", expand = FALSE) + +add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), as = NULL, + sysreqs = TRUE, repos = "https://cran.rstudio.com/", + expand = FALSE) + +add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), as = NULL, + sysreqs = TRUE, repos = "https://cran.rstudio.com/", + expand = FALSE) +} +\arguments{ +\item{path}{path to the DESCRIPTION file to use as an input.} + +\item{output}{name of the Dockerfile output.} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{from}{The FROM of the Dockerfile. Default is FROM rocker/r-ver: +with \code{R.Version()$major} and \code{R.Version()$minor}.} + +\item{as}{The AS of the Dockerfile. Default it NULL.} + +\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. +Default is 80.} + +\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. +Default is 0.0.0.0.} + +\item{sysreqs}{boolean to check the system requirements} + +\item{repos}{character vector, the base URL of the repositories} + +\item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} +} +\description{ +Build a container containing your Shiny App. \code{add_dockerfile()} creates +a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and +\code{add_dockerfile_heroku()} creates platform specific Dockerfile. +} +\examples{ +\donttest{ +# Add a standard Dockerfile +if (interactive()){ + add_dockerfile() +} +# Add a Dockerfile for ShinyProxy +if (interactive()){ + add_dockerfile_shinyproxy() +} +# Add a Dockerfile for Heroku +if (interactive()){ + add_dockerfile_heroku() +} +} +} From 167d247174a0c0e5e63fd4bf9b71f1287a3fadca Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 22:17:57 +0200 Subject: [PATCH 091/211] add_dockerfile tests now work --- tests/testthat/helper-config.R | 47 ++++++++++++++++++++++------ tests/testthat/test-add_dockerfile.R | 32 +++++++++---------- tests/testthat/test-add_function.R | 4 +-- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 39394cdd..20a09493 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -1,18 +1,47 @@ ### lib library(withr) -#unlink(list.files(tempdir(), full.names = TRUE), recursive = TRUE) + +### Funs remove_file <- function(path){ - if (file.exists(path)) file.remove(path) + if (file.exists(path)) unlink(path, force = TRUE) +} + +remove_files <- function(path, pattern = NULL){ + fls <- list.files( + path, pattern, full.names = TRUE, recursive = TRUE + ) + if (length(fls) >0){ + res <- lapply(fls, function(x){ + if (file.exists(x)) unlink(x, force = TRUE) + }) + } +} + +expect_exists <- function(fls) { + + act <- quasi_label(rlang::enquo(fls), arg = "fls") + + act$val <- file.exists(fls) + expect( + isTRUE(act$val), + sprintf("File %s doesn't exist.", fls) + ) + + invisible(act$val) } + ## fake package -fakename <- paste0(sample(letters, 10, TRUE), collapse = "") -#unlink(list.files(tempdir()), recursive = TRUE) +fakename <- sprintf( + "%s%s", + paste0(sample(letters, 10, TRUE), collapse = ""), + gsub("[ :-]", "", Sys.time()) + ) + tpdir <- tempdir() -if(!dir.exists(file.path(tpdir,fakename))){ - create_golem(file.path(tpdir, fakename), open = FALSE) -} +unlink(file.path(tpdir,fakename), recursive = TRUE) +create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) ## random dir @@ -24,6 +53,4 @@ rand_name <- function(){ paste0(sample(letters, 10, TRUE), collapse = "") } -set_golem_wd(pkg) -orig_test <- usethis::proj_get() -usethis::proj_set(pkg) \ No newline at end of file +orig_test <- set_golem_wd(pkg) diff --git a/tests/testthat/test-add_dockerfile.R b/tests/testthat/test-add_dockerfile.R index 41e385b3..2329cdfc 100644 --- a/tests/testthat/test-add_dockerfile.R +++ b/tests/testthat/test-add_dockerfile.R @@ -2,37 +2,33 @@ context("function add_dockerfile") test_that("add_dockerfile", { with_dir(pkg, { - remove_file("Dockerfile") + unlink("Dockerfile", force = TRUE) output <- testthat::capture_output(add_dockerfile(pkg = pkg)) - expect_true(file.exists("Dockerfile")) - test <- - stringr::str_detect(output, "Dockerfile created at Dockerfile") + expect_exists("Dockerfile") + test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") expect_true(test) - remove_file("Dockerfile") + remove_files("Dockerfile") }) }) - - +# +# test_that("add_dockerfile_heroku", { with_dir(pkg, { - remove_file("Dockerfile") + unlink("Dockerfile", force = TRUE) output <- testthat::capture_output(add_dockerfile_heroku(pkg = pkg)) - expect_true(file.exists("Dockerfile")) - test <- - stringr::str_detect(output, "Dockerfile created at Dockerfile") + expect_exists("Dockerfile") + test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") expect_true(test) - remove_file("Dockerfile") + remove_files("Dockerfile") }) }) test_that("add_dockerfile_shinyproxy", { with_dir(pkg, { - remove_file("Dockerfile") + unlink("Dockerfile", force = TRUE) output <- testthat::capture_output(add_dockerfile_shinyproxy(pkg = pkg)) - expect_true(file.exists("Dockerfile")) - test <- - stringr::str_detect(output, "Dockerfile created at Dockerfile") - expect_true(test) - remove_file("Dockerfile") + expect_exists("Dockerfile") + test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") + remove_files("Dockerfile") }) }) diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_function.R index ac273727..f5e682cf 100644 --- a/tests/testthat/test-add_function.R +++ b/tests/testthat/test-add_function.R @@ -3,9 +3,9 @@ context("test-add_file function") test_that("add_css_file", { with_dir(pkg, { - remove_file("inst/app/www/style.css") + remove_files("inst/app/www", "\\.css$") add_css_file("style", pkg = pkg, open = FALSE) - expect_true(file.exists("inst/app/www/style.css")) + expect_exists("inst/app/www/style.css") add_css_file("stylebis", pkg = pkg, open = FALSE, dir = normalizePath(fp)) expect_true( From b8b3d1082eaa010666c99e1d6aa243ccf8087966 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 22:28:44 +0200 Subject: [PATCH 092/211] merge from dev --- man/get_sysreqs.Rd | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/man/get_sysreqs.Rd b/man/get_sysreqs.Rd index e3607f9a..c8a1f4ce 100644 --- a/man/get_sysreqs.Rd +++ b/man/get_sysreqs.Rd @@ -1,16 +1,16 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_sysreqs.R -\name{get_sysreqs} -\alias{get_sysreqs} -\title{get system requirements} -\usage{ -get_sysreqs(packages, quiet = TRUE) -} -\arguments{ -\item{packages}{character vector of packages names} - -\item{quiet}{boolean if TRUE the function is quiet} -} -\description{ -get system requirements -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_sysreqs.R +\name{get_sysreqs} +\alias{get_sysreqs} +\title{get system requirements} +\usage{ +get_sysreqs(packages, quiet = TRUE) +} +\arguments{ +\item{packages}{character vector of packages names} + +\item{quiet}{boolean if TRUE the function is quiet} +} +\description{ +get system requirements +} From cc0a4e2aef6b37d052fe259b0f5d50daaadc7736 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 23 Oct 2019 22:32:54 +0200 Subject: [PATCH 093/211] update doc --- man/get_sysreqs.Rd | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/man/get_sysreqs.Rd b/man/get_sysreqs.Rd index e3607f9a..c8a1f4ce 100644 --- a/man/get_sysreqs.Rd +++ b/man/get_sysreqs.Rd @@ -1,16 +1,16 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_sysreqs.R -\name{get_sysreqs} -\alias{get_sysreqs} -\title{get system requirements} -\usage{ -get_sysreqs(packages, quiet = TRUE) -} -\arguments{ -\item{packages}{character vector of packages names} - -\item{quiet}{boolean if TRUE the function is quiet} -} -\description{ -get system requirements -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_sysreqs.R +\name{get_sysreqs} +\alias{get_sysreqs} +\title{get system requirements} +\usage{ +get_sysreqs(packages, quiet = TRUE) +} +\arguments{ +\item{packages}{character vector of packages names} + +\item{quiet}{boolean if TRUE the function is quiet} +} +\description{ +get system requirements +} From 47ce7fc908edfe0f246a5650b181e1e2c907183d Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 22:34:58 +0200 Subject: [PATCH 094/211] rd --- NAMESPACE | 168 ++++++++++++++++++------------------- golem.Rproj | 36 ++++---- man/add_files.Rd | 72 ++++++++-------- man/add_module.Rd | 60 ++++++------- man/add_resource_path.Rd | 36 ++++---- man/browser_button.Rd | 28 +++---- man/create_golem.Rd | 54 ++++++------ man/detach_all_attached.Rd | 22 ++--- man/dockerfiles.Rd | 120 +++++++++++++------------- man/document_and_reload.Rd | 30 +++---- man/favicon.Rd | 68 +++++++-------- man/file_creation.Rd | 56 ++++++------- man/fill_desc.Rd | 60 ++++++------- man/get_dependencies.Rd | 56 ++++++------- man/get_golem_options.Rd | 32 +++---- man/go_to.Rd | 32 +++---- man/golem.Rd | 70 ++++++++-------- man/golem_js.Rd | 82 +++++++++--------- man/golem_wd.Rd | 58 ++++++------- man/insert_ns.Rd | 22 ++--- man/made_dev.Rd | 56 ++++++------- man/make_dev.Rd | 30 +++---- man/prod.Rd | 34 ++++---- man/rstudio_deploy.Rd | 84 +++++++++---------- man/set_golem_options.Rd | 44 +++++----- man/testhelpers.Rd | 64 +++++++------- man/use_recommended.Rd | 46 +++++----- man/utils_files.Rd | 40 ++++----- man/with_golem_options.Rd | 48 +++++------ 29 files changed, 804 insertions(+), 804 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 7b552258..c3acf160 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,84 +1,84 @@ -# Generated by roxygen2: do not edit by hand - -export(activate_js) -export(add_css_file) -export(add_dockerfile) -export(add_dockerfile_heroku) -export(add_dockerfile_shinyproxy) -export(add_fct) -export(add_js_file) -export(add_js_handler) -export(add_module) -export(add_resource_path) -export(add_rstudioconnect_file) -export(add_shinyappsio_file) -export(add_shinyserver_file) -export(add_ui_server_files) -export(add_utils) -export(app_dev) -export(app_prod) -export(browser_button) -export(browser_dev) -export(cat_dev) -export(create_golem) -export(detach_all_attached) -export(document_and_reload) -export(expect_html_equal) -export(expect_shinytag) -export(expect_shinytaglist) -export(favicon) -export(fill_desc) -export(get_dependencies) -export(get_golem_options) -export(get_golem_wd) -export(insert_ns) -export(invoke_js) -export(make_dev) -export(message_dev) -export(print_dev) -export(remove_favicon) -export(set_golem_options) -export(set_golem_wd) -export(use_favicon) -export(use_recommended_deps) -export(use_recommended_tests) -export(use_utils_server) -export(use_utils_ui) -export(warning_dev) -export(with_golem_options) -importFrom(attempt,attempt) -importFrom(attempt,stop_if_not) -importFrom(attempt,without_warning) -importFrom(cli,cat_bullet) -importFrom(cli,cat_line) -importFrom(cli,cat_rule) -importFrom(desc,description) -importFrom(glue,glue) -importFrom(htmltools,includeScript) -importFrom(htmltools,save_html) -importFrom(htmltools,tags) -importFrom(pkgload,load_all) -importFrom(pkgload,pkg_name) -importFrom(rlang,enquo) -importFrom(rlang,is_empty) -importFrom(roxygen2,roxygenise) -importFrom(rstudioapi,getSourceEditorContext) -importFrom(rstudioapi,isAvailable) -importFrom(rstudioapi,modifyRange) -importFrom(rstudioapi,openProject) -importFrom(shiny,addResourcePath) -importFrom(shiny,getShinyOption) -importFrom(stats,setNames) -importFrom(testthat,expect) -importFrom(testthat,expect_equal) -importFrom(testthat,quasi_label) -importFrom(tools,file_path_sans_ext) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_package) -importFrom(usethis,use_testthat) -importFrom(utils,capture.output) -importFrom(utils,file.edit) -importFrom(utils,getFromNamespace) -importFrom(utils,installed.packages) -importFrom(utils,sessionInfo) -importFrom(yesno,yesno) +# Generated by roxygen2: do not edit by hand + +export(activate_js) +export(add_css_file) +export(add_dockerfile) +export(add_dockerfile_heroku) +export(add_dockerfile_shinyproxy) +export(add_fct) +export(add_js_file) +export(add_js_handler) +export(add_module) +export(add_resource_path) +export(add_rstudioconnect_file) +export(add_shinyappsio_file) +export(add_shinyserver_file) +export(add_ui_server_files) +export(add_utils) +export(app_dev) +export(app_prod) +export(browser_button) +export(browser_dev) +export(cat_dev) +export(create_golem) +export(detach_all_attached) +export(document_and_reload) +export(expect_html_equal) +export(expect_shinytag) +export(expect_shinytaglist) +export(favicon) +export(fill_desc) +export(get_dependencies) +export(get_golem_options) +export(get_golem_wd) +export(insert_ns) +export(invoke_js) +export(make_dev) +export(message_dev) +export(print_dev) +export(remove_favicon) +export(set_golem_options) +export(set_golem_wd) +export(use_favicon) +export(use_recommended_deps) +export(use_recommended_tests) +export(use_utils_server) +export(use_utils_ui) +export(warning_dev) +export(with_golem_options) +importFrom(attempt,attempt) +importFrom(attempt,stop_if_not) +importFrom(attempt,without_warning) +importFrom(cli,cat_bullet) +importFrom(cli,cat_line) +importFrom(cli,cat_rule) +importFrom(desc,description) +importFrom(glue,glue) +importFrom(htmltools,includeScript) +importFrom(htmltools,save_html) +importFrom(htmltools,tags) +importFrom(pkgload,load_all) +importFrom(pkgload,pkg_name) +importFrom(rlang,enquo) +importFrom(rlang,is_empty) +importFrom(roxygen2,roxygenise) +importFrom(rstudioapi,getSourceEditorContext) +importFrom(rstudioapi,isAvailable) +importFrom(rstudioapi,modifyRange) +importFrom(rstudioapi,openProject) +importFrom(shiny,addResourcePath) +importFrom(shiny,getShinyOption) +importFrom(stats,setNames) +importFrom(testthat,expect) +importFrom(testthat,expect_equal) +importFrom(testthat,quasi_label) +importFrom(tools,file_path_sans_ext) +importFrom(usethis,use_build_ignore) +importFrom(usethis,use_package) +importFrom(usethis,use_testthat) +importFrom(utils,capture.output) +importFrom(utils,file.edit) +importFrom(utils,getFromNamespace) +importFrom(utils,installed.packages) +importFrom(utils,sessionInfo) +importFrom(yesno,yesno) diff --git a/golem.Rproj b/golem.Rproj index 8cc08ce6..1788e686 100644 --- a/golem.Rproj +++ b/golem.Rproj @@ -1,18 +1,18 @@ -Version: 1.0 - -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -BuildType: Package -PackageUseDevtools: Yes -PackageInstallArgs: --no-multiarch --with-keep.source -PackageRoxygenize: rd,collate,namespace,vignette +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace,vignette diff --git a/man/add_files.Rd b/man/add_files.Rd index f6b42646..a6daf534 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -1,36 +1,36 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_files.R -\name{add_js_file} -\alias{add_js_file} -\alias{add_js_handler} -\alias{add_css_file} -\alias{add_ui_server_files} -\title{Create Files} -\usage{ -add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) - -add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) - -add_css_file(name, 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{ -\item{name}{The name of the module} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dir}{Path to the dir where the file while be created.} - -\item{open}{Should the file be opened?} - -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} -} -\description{ -These functions create files inside the \code{inst/app} folder. -These functions can be used outside of a {golem} project. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_files.R +\name{add_js_file} +\alias{add_js_file} +\alias{add_js_handler} +\alias{add_css_file} +\alias{add_ui_server_files} +\title{Create Files} +\usage{ +add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE) + +add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE) + +add_css_file(name, 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{ +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dir}{Path to the dir where the file while be created.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} +} +\description{ +These functions create files inside the \code{inst/app} folder. +These functions can be used outside of a {golem} project. +} diff --git a/man/add_module.Rd b/man/add_module.Rd index b00c70f8..a6b8342a 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -1,30 +1,30 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_modules.R -\name{add_module} -\alias{add_module} -\title{Create a module} -\usage{ -add_module(name, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE, fct = NULL, utils = NULL) -} -\arguments{ -\item{name}{The name of the module} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{open}{Should the file be opened?} - -\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} - -\item{fct}{The name of the fct file.} - -\item{utils}{The name of the utils file.} -} -\description{ -This function creates a module inside the \code{R/} folder, based -on a specific module structure. This function can be used outside -of a {golem} project. -} -\note{ -This function will prefix the \code{name} argument with \code{mod_}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_modules.R +\name{add_module} +\alias{add_module} +\title{Create a module} +\usage{ +add_module(name, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE, fct = NULL, utils = NULL) +} +\arguments{ +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} + +\item{fct}{The name of the fct file.} + +\item{utils}{The name of the utils file.} +} +\description{ +This function creates a module inside the \code{R/} folder, based +on a specific module structure. This function can be used outside +of a {golem} project. +} +\note{ +This function will prefix the \code{name} argument with \code{mod_}. +} diff --git a/man/add_resource_path.Rd b/man/add_resource_path.Rd index d0bc958e..061a62a3 100644 --- a/man/add_resource_path.Rd +++ b/man/add_resource_path.Rd @@ -1,18 +1,18 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_ressource_path.R -\name{add_resource_path} -\alias{add_resource_path} -\title{Add resource path} -\usage{ -add_resource_path(prefix, directory_path, warn_empty = FALSE) -} -\arguments{ -\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory.} - -\item{directory_path}{The directory that contains the static resources to be served.} - -\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if directory empty} -} -\description{ -Add resource path -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_ressource_path.R +\name{add_resource_path} +\alias{add_resource_path} +\title{Add resource path} +\usage{ +add_resource_path(prefix, directory_path, warn_empty = FALSE) +} +\arguments{ +\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory.} + +\item{directory_path}{The directory that contains the static resources to be served.} + +\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if directory empty} +} +\description{ +Add resource path +} diff --git a/man/browser_button.Rd b/man/browser_button.Rd index 7c858744..91498f39 100644 --- a/man/browser_button.Rd +++ b/man/browser_button.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/browser_button.R -\name{browser_button} -\alias{browser_button} -\title{Insert an hidden browser button} -\usage{ -browser_button() -} -\value{ -Prints the code to the console. -} -\description{ -See \url{https://rtask.thinkr.fr/blog/a-little-trick-for-debugging-shiny/} for more context. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/browser_button.R +\name{browser_button} +\alias{browser_button} +\title{Insert an hidden browser button} +\usage{ +browser_button() +} +\value{ +Prints the code to the console. +} +\description{ +See \url{https://rtask.thinkr.fr/blog/a-little-trick-for-debugging-shiny/} for more context. +} diff --git a/man/create_golem.Rd b/man/create_golem.Rd index a59c31b9..865a122a 100644 --- a/man/create_golem.Rd +++ b/man/create_golem.Rd @@ -1,27 +1,27 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/create_golem.R -\name{create_golem} -\alias{create_golem} -\title{Create a package for Shiny App using golem} -\usage{ -create_golem(path, check_name = TRUE, open = TRUE, - package_name = basename(path), without_comments = FALSE, ...) -} -\arguments{ -\item{path}{Name of the folder to create the package in. This will also be -used as the package name.} - -\item{check_name}{When using this function in the console, you can prevent -the package name from being checked.} - -\item{open}{boolean open the created project} - -\item{package_name}{package name to use} - -\item{without_comments}{boolean start project without golem comments} - -\item{...}{not used} -} -\description{ -Create a package for Shiny App using golem -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/create_golem.R +\name{create_golem} +\alias{create_golem} +\title{Create a package for Shiny App using golem} +\usage{ +create_golem(path, check_name = TRUE, open = TRUE, + package_name = basename(path), without_comments = FALSE, ...) +} +\arguments{ +\item{path}{Name of the folder to create the package in. This will also be +used as the package name.} + +\item{check_name}{When using this function in the console, you can prevent +the package name from being checked.} + +\item{open}{boolean open the created project} + +\item{package_name}{package name to use} + +\item{without_comments}{boolean start project without golem comments} + +\item{...}{not used} +} +\description{ +Create a package for Shiny App using golem +} diff --git a/man/detach_all_attached.Rd b/man/detach_all_attached.Rd index 9e9ce540..3e12756f 100644 --- a/man/detach_all_attached.Rd +++ b/man/detach_all_attached.Rd @@ -1,11 +1,11 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reload.R -\name{detach_all_attached} -\alias{detach_all_attached} -\title{Detach all attached package} -\usage{ -detach_all_attached() -} -\description{ -Detach all attached package -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reload.R +\name{detach_all_attached} +\alias{detach_all_attached} +\title{Detach all attached package} +\usage{ +detach_all_attached() +} +\description{ +Detach all attached package +} diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index d59eab69..3cb7c0e6 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,60 +1,60 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{add_dockerfile} -\alias{add_dockerfile} -\alias{add_dockerfile_shinyproxy} -\alias{add_dockerfile_heroku} -\title{Create a Dockerfile for Shiny App} -\usage{ -add_dockerfile(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, - host = "0.0.0.0") - -add_dockerfile_shinyproxy(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) - -add_dockerfile_heroku(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) -} -\arguments{ -\item{input}{path to the DESCRIPTION file to use as an input.} - -\item{output}{name of the Dockerfile output.} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: -with \code{R.Version()$major} and \code{R.Version()$minor}.} - -\item{as}{The AS of the Dockerfile. Default it NULL.} - -\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. -Default is 80.} - -\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. -Default is 0.0.0.0.} -} -\description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and -\code{add_dockerfile_heroku()} creates platform specific Dockerfile. -} -\examples{ -\donttest{ -# Add a standard Dockerfile -if (interactive()){ - add_dockerfile() -} -# Add a Dockerfile for ShinyProxy -if (interactive()){ - add_dockerfile_shinyproxy() -} -# Add a Dockerfile for Heroku -if (interactive()){ - add_dockerfile_heroku() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{add_dockerfile} +\alias{add_dockerfile} +\alias{add_dockerfile_shinyproxy} +\alias{add_dockerfile_heroku} +\title{Create a Dockerfile for Shiny App} +\usage{ +add_dockerfile(input = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, + host = "0.0.0.0") + +add_dockerfile_shinyproxy(input = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL) + +add_dockerfile_heroku(input = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", + R.Version()$major, ".", R.Version()$minor), as = NULL) +} +\arguments{ +\item{input}{path to the DESCRIPTION file to use as an input.} + +\item{output}{name of the Dockerfile output.} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: +with \code{R.Version()$major} and \code{R.Version()$minor}.} + +\item{as}{The AS of the Dockerfile. Default it NULL.} + +\item{port}{The \code{options('shiny.port')} on which to run the Shiny App. +Default is 80.} + +\item{host}{The \code{options('shiny.host')} on which to run the Shiny App. +Default is 0.0.0.0.} +} +\description{ +Build a container containing your Shiny App. \code{add_dockerfile()} creates +a "classical" Dockerfile, while \code{add_dockerfile_shinyproxy()} and +\code{add_dockerfile_heroku()} creates platform specific Dockerfile. +} +\examples{ +\donttest{ +# Add a standard Dockerfile +if (interactive()){ + add_dockerfile() +} +# Add a Dockerfile for ShinyProxy +if (interactive()){ + add_dockerfile_shinyproxy() +} +# 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 62ec1136..d6d11d24 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -1,15 +1,15 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reload.R -\name{document_and_reload} -\alias{document_and_reload} -\title{Document and reload your package} -\usage{ -document_and_reload(pkg = get_golem_wd()) -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} -} -\description{ -This function calls \code{rstudioapi::documentSaveAll()}, -\code{roxygen2::roxygenise()} and \code{pkgload::load_all()}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reload.R +\name{document_and_reload} +\alias{document_and_reload} +\title{Document and reload your package} +\usage{ +document_and_reload(pkg = get_golem_wd()) +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} +} +\description{ +This function calls \code{rstudioapi::documentSaveAll()}, +\code{roxygen2::roxygenise()} and \code{pkgload::load_all()}. +} diff --git a/man/favicon.Rd b/man/favicon.Rd index 80839ef5..8150b790 100644 --- a/man/favicon.Rd +++ b/man/favicon.Rd @@ -1,34 +1,34 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_favicon.R -\name{use_favicon} -\alias{use_favicon} -\alias{remove_favicon} -\alias{favicon} -\title{Add a favicon to your shinyapp} -\usage{ -use_favicon(path, pkg = get_golem_wd()) - -remove_favicon(path = "inst/app/www/favicon.ico") - -favicon(ico = "www/favicon.ico", rel = "shortcut icon") -} -\arguments{ -\item{path}{Path to your favicon file (.ico or .png)} - -\item{pkg}{Path to the root of the package. Default is \code{"."}} - -\item{ico}{path to favicon file} - -\item{rel}{rel} -} -\description{ -This function adds the favicon from \code{ico} to your shiny app. -} -\examples{ -\donttest{ -if (interactive()){ - use_favicon() - use_favicon(path='path/to/your/favicon.ico') -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_favicon.R +\name{use_favicon} +\alias{use_favicon} +\alias{remove_favicon} +\alias{favicon} +\title{Add a favicon to your shinyapp} +\usage{ +use_favicon(path, pkg = get_golem_wd()) + +remove_favicon(path = "inst/app/www/favicon.ico") + +favicon(ico = "www/favicon.ico", rel = "shortcut icon") +} +\arguments{ +\item{path}{Path to your favicon file (.ico or .png)} + +\item{pkg}{Path to the root of the package. Default is \code{"."}} + +\item{ico}{path to favicon file} + +\item{rel}{rel} +} +\description{ +This function adds the favicon from \code{ico} to your shiny app. +} +\examples{ +\donttest{ +if (interactive()){ + use_favicon() + use_favicon(path='path/to/your/favicon.ico') +} +} +} diff --git a/man/file_creation.Rd b/man/file_creation.Rd index d0835101..dea0be83 100644 --- a/man/file_creation.Rd +++ b/man/file_creation.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_r_files.R -\name{add_fct} -\alias{add_fct} -\alias{add_utils} -\title{Add fct_ and utils_ files} -\usage{ -add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) - -add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) -} -\arguments{ -\item{name}{The name of the file} - -\item{module}{If not NULL, the file will be module specific in the naming (you don't need to add the leading \code{mod_})} - -\item{pkg}{The working directory} - -\item{open}{Should the file be opened once created?} - -\item{dir_create}{Should the folder be created if it doesn't exist?} -} -\description{ -These function adds files in the R/ folder -that starts either with fct_ or with utils_ -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_r_files.R +\name{add_fct} +\alias{add_fct} +\alias{add_utils} +\title{Add fct_ and utils_ files} +\usage{ +add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) + +add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) +} +\arguments{ +\item{name}{The name of the file} + +\item{module}{If not NULL, the file will be module specific in the naming (you don't need to add the leading \code{mod_})} + +\item{pkg}{The working directory} + +\item{open}{Should the file be opened once created?} + +\item{dir_create}{Should the folder be created if it doesn't exist?} +} +\description{ +These function adds files in the R/ folder +that starts either with fct_ or with utils_ +} diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index 23cb45f5..8a6e3407 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -1,30 +1,30 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/desc.R -\name{fill_desc} -\alias{fill_desc} -\title{Fill your description} -\usage{ -fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, - author_last_name, author_email, repo_url = NULL, - pkg = get_golem_wd()) -} -\arguments{ -\item{pkg_name}{The name of the package} - -\item{pkg_title}{The title of the package} - -\item{pkg_description}{Description of the package} - -\item{author_first_name}{First Name of the author} - -\item{author_last_name}{Last Name of the author} - -\item{author_email}{Email of the author} - -\item{repo_url}{URL (if needed)} - -\item{pkg}{Path to look for the DESCRIPTION} -} -\description{ -Fill your description -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/desc.R +\name{fill_desc} +\alias{fill_desc} +\title{Fill your description} +\usage{ +fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, + author_last_name, author_email, repo_url = NULL, + pkg = get_golem_wd()) +} +\arguments{ +\item{pkg_name}{The name of the package} + +\item{pkg_title}{The title of the package} + +\item{pkg_description}{Description of the package} + +\item{author_first_name}{First Name of the author} + +\item{author_last_name}{Last Name of the author} + +\item{author_email}{Email of the author} + +\item{repo_url}{URL (if needed)} + +\item{pkg}{Path to look for the DESCRIPTION} +} +\description{ +Fill your description +} diff --git a/man/get_dependencies.Rd b/man/get_dependencies.Rd index 82c933ff..bbd90470 100644 --- a/man/get_dependencies.Rd +++ b/man/get_dependencies.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_dependencies.R -\name{get_dependencies} -\alias{get_dependencies} -\title{Return all package dependencies from current package} -\usage{ -get_dependencies(path = "DESCRIPTION", pkg = get_golem_wd(), - dput = FALSE, field = c("Imports")) -} -\arguments{ -\item{path}{path to the DESCRIPTION file} - -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{dput}{if TRUE return a dput output instead of character vector} - -\item{field}{DESCRIPTION fields to parse. Default is Import} -} -\description{ -Return all package dependencies from current package -} -\examples{ -\donttest{ -if (interactive()){ - get_dependencies() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_dependencies.R +\name{get_dependencies} +\alias{get_dependencies} +\title{Return all package dependencies from current package} +\usage{ +get_dependencies(path = "DESCRIPTION", pkg = get_golem_wd(), + dput = FALSE, field = c("Imports")) +} +\arguments{ +\item{path}{path to the DESCRIPTION file} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dput}{if TRUE return a dput output instead of character vector} + +\item{field}{DESCRIPTION fields to parse. Default is Import} +} +\description{ +Return all package dependencies from current package +} +\examples{ +\donttest{ +if (interactive()){ + get_dependencies() +} +} +} diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index 1676a32c..d580f9c9 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -1,16 +1,16 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/with_opt.R -\name{get_golem_options} -\alias{get_golem_options} -\title{Get all or one golem options} -\usage{ -get_golem_options(which = NULL) -} -\arguments{ -\item{which}{NULL (default), or the name of an option} -} -\description{ -This function is to be used inside the -server and UI from your app, in order to call the -parameters passed to \code{run_app()}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{get_golem_options} +\alias{get_golem_options} +\title{Get all or one golem options} +\usage{ +get_golem_options(which = NULL) +} +\arguments{ +\item{which}{NULL (default), or the name of an option} +} +\description{ +This function is to be used inside the +server and UI from your app, in order to call the +parameters passed to \code{run_app()}. +} diff --git a/man/go_to.Rd b/man/go_to.Rd index 4849316e..903fddc1 100644 --- a/man/go_to.Rd +++ b/man/go_to.Rd @@ -1,16 +1,16 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addins.R -\name{go_to} -\alias{go_to} -\title{{golem} addins} -\usage{ -go_to(file, wd = golem::get_golem_wd()) -} -\arguments{ -\item{file}{Name of the file to be loaded from the \code{dev} directory.} - -\item{wd}{Working directory of the current golem package.} -} -\description{ -Addins to go to common files used in developing a \code{{golem}} package. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addins.R +\name{go_to} +\alias{go_to} +\title{{golem} addins} +\usage{ +go_to(file, wd = golem::get_golem_wd()) +} +\arguments{ +\item{file}{Name of the file to be loaded from the \code{dev} directory.} + +\item{wd}{Working directory of the current golem package.} +} +\description{ +Addins to go to common files used in developing a \code{{golem}} package. +} diff --git a/man/golem.Rd b/man/golem.Rd index d55a1d1f..f41867c2 100644 --- a/man/golem.Rd +++ b/man/golem.Rd @@ -1,35 +1,35 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/golem-package.R -\docType{package} -\name{golem} -\alias{golem} -\alias{golem-package} -\title{A package for building Shiny App} -\description{ -Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. -} -\seealso{ -Useful links: -\itemize{ - \item \url{https://github.com/ThinkR-open/golem} - \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} -} - -} -\author{ -\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) - -Authors: -\itemize{ - \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) - \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) - \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) -} - -Other contributors: -\itemize{ - \item Novica Nakov \email{nnovica@gmail.com} [contributor] - \item ThinkR [copyright holder] -} - -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/golem-package.R +\docType{package} +\name{golem} +\alias{golem} +\alias{golem-package} +\title{A package for building Shiny App} +\description{ +Read more about building big shiny apps at \url{https://thinkr-open.github.io/building-shiny-apps-workflow/}. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/ThinkR-open/golem} + \item Report bugs at \url{https://github.com/ThinkR-open/golem/issues} +} + +} +\author{ +\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) + +Authors: +\itemize{ + \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) + \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) + \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) +} + +Other contributors: +\itemize{ + \item Novica Nakov \email{nnovica@gmail.com} [contributor] + \item ThinkR [copyright holder] +} + +} diff --git a/man/golem_js.Rd b/man/golem_js.Rd index fbce52fc..f1c87382 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -1,41 +1,41 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/js.R -\name{activate_js} -\alias{activate_js} -\alias{invoke_js} -\title{Interact with JavaScript built-in Functions} -\usage{ -activate_js() - -invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) -} -\arguments{ -\item{fun}{JS function to be invoked.} - -\item{...}{JSON-like messages to be sent to the triggered JS function} - -\item{session}{The shiny session within which to call \code{sendCustomMessage}. - -\describe{ -\item{show}{Show an element with the jQuery selector provided.} -\item{hide}{Hide an element with the jQuery selector provided.} -\item{showid}{Show an element with the id provided.} -\item{hideid}{Hide an element with the id provided.} -\item{showclass}{Same as showid, but with class.} -\item{hideclass}{Same as hideid, but with class.} -\item{showhref}{Same as showid, but with \code{a[href*=}} -\item{hidehref}{Same as hideid, but with \code{a[href*=}} -\item{clickon}{Click on an element. The full jQuery selector has to be used.} -\item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} -\item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} -\item{alert}{Open an alert box with the message provided.} -\item{prompt}{Open a prompt box with the message provided.} -\item{confirm}{Open a confirm box with the message provided.} -}} -} -\description{ -\code{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 \code{invoke_js}. \code{invoke_js} can also be used -to launch any JS function created inside a Shiny JavaScript handler. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/js.R +\name{activate_js} +\alias{activate_js} +\alias{invoke_js} +\title{Interact with JavaScript built-in Functions} +\usage{ +activate_js() + +invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) +} +\arguments{ +\item{fun}{JS function to be invoked.} + +\item{...}{JSON-like messages to be sent to the triggered JS function} + +\item{session}{The shiny session within which to call \code{sendCustomMessage}. + +\describe{ +\item{show}{Show an element with the jQuery selector provided.} +\item{hide}{Hide an element with the jQuery selector provided.} +\item{showid}{Show an element with the id provided.} +\item{hideid}{Hide an element with the id provided.} +\item{showclass}{Same as showid, but with class.} +\item{hideclass}{Same as hideid, but with class.} +\item{showhref}{Same as showid, but with \code{a[href*=}} +\item{hidehref}{Same as hideid, but with \code{a[href*=}} +\item{clickon}{Click on an element. The full jQuery selector has to be used.} +\item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} +\item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} +\item{alert}{Open an alert box with the message provided.} +\item{prompt}{Open a prompt box with the message provided.} +\item{confirm}{Open a confirm box with the message provided.} +}} +} +\description{ +\code{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 \code{invoke_js}. \code{invoke_js} can also be used +to launch any JS function created inside a Shiny JavaScript handler. +} diff --git a/man/golem_wd.Rd b/man/golem_wd.Rd index 92655d8f..e4bd279d 100644 --- a/man/golem_wd.Rd +++ b/man/golem_wd.Rd @@ -1,29 +1,29 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{get_golem_wd} -\alias{get_golem_wd} -\alias{set_golem_wd} -\title{Get and set \code{{golem}} working directory} -\usage{ -get_golem_wd() - -set_golem_wd(path, talkative = TRUE) -} -\arguments{ -\item{path}{The path to set the golem working directory. -Note that it will be passed to \code{normalizePath}.} - -\item{talkative}{Should the function print where the -new path is defined?} -} -\value{ -The path to the working directory. -} -\description{ -Many \code{{golem}} functions rely on a specific working directory, -most of the time the root of the package. This working directory -is set by \code{set_golem_options} or the first time you create a file. -It default to \code{"."}, the current directory when starting a golem. -You can use these two functions if you need to manipulate this -directory. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{get_golem_wd} +\alias{get_golem_wd} +\alias{set_golem_wd} +\title{Get and set \code{{golem}} working directory} +\usage{ +get_golem_wd() + +set_golem_wd(path, talkative = TRUE) +} +\arguments{ +\item{path}{The path to set the golem working directory. +Note that it will be passed to \code{normalizePath}.} + +\item{talkative}{Should the function print where the +new path is defined?} +} +\value{ +The path to the working directory. +} +\description{ +Many \code{{golem}} functions rely on a specific working directory, +most of the time the root of the package. This working directory +is set by \code{set_golem_options} or the first time you create a file. +It default to \code{"."}, the current directory when starting a golem. +You can use these two functions if you need to manipulate this +directory. +} diff --git a/man/insert_ns.Rd b/man/insert_ns.Rd index 01cb8e51..d7d0f9f6 100644 --- a/man/insert_ns.Rd +++ b/man/insert_ns.Rd @@ -1,11 +1,11 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addin_functions.R -\name{insert_ns} -\alias{insert_ns} -\title{Shorcut to insert NS} -\usage{ -insert_ns() -} -\description{ -Shorcut to insert NS -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addin_functions.R +\name{insert_ns} +\alias{insert_ns} +\title{Shorcut to insert NS} +\usage{ +insert_ns() +} +\description{ +Shorcut to insert NS +} diff --git a/man/made_dev.Rd b/man/made_dev.Rd index b04e97ed..43a56371 100644 --- a/man/made_dev.Rd +++ b/man/made_dev.Rd @@ -1,28 +1,28 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{cat_dev} -\alias{cat_dev} -\alias{print_dev} -\alias{message_dev} -\alias{warning_dev} -\alias{browser_dev} -\title{Functions already made dev dependent} -\usage{ -cat_dev(...) - -print_dev(...) - -message_dev(...) - -warning_dev(...) - -browser_dev(...) -} -\arguments{ -\item{...}{\R objects (see \sQuote{Details} for the types of objects - allowed).} -} -\description{ -This functions will be run only if \code{golem::app_dev()} -returns TRUE. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{cat_dev} +\alias{cat_dev} +\alias{print_dev} +\alias{message_dev} +\alias{warning_dev} +\alias{browser_dev} +\title{Functions already made dev dependent} +\usage{ +cat_dev(...) + +print_dev(...) + +message_dev(...) + +warning_dev(...) + +browser_dev(...) +} +\arguments{ +\item{...}{\R objects (see \sQuote{Details} for the types of objects + allowed).} +} +\description{ +This functions will be run only if \code{golem::app_dev()} +returns TRUE. +} diff --git a/man/make_dev.Rd b/man/make_dev.Rd index c9bcfd5f..487ca1c9 100644 --- a/man/make_dev.Rd +++ b/man/make_dev.Rd @@ -1,15 +1,15 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{make_dev} -\alias{make_dev} -\title{Make a function dependent to dev mode} -\usage{ -make_dev(fun) -} -\arguments{ -\item{fun}{A function} -} -\description{ -The function returned will be run only if \code{golem::app_dev()} -returns TRUE. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{make_dev} +\alias{make_dev} +\title{Make a function dependent to dev mode} +\usage{ +make_dev(fun) +} +\arguments{ +\item{fun}{A function} +} +\description{ +The function returned will be run only if \code{golem::app_dev()} +returns TRUE. +} diff --git a/man/prod.Rd b/man/prod.Rd index 428ecb0b..9255c15b 100644 --- a/man/prod.Rd +++ b/man/prod.Rd @@ -1,17 +1,17 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/make_dev.R -\name{app_prod} -\alias{app_prod} -\alias{app_dev} -\title{Is the app in dev mode or prod mode?} -\usage{ -app_prod() - -app_dev() -} -\value{ -\code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} -} -\description{ -Is the app in dev mode or prod mode? -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/make_dev.R +\name{app_prod} +\alias{app_prod} +\alias{app_dev} +\title{Is the app in dev mode or prod mode?} +\usage{ +app_prod() + +app_dev() +} +\value{ +\code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} +} +\description{ +Is the app in dev mode or prod mode? +} diff --git a/man/rstudio_deploy.Rd b/man/rstudio_deploy.Rd index e0c2189c..e848f0d4 100644 --- a/man/rstudio_deploy.Rd +++ b/man/rstudio_deploy.Rd @@ -1,42 +1,42 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{add_rstudioconnect_file} -\alias{add_rstudioconnect_file} -\alias{add_rconnect_file} -\alias{add_shinyappsio_file} -\alias{add_shinyserver_file} -\title{Add an app.R at the root of your package to deploy on RStudio Connect} -\usage{ -add_rstudioconnect_file(pkg = get_golem_wd(), open = TRUE) - -add_shinyappsio_file(pkg = get_golem_wd(), open = TRUE) - -add_shinyserver_file(pkg = get_golem_wd(), open = TRUE) -} -\arguments{ -\item{pkg}{Where to put the app.R.} - -\item{open}{Open the file} -} -\description{ -Add an app.R at the root of your package to deploy on RStudio Connect -} -\note{ -In previous versions, this function was called add_rconnect_file. -} -\examples{ -\donttest{ -# Add a file for Connect -if (interactive()){ - add_rstudioconnect_file() -} -# Add a file for Shiny Server -if (interactive()){ - add_shinyserver_file() -} -# Add a file for Shinyapps.io -if (interactive()){ - add_shinyappsio_file() -} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_deploy_helpers.R +\name{add_rstudioconnect_file} +\alias{add_rstudioconnect_file} +\alias{add_rconnect_file} +\alias{add_shinyappsio_file} +\alias{add_shinyserver_file} +\title{Add an app.R at the root of your package to deploy on RStudio Connect} +\usage{ +add_rstudioconnect_file(pkg = get_golem_wd(), open = TRUE) + +add_shinyappsio_file(pkg = get_golem_wd(), open = TRUE) + +add_shinyserver_file(pkg = get_golem_wd(), open = TRUE) +} +\arguments{ +\item{pkg}{Where to put the app.R.} + +\item{open}{Open the file} +} +\description{ +Add an app.R at the root of your package to deploy on RStudio Connect +} +\note{ +In previous versions, this function was called add_rconnect_file. +} +\examples{ +\donttest{ +# Add a file for Connect +if (interactive()){ + add_rstudioconnect_file() +} +# Add a file for Shiny Server +if (interactive()){ + add_shinyserver_file() +} +# Add a file for Shinyapps.io +if (interactive()){ + add_shinyappsio_file() +} +} +} diff --git a/man/set_golem_options.Rd b/man/set_golem_options.Rd index 9a7e37fb..5942ae8f 100644 --- a/man/set_golem_options.Rd +++ b/man/set_golem_options.Rd @@ -1,22 +1,22 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\title{{golem} options} -\usage{ -set_golem_options(golem_name = pkgload::pkg_name(), - golem_version = pkgload::pkg_version(), - golem_wd = pkgload::pkg_path(), app_prod = FALSE) -} -\arguments{ -\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?} -} -\description{ -Set a series of options to be used internally by \code{{golem}}. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{set_golem_options} +\alias{set_golem_options} +\title{{golem} options} +\usage{ +set_golem_options(golem_name = pkgload::pkg_name(), + golem_version = pkgload::pkg_version(), + golem_wd = pkgload::pkg_path(), app_prod = FALSE) +} +\arguments{ +\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?} +} +\description{ +Set a series of options to be used internally by \code{{golem}}. +} diff --git a/man/testhelpers.Rd b/man/testhelpers.Rd index eaee8929..2ca76003 100644 --- a/man/testhelpers.Rd +++ b/man/testhelpers.Rd @@ -1,32 +1,32 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/test_helpers.R -\name{expect_shinytag} -\alias{expect_shinytag} -\alias{expect_shinytaglist} -\alias{expect_html_equal} -\title{Test helpers} -\usage{ -expect_shinytag(object) - -expect_shinytaglist(object) - -expect_html_equal(ui, html) -} -\arguments{ -\item{object}{the object to test} - -\item{ui}{output of an UI function} - -\item{html}{html file to compare to ui} -} -\value{ -A testthat result -} -\description{ -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)) -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/test_helpers.R +\name{expect_shinytag} +\alias{expect_shinytag} +\alias{expect_shinytaglist} +\alias{expect_html_equal} +\title{Test helpers} +\usage{ +expect_shinytag(object) + +expect_shinytaglist(object) + +expect_html_equal(ui, html) +} +\arguments{ +\item{object}{the object to test} + +\item{ui}{output of an UI function} + +\item{html}{html file to compare to ui} +} +\value{ +A testthat result +} +\description{ +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/man/use_recommended.Rd b/man/use_recommended.Rd index 616891f2..b28f3489 100644 --- a/man/use_recommended.Rd +++ b/man/use_recommended.Rd @@ -1,23 +1,23 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_recommended.R -\name{use_recommended_deps} -\alias{use_recommended_deps} -\alias{use_recommended_tests} -\title{Add recommended elements} -\usage{ -use_recommended_deps(pkg = get_golem_wd(), recommended = c("shiny", - "DT", "attempt", "glue", "htmltools", "golem")) - -use_recommended_tests(pkg = get_golem_wd()) -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} - -\item{recommended}{A vector of recommended packages.} -} -\description{ -\describe{ -\item{use_recommended_deps}{Adds \code{shiny}, \code{DT}, \code{attempt}, \code{glue}, \code{golem}, \code{htmltools} to dependencies} -\item{use_recommended_tests}{Adds a test folder and copy the golem tests} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_recommended.R +\name{use_recommended_deps} +\alias{use_recommended_deps} +\alias{use_recommended_tests} +\title{Add recommended elements} +\usage{ +use_recommended_deps(pkg = get_golem_wd(), recommended = c("shiny", + "DT", "attempt", "glue", "htmltools", "golem")) + +use_recommended_tests(pkg = get_golem_wd()) +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{recommended}{A vector of recommended packages.} +} +\description{ +\describe{ +\item{use_recommended_deps}{Adds \code{shiny}, \code{DT}, \code{attempt}, \code{glue}, \code{golem}, \code{htmltools} to dependencies} +\item{use_recommended_tests}{Adds a test folder and copy the golem tests} +} +} diff --git a/man/utils_files.Rd b/man/utils_files.Rd index b533241d..69bfabaa 100644 --- a/man/utils_files.Rd +++ b/man/utils_files.Rd @@ -1,20 +1,20 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use_utils.R -\name{use_utils_ui} -\alias{use_utils_ui} -\alias{use_utils_server} -\title{Use the utils files} -\usage{ -use_utils_ui(pkg = get_golem_wd()) - -use_utils_server(pkg = get_golem_wd()) -} -\arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} -} -\description{ -\describe{ -\item{use_utils_ui}{Copies the golem_utils_ui.R to the R folder.} -\item{use_utils_server}{Copies the golem_utils_server.R to the R folder.} -} -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_utils.R +\name{use_utils_ui} +\alias{use_utils_ui} +\alias{use_utils_server} +\title{Use the utils files} +\usage{ +use_utils_ui(pkg = get_golem_wd()) + +use_utils_server(pkg = get_golem_wd()) +} +\arguments{ +\item{pkg}{Path to the root of the package. Default is \code{"."}.} +} +\description{ +\describe{ +\item{use_utils_ui}{Copies the golem_utils_ui.R to the R folder.} +\item{use_utils_server}{Copies the golem_utils_server.R to the R folder.} +} +} diff --git a/man/with_golem_options.Rd b/man/with_golem_options.Rd index 3ea7adf4..83824abf 100644 --- a/man/with_golem_options.Rd +++ b/man/with_golem_options.Rd @@ -1,24 +1,24 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/with_opt.R -\name{with_golem_options} -\alias{with_golem_options} -\title{Add Golem options to a Shiny App} -\usage{ -with_golem_options(app, golem_opts) -} -\arguments{ -\item{app}{the app object.} - -\item{golem_opts}{A list of Options to be added to the app} -} -\value{ -a shiny.appObj object -} -\description{ -Add Golem options to a Shiny App -} -\note{ -You'll probably never have to write this function -as it is included in the golem template created on -launch. -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{with_golem_options} +\alias{with_golem_options} +\title{Add Golem options to a Shiny App} +\usage{ +with_golem_options(app, golem_opts) +} +\arguments{ +\item{app}{the app object.} + +\item{golem_opts}{A list of Options to be added to the app} +} +\value{ +a shiny.appObj object +} +\description{ +Add Golem options to a Shiny App +} +\note{ +You'll probably never have to write this function +as it is included in the golem template created on +launch. +} From ae403ae9a7cf2ca4c946837b231a7c7c6a10c6e2 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 22:47:47 +0200 Subject: [PATCH 095/211] refactored the golem_name and app_sys functions --- NAMESPACE | 23 ++--------------------- R/options.R | 23 ++++++++++++++++++++--- R/utils.R | 5 ----- inst/shinyexample/R/app_ui.R | 6 ++++-- man/golem_name.Rd | 21 +++++++++++++++++++++ 5 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 man/golem_name.Rd diff --git a/NAMESPACE b/NAMESPACE index 5916f109..843e986e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,6 +17,7 @@ export(add_ui_server_files) export(add_utils) export(app_dev) export(app_prod) +export(app_sys) export(browser_button) export(browser_dev) export(cat_dev) @@ -28,15 +29,10 @@ export(expect_shinytag) export(expect_shinytaglist) export(favicon) export(fill_desc) -<<<<<<< HEAD -export(get_dependencies) -export(get_golem_options) -export(get_golem_wd) -======= +export(get_golem_name) export(get_golem_options) export(get_golem_wd) export(get_sysreqs) ->>>>>>> c6b7641a205425d25ec27b011f3cde8a97e49190 export(insert_ns) export(invoke_js) export(make_dev) @@ -58,28 +54,19 @@ importFrom(attempt,without_warning) importFrom(cli,cat_bullet) importFrom(cli,cat_line) importFrom(cli,cat_rule) -<<<<<<< HEAD -importFrom(desc,description) -======= importFrom(desc,desc_get_deps) importFrom(desc,description) importFrom(dockerfiler,Dockerfile) ->>>>>>> c6b7641a205425d25ec27b011f3cde8a97e49190 importFrom(glue,glue) importFrom(htmltools,includeScript) importFrom(htmltools,save_html) importFrom(htmltools,tags) -<<<<<<< HEAD -importFrom(pkgload,load_all) -importFrom(pkgload,pkg_name) -======= importFrom(jsonlite,fromJSON) importFrom(magrittr,"%>%") importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(remotes,dev_package_deps) importFrom(remotes,package_deps) ->>>>>>> c6b7641a205425d25ec27b011f3cde8a97e49190 importFrom(rlang,enquo) importFrom(rlang,is_empty) importFrom(roxygen2,roxygenise) @@ -98,16 +85,10 @@ importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) importFrom(usethis,use_testthat) importFrom(utils,capture.output) -<<<<<<< HEAD -importFrom(utils,file.edit) -importFrom(utils,getFromNamespace) -importFrom(utils,installed.packages) -======= importFrom(utils,download.file) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) importFrom(utils,installed.packages) importFrom(utils,packageVersion) ->>>>>>> c6b7641a205425d25ec27b011f3cde8a97e49190 importFrom(utils,sessionInfo) importFrom(yesno,yesno) diff --git a/R/options.R b/R/options.R index d06756e0..7f1a33ac 100644 --- a/R/options.R +++ b/R/options.R @@ -77,8 +77,25 @@ set_golem_wd <- function( #' @return The name of the golem. #' @export #' @rdname golem_name - +#' @importFrom pkgload pkg_name get_golem_name <- function(){ - getOption("golem_name") -} \ No newline at end of file + if (is.null(getOption("golem_name"))){ + options("golem.pkg.name" = pkg_name()) + } + getOption("golem.pkg.name") +} + +#' A wrapper around `system.file(..., package = get_golem_name())` +#' +#' This function allows to use `app_sys()` instead of using the +#' `system.file(, package = "pkg")`. +#' +#' @return The path to the current golem. +#' @export +#' @rdname golem_name + +#' @export +app_sys <- function() { + system.file(..., package = get_golem_name()) +} diff --git a/R/utils.R b/R/utils.R index 29351a89..c06d58ea 100644 --- a/R/utils.R +++ b/R/utils.R @@ -2,11 +2,6 @@ golem_sys <- function(..., lib.loc = NULL, mustWork = FALSE){ system.file(..., package = "golem", lib.loc = lib.loc, mustWork = mustWork) } -#' @export -#' @rdname app_sys -app_sys <- function() { - system.file(..., package = get_golem_name()) -} # from usethis https://github.com/r-lib/usethis/ diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 3765130e..26151246 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -11,19 +11,21 @@ app_ui <- function(request) { } #' @import shiny +#' @importFrom golem app_sys golem_add_external_resources <- function(){ golem::add_resource_path( - 'www', system.file('app/www', package = 'shinyexample') + 'www', app_sys('app/www') ) tags$head( golem::activate_js(), golem::favicon(), + tags$title("shinyexample") # Add here all the external resources # If you have a custom.css in the inst/app/www # Or for example, you can add shinyalert::useShinyalert() here #tags$link(rel="stylesheet", type="text/css", href="www/custom.css") - tags$title("shinyexample") + ) } \ No newline at end of file diff --git a/man/golem_name.Rd b/man/golem_name.Rd new file mode 100644 index 00000000..98caff7f --- /dev/null +++ b/man/golem_name.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{get_golem_name} +\alias{get_golem_name} +\alias{app_sys} +\title{A function to return the golem name to be used elsewhere +in the package.} +\usage{ +get_golem_name() + +app_sys() +} +\value{ +The name of the golem. + +The path to the current golem. +} +\description{ +This function allows to use \code{app_sys()} instead of using the +\code{system.file(, package = "pkg")}. +} From 8d647ea2ad9e20418d00f841d695b96411b20a00 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 22:54:55 +0200 Subject: [PATCH 096/211] =?UTF-8?q?=E2=94=80=E2=94=80=20R=20CMD=20check=20?= =?UTF-8?q?results=20=E2=94=80=E2=94=80=20golem=200.1.0.9500=20=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=20locally,=20hoping=20this=20will?= =?UTF-8?q?=20pass=20travis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- R/options.R | 3 ++- man/golem_name.Rd | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/R/options.R b/R/options.R index 7f1a33ac..a6562c82 100644 --- a/R/options.R +++ b/R/options.R @@ -91,11 +91,12 @@ get_golem_name <- function(){ #' This function allows to use `app_sys()` instead of using the #' `system.file(, package = "pkg")`. #' +#' @inheritParams base::system.file #' @return The path to the current golem. #' @export #' @rdname golem_name #' @export -app_sys <- function() { +app_sys <- function(...) { system.file(..., package = get_golem_name()) } diff --git a/man/golem_name.Rd b/man/golem_name.Rd index 98caff7f..e7f7b956 100644 --- a/man/golem_name.Rd +++ b/man/golem_name.Rd @@ -8,7 +8,12 @@ in the package.} \usage{ get_golem_name() -app_sys() +app_sys(...) +} +\arguments{ +\item{...}{character vectors, specifying subdirectory and file(s) + within some package. The default, none, returns the + root of the package. Wildcards are not supported.} } \value{ The name of the golem. From 4811d8003ccdf22cb61f44275db84dba7d1d5ed1 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 23:17:50 +0200 Subject: [PATCH 097/211] news update and version bump, closes #214 --- NEWS.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index ac0393e0..71253fc0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,7 @@ ## New functions -+ `add_dockerfile()` now use installed pacakge version and explicit System Requirements ++ `add_dockerfile()` was completely refactore. 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_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). @@ -14,9 +14,9 @@ + `add_external_js_file` and `add_external_css_file` are designed to download .js and .css file off the web to the appropriate directory (#130, @zwycl) -## New features - ++ There is now an `app_sys()` function, which is a wrapper around `system.file(..., package = "myapp")` (#207, @novica) +## New features + `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157) @@ -36,6 +36,8 @@ + golem apps now have a title tag in the header by default, (#172, @novica) ++ The `rsconnect` folder is now added to `.Rbuildignore` (#244) + ## Breaking changes + `invoke_js()` now takes a list of elements to send to JS (through `...`) instead of a vector (#155, @zwycl) @@ -58,10 +60,16 @@ + 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) + ++ Bug fix of the pkgdown website (#180, ) + ## Internal changes + We no longer depend on `{stringr}` (#201, @TomerPacific) ++ get_golem_wd() is now used everywhere in `{golem}` (#237, @felixgolcher) + # golem 0.1.0 - CRAN release candidate, v2 ## New Functions From cc6ce097113400adc6f66c7f7a64597dd9187d74 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 23 Oct 2019 23:23:58 +0200 Subject: [PATCH 098/211] rebuild doc --- man/dockerfiles.Rd | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index aae394ef..910db9af 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -6,23 +6,6 @@ \alias{add_dockerfile_heroku} \title{Create a Dockerfile for Shiny App} \usage{ -<<<<<<< HEAD -add_dockerfile(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, - host = "0.0.0.0") - -add_dockerfile_shinyproxy(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) - -add_dockerfile_heroku(input = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/tidyverse:", - R.Version()$major, ".", R.Version()$minor), as = NULL) -} -\arguments{ -\item{input}{path to the DESCRIPTION file to use as an input.} -======= add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, @@ -43,17 +26,12 @@ add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} ->>>>>>> c6b7641a205425d25ec27b011f3cde8a97e49190 \item{output}{name of the Dockerfile output.} \item{pkg}{Path to the root of the package. Default is \code{"."}.} -<<<<<<< HEAD -\item{from}{The FROM of the Dockerfile. Default is FROM rocker/tidyverse: -======= \item{from}{The FROM of the Dockerfile. Default is FROM rocker/r-ver: ->>>>>>> c6b7641a205425d25ec27b011f3cde8a97e49190 with \code{R.Version()$major} and \code{R.Version()$minor}.} \item{as}{The AS of the Dockerfile. Default it NULL.} @@ -63,15 +41,12 @@ Default is 80.} \item{host}{The \code{options('shiny.host')} on which to run the Shiny App. Default is 0.0.0.0.} -<<<<<<< HEAD -======= \item{sysreqs}{boolean to check the system requirements} \item{repos}{character vector, the base URL of the repositories} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} ->>>>>>> c6b7641a205425d25ec27b011f3cde8a97e49190 } \description{ Build a container containing your Shiny App. \code{add_dockerfile()} creates From 8013208ca010f35f576deced1587f33d3b9cec8b Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 27 Oct 2019 11:15:54 +0100 Subject: [PATCH 099/211] fix #248 --- NAMESPACE | 1 + R/create_golem.R | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 843e986e..b42138d1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -70,6 +70,7 @@ importFrom(remotes,package_deps) importFrom(rlang,enquo) importFrom(rlang,is_empty) importFrom(roxygen2,roxygenise) +importFrom(rstudioapi,getActiveProject) importFrom(rstudioapi,getSourceEditorContext) importFrom(rstudioapi,isAvailable) importFrom(rstudioapi,modifyRange) diff --git a/R/create_golem.R b/R/create_golem.R index f8082e3e..23279808 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -15,6 +15,7 @@ #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable #' @importFrom rstudioapi openProject +#' @importFrom rstudioapi getActiveProject #' @export create_golem <- function( path, @@ -25,6 +26,14 @@ create_golem <- function( ... ) { + if (path == '.' & package_name == basename(path)){ + if ( rstudioapi::isAvailable()){ + package_name <- basename(rstudioapi::getActiveProject()) + } + } + + + if (check_name){ getFromNamespace("check_package_name", "usethis")(package_name) } From 14540682e749906608d833e0f3109579cf0ae4d2 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 27 Oct 2019 17:32:57 +0100 Subject: [PATCH 100/211] use of basename(getwd()) instead of getActiveProject() --- NAMESPACE | 1 - R/create_golem.R | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index b42138d1..843e986e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -70,7 +70,6 @@ importFrom(remotes,package_deps) importFrom(rlang,enquo) importFrom(rlang,is_empty) importFrom(roxygen2,roxygenise) -importFrom(rstudioapi,getActiveProject) importFrom(rstudioapi,getSourceEditorContext) importFrom(rstudioapi,isAvailable) importFrom(rstudioapi,modifyRange) diff --git a/R/create_golem.R b/R/create_golem.R index 23279808..5d293fd2 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -15,7 +15,6 @@ #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable #' @importFrom rstudioapi openProject -#' @importFrom rstudioapi getActiveProject #' @export create_golem <- function( path, @@ -27,9 +26,7 @@ create_golem <- function( ) { if (path == '.' & package_name == basename(path)){ - if ( rstudioapi::isAvailable()){ - package_name <- basename(rstudioapi::getActiveProject()) - } + package_name <- basename(getwd()) } From 00277704cce74c06e99be0b28d9cf9cc76384593 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 28 Oct 2019 09:11:18 +0100 Subject: [PATCH 101/211] update doc --- R/create_golem.R | 7 ++++--- man/create_golem.Rd | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/R/create_golem.R b/R/create_golem.R index 5d293fd2..90f7fac1 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -4,9 +4,10 @@ #' used as the package name. #' @param check_name When using this function in the console, you can prevent #' the package name from being checked. -#' @param open boolean open the created project -#' @param package_name package name to use -#' @param without_comments boolean start project without golem comments +#' @param open Boolean open the created project +#' @param package_name Package name to use.By default it's `basename(path)` but if path == '.' and `package_name` +#' not explicitly given, then `basename(getwd())` will be used. +#' @param without_comments Poolean start project without golem comments #' @param ... not used #' #' @importFrom yesno yesno diff --git a/man/create_golem.Rd b/man/create_golem.Rd index 865a122a..3840de08 100644 --- a/man/create_golem.Rd +++ b/man/create_golem.Rd @@ -14,11 +14,12 @@ used as the package name.} \item{check_name}{When using this function in the console, you can prevent the package name from being checked.} -\item{open}{boolean open the created project} +\item{open}{Boolean open the created project} -\item{package_name}{package name to use} +\item{package_name}{Package name to use.By default it's \code{basename(path)} but if path == '.' and \code{package_name} +not explicitly given, then \code{basename(getwd())} will be used.} -\item{without_comments}{boolean start project without golem comments} +\item{without_comments}{Poolean start project without golem comments} \item{...}{not used} } From 4f1d11eab449dc6b92aba356afc40f57a397a6b6 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 29 Oct 2019 11:17:12 +0100 Subject: [PATCH 102/211] importFrom(golem,app_sys) in NAMESPACE for shinyexample --- inst/shinyexample/NAMESPACE | 13 +++++++------ inst/shinyexample/man/app_ui.Rd | 28 ++++++++++++++-------------- inst/shinyexample/man/run_app.Rd | 28 ++++++++++++++-------------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index 8429ee35..064445b7 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -1,6 +1,7 @@ -# Generated by roxygen2: do not edit by hand - -export(run_app) -import(shiny) -importFrom(golem,with_golem_options) -importFrom(shiny,shinyApp) +# Generated by roxygen2: do not edit by hand + +export(run_app) +import(shiny) +importFrom(golem,app_sys) +importFrom(golem,with_golem_options) +importFrom(shiny,shinyApp) diff --git a/inst/shinyexample/man/app_ui.Rd b/inst/shinyexample/man/app_ui.Rd index bac6b331..6e5abce8 100644 --- a/inst/shinyexample/man/app_ui.Rd +++ b/inst/shinyexample/man/app_ui.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/app_ui.R -\name{app_ui} -\alias{app_ui} -\title{ui} -\usage{ -app_ui(request) -} -\arguments{ -\item{request}{needed for bookmarking} -} -\description{ -ui -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_ui.R +\name{app_ui} +\alias{app_ui} +\title{ui} +\usage{ +app_ui(request) +} +\arguments{ +\item{request}{needed for bookmarking} +} +\description{ +ui +} diff --git a/inst/shinyexample/man/run_app.Rd b/inst/shinyexample/man/run_app.Rd index 50364079..174137cd 100644 --- a/inst/shinyexample/man/run_app.Rd +++ b/inst/shinyexample/man/run_app.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/run_app.R -\name{run_app} -\alias{run_app} -\title{Run the Shiny Application} -\usage{ -run_app(...) -} -\arguments{ -\item{...}{A list of Options to be added to the app} -} -\description{ -Run the Shiny Application -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/run_app.R +\name{run_app} +\alias{run_app} +\title{Run the Shiny Application} +\usage{ +run_app(...) +} +\arguments{ +\item{...}{A list of Options to be added to the app} +} +\description{ +Run the Shiny Application +} From b25032943a262d7f237aa3164b5c07db1165f2d1 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 31 Oct 2019 20:22:14 +0100 Subject: [PATCH 103/211] get_sysreqs now ask 30 by 30 the sysreqs API --- NAMESPACE | 1 + R/get_sysreqs.R | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 843e986e..e07d3e35 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -65,6 +65,7 @@ importFrom(jsonlite,fromJSON) importFrom(magrittr,"%>%") importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) +importFrom(purrr,map_chr) importFrom(remotes,dev_package_deps) importFrom(remotes,package_deps) importFrom(rlang,enquo) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 1549d19d..a4e6786d 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -2,20 +2,43 @@ #' get system requirements #' #' @param packages character vector of packages names +#' @param batch_n number of simultaneous packages to ask #' @param quiet boolean if TRUE the function is quiet +#' #' @importFrom utils download.file #' @importFrom jsonlite fromJSON #' @importFrom remotes package_deps +#' @importFrom purrr map +#' @importFrom magrittr %>% #' @export -get_sysreqs <- function(packages, quiet = TRUE){ +get_sysreqs <- function(packages, quiet = TRUE,batch_n=30){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") # all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages, suggests = FALSE)))), collapse = ",") - all_deps <- paste(unique(c(packages, unlist(remotes::package_deps(packages)$package))), collapse = ",") + all_deps <- sort(unique(c(packages, unlist(remotes::package_deps(packages)$package)))) + all_deps + + split(all_deps, ceiling(seq_along(all_deps)/batch_n)) %>% + map(~get_batch_sysreqs(.x)) %>% + unlist() %>% + unname() + +} + + +get_batch_sysreqs <- function(all_deps){ + + all_deps<-paste(all_deps , collapse = ",") + + url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) out <- jsonlite::fromJSON(path) unlink(path) + + + sort(unique(out[!is.na(out)])) + } From 0c20ccd50cfc8bb3a359fa91b867ec0c7b12954e Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 31 Oct 2019 20:22:14 +0100 Subject: [PATCH 104/211] get_sysreqs now ask 30 by 30 the sysreqs API --- NAMESPACE | 1 + R/get_sysreqs.R | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 843e986e..db2b6413 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -65,6 +65,7 @@ importFrom(jsonlite,fromJSON) importFrom(magrittr,"%>%") importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) +importFrom(purrr,map) importFrom(remotes,dev_package_deps) importFrom(remotes,package_deps) importFrom(rlang,enquo) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 1549d19d..41362cdf 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -2,20 +2,43 @@ #' get system requirements #' #' @param packages character vector of packages names +#' @param batch_n number of simultaneous packages to ask #' @param quiet boolean if TRUE the function is quiet +#' #' @importFrom utils download.file #' @importFrom jsonlite fromJSON #' @importFrom remotes package_deps +#' @importFrom purrr map +#' @importFrom magrittr %>% #' @export -get_sysreqs <- function(packages, quiet = TRUE){ +get_sysreqs <- function(packages, quiet = TRUE,batch_n=30){ # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") # all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages, suggests = FALSE)))), collapse = ",") - all_deps <- paste(unique(c(packages, unlist(remotes::package_deps(packages)$package))), collapse = ",") + all_deps <- sort(unique(c(packages, unlist(remotes::package_deps(packages)$package)))) + all_deps + + split(all_deps, ceiling(seq_along(all_deps)/batch_n)) %>% + map(~get_batch_sysreqs(.x,quiet=quiet)) %>% + unlist() %>% + unname() + +} + + +get_batch_sysreqs <- function(all_deps,quiet=TRUE){ + + all_deps<-paste(all_deps , collapse = ",") + + url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) path <- tempfile() utils::download.file(url, path,mode = "wb",quiet = quiet) out <- jsonlite::fromJSON(path) unlink(path) + + + sort(unique(out[!is.na(out)])) + } From 089e315754835d9c3f5d941315f4523c2f956100 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 31 Oct 2019 21:15:39 +0100 Subject: [PATCH 105/211] fix #260 --- R/get_sysreqs.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 41362cdf..1f660036 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -21,7 +21,9 @@ get_sysreqs <- function(packages, quiet = TRUE,batch_n=30){ split(all_deps, ceiling(seq_along(all_deps)/batch_n)) %>% map(~get_batch_sysreqs(.x,quiet=quiet)) %>% unlist() %>% - unname() + unname() %>% + unique() %>% + sort() } From 3fe044fbfdd690f550524c1ea41c409a9de2f26f Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 1 Nov 2019 22:03:32 +0100 Subject: [PATCH 106/211] close #258 --- R/options.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/options.R b/R/options.R index a6562c82..de7b0807 100644 --- a/R/options.R +++ b/R/options.R @@ -98,5 +98,5 @@ get_golem_name <- function(){ #' @export app_sys <- function(...) { - system.file(..., package = get_golem_name()) + getFromNamespace("shim_system.file", "pkgload")(..., package = get_golem_name()) } From de64785223b5c4ccfade07609fa167b678b18776 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 1 Nov 2019 22:18:16 +0100 Subject: [PATCH 107/211] lf --- inst/shinyexample/NAMESPACE | 12 ++++++------ inst/shinyexample/man/app_ui.Rd | 28 ++++++++++++++-------------- inst/shinyexample/man/run_app.Rd | 28 ++++++++++++++-------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index 8429ee35..e9569e88 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -1,6 +1,6 @@ -# Generated by roxygen2: do not edit by hand - -export(run_app) -import(shiny) -importFrom(golem,with_golem_options) -importFrom(shiny,shinyApp) +# Generated by roxygen2: do not edit by hand + +export(run_app) +import(shiny) +importFrom(golem,with_golem_options) +importFrom(shiny,shinyApp) diff --git a/inst/shinyexample/man/app_ui.Rd b/inst/shinyexample/man/app_ui.Rd index bac6b331..6e5abce8 100644 --- a/inst/shinyexample/man/app_ui.Rd +++ b/inst/shinyexample/man/app_ui.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/app_ui.R -\name{app_ui} -\alias{app_ui} -\title{ui} -\usage{ -app_ui(request) -} -\arguments{ -\item{request}{needed for bookmarking} -} -\description{ -ui -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_ui.R +\name{app_ui} +\alias{app_ui} +\title{ui} +\usage{ +app_ui(request) +} +\arguments{ +\item{request}{needed for bookmarking} +} +\description{ +ui +} diff --git a/inst/shinyexample/man/run_app.Rd b/inst/shinyexample/man/run_app.Rd index 50364079..174137cd 100644 --- a/inst/shinyexample/man/run_app.Rd +++ b/inst/shinyexample/man/run_app.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/run_app.R -\name{run_app} -\alias{run_app} -\title{Run the Shiny Application} -\usage{ -run_app(...) -} -\arguments{ -\item{...}{A list of Options to be added to the app} -} -\description{ -Run the Shiny Application -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/run_app.R +\name{run_app} +\alias{run_app} +\title{Run the Shiny Application} +\usage{ +run_app(...) +} +\arguments{ +\item{...}{A list of Options to be added to the app} +} +\description{ +Run the Shiny Application +} From 1117caaa1e13f73c3ccb905dcb88f78bbb55f3da Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 4 Nov 2019 14:16:28 +0100 Subject: [PATCH 108/211] Rd --- inst/shinyexample/NAMESPACE | 12 ++++++------ inst/shinyexample/man/app_ui.Rd | 28 ++++++++++++++-------------- inst/shinyexample/man/run_app.Rd | 28 ++++++++++++++-------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index 8429ee35..e9569e88 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -1,6 +1,6 @@ -# Generated by roxygen2: do not edit by hand - -export(run_app) -import(shiny) -importFrom(golem,with_golem_options) -importFrom(shiny,shinyApp) +# Generated by roxygen2: do not edit by hand + +export(run_app) +import(shiny) +importFrom(golem,with_golem_options) +importFrom(shiny,shinyApp) diff --git a/inst/shinyexample/man/app_ui.Rd b/inst/shinyexample/man/app_ui.Rd index bac6b331..6e5abce8 100644 --- a/inst/shinyexample/man/app_ui.Rd +++ b/inst/shinyexample/man/app_ui.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/app_ui.R -\name{app_ui} -\alias{app_ui} -\title{ui} -\usage{ -app_ui(request) -} -\arguments{ -\item{request}{needed for bookmarking} -} -\description{ -ui -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_ui.R +\name{app_ui} +\alias{app_ui} +\title{ui} +\usage{ +app_ui(request) +} +\arguments{ +\item{request}{needed for bookmarking} +} +\description{ +ui +} diff --git a/inst/shinyexample/man/run_app.Rd b/inst/shinyexample/man/run_app.Rd index 50364079..174137cd 100644 --- a/inst/shinyexample/man/run_app.Rd +++ b/inst/shinyexample/man/run_app.Rd @@ -1,14 +1,14 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/run_app.R -\name{run_app} -\alias{run_app} -\title{Run the Shiny Application} -\usage{ -run_app(...) -} -\arguments{ -\item{...}{A list of Options to be added to the app} -} -\description{ -Run the Shiny Application -} +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/run_app.R +\name{run_app} +\alias{run_app} +\title{Run the Shiny Application} +\usage{ +run_app(...) +} +\arguments{ +\item{...}{A list of Options to be added to the app} +} +\description{ +Run the Shiny Application +} From a1a440b476bc3e57b97d3d70b49c95898c8a5e05 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 5 Nov 2019 21:01:46 +0100 Subject: [PATCH 109/211] news update --- NEWS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 71253fc0..798c060b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -62,7 +62,9 @@ + 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) ## Internal changes From ca1f5968b68487238cb007dc3896b2fe7c464393 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 5 Nov 2019 21:11:41 +0100 Subject: [PATCH 110/211] purrr is now an hard dep --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 28149a94..d940ff37 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -59,14 +59,14 @@ Imports: tools, usethis, utils, - yesno + yesno, + purrr Suggests: covr, devtools, DT, knitr, pkgdown, - purrr, rcmdcheck, rmarkdown, spelling, From 0e623301d396ac98cd0598449c20ecd7947b20b0 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 8 Nov 2019 22:24:27 +0100 Subject: [PATCH 111/211] restored use_file --- NAMESPACE | 2 + NEWS.md | 2 +- R/use_files.R | 127 +++++++++++++++++++++++++++++++++++++++++++++++ man/use_files.Rd | 29 +++++++++++ 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 R/use_files.R create mode 100644 man/use_files.Rd diff --git a/NAMESPACE b/NAMESPACE index db2b6413..617b3146 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -41,6 +41,8 @@ export(print_dev) export(remove_favicon) export(set_golem_options) export(set_golem_wd) +export(use_external_css_file) +export(use_external_js_file) export(use_favicon) export(use_recommended_deps) export(use_recommended_tests) diff --git a/NEWS.md b/NEWS.md index 798c060b..1bd6e902 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,7 +12,7 @@ + New JavaScript functions to use alert, prompt and confirm (#108, @zwycl) -+ `add_external_js_file` and `add_external_css_file` are designed to download .js and .css file off the web to the appropriate directory (#130, @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) + There is now an `app_sys()` function, which is a wrapper around `system.file(..., package = "myapp")` (#207, @novica) diff --git a/R/use_files.R b/R/use_files.R new file mode 100644 index 00000000..5ef60f49 --- /dev/null +++ b/R/use_files.R @@ -0,0 +1,127 @@ +#' Use Files +#' +#' These functions download files from external sources and install them inside the approriate directory. +#' +#' @inheritParams add_module +#' @param url String representation of URL for the file to be downloaded +#' @param dir Path to the dir where the file while be created. +#' @export +#' @rdname use_files +#' @importFrom glue glue +#' @importFrom cli cat_bullet + +use_external_js_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.js") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "js") { + cat_red_bullet( + "File not added (URL must end with .js extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} + +#' @export +#' @rdname use_files +use_external_css_file <- function( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +){ + + old <- setwd(normalizePath(pkg)) + on.exit(setwd(old)) + new_file <- glue::glue("{name}.css") + + dir_created <- create_dir_if_needed( + dir, + dir_create + ) + + if (!dir_created){ + cat_red_bullet( + "File not added (needs a valid directory)" + ) + return(invisible(FALSE)) + } + + dir <- normalizePath(dir) + + where <- file.path( + dir, new_file + ) + if ( !check_file_exist(where) ) { + return(invisible(FALSE)) + } + + if ( tools::file_ext(url) != "css") { + cat_red_bullet( + "File not added (URL must end with .css extension)" + ) + return(invisible(FALSE)) + } + + utils::download.file(url, where) + + cat_green_tick(glue::glue("File created at {where}")) + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' + ) + ) + + if (rstudioapi::isAvailable() & open){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet(glue::glue("Go to {where}")) + } +} \ No newline at end of file diff --git a/man/use_files.Rd b/man/use_files.Rd new file mode 100644 index 00000000..b6e3d472 --- /dev/null +++ b/man/use_files.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_files.R +\name{use_external_js_file} +\alias{use_external_js_file} +\alias{use_external_css_file} +\title{Use Files} +\usage{ +use_external_js_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) + +use_external_css_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) +} +\arguments{ +\item{url}{String representation of URL for the file to be downloaded} + +\item{name}{The name of the module} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{dir}{Path to the dir where the file while be created.} + +\item{open}{Should the file be opened?} + +\item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} +} +\description{ +These functions download files from external sources and install them inside the approriate directory. +} From 47b9d2c017c6fa305df8d9ed5f34c04c3f309dce Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 16:13:36 +0100 Subject: [PATCH 112/211] road to #269 --- R/add_deploy_helpers.R | 37 ++++++++++++++++++++++++++----------- man/dock_from_desc.Rd | 2 +- man/dockerfiles.Rd | 3 ++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 908ee512..35eccd6d 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -156,7 +156,8 @@ add_dockerfile <- function( host = "0.0.0.0", sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + build_golem_from_source = FALSE # , function_to_launch = "run_app" ) { @@ -168,7 +169,8 @@ add_dockerfile <- function( - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos,expand = expand) + dock <- dock_from_desc(path = path, FROM = from, AS = as, + sysreqs = sysreqs, repos = repos,expand = expand,build_golem_from_source = build_golem_from_source) dock$EXPOSE(port) dock$CMD( glue::glue( @@ -194,7 +196,8 @@ add_dockerfile_shinyproxy <- function( as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + build_golem_from_source = FALSE ){ where <- file.path(pkg, output) @@ -202,7 +205,7 @@ add_dockerfile_shinyproxy <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) dock <- dock_from_desc(path = path, FROM = from, AS = as, - sysreqs = sysreqs, repos = repos, expand = expand) + sysreqs = sysreqs, repos = repos, expand = expand,build_golem_from_source=build_golem_from_source) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -232,7 +235,8 @@ add_dockerfile_heroku <- function( as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + build_golem_from_source = FALSE ){ where <- file.path(pkg, output) @@ -240,7 +244,7 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, expand = expand) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, expand = expand,build_golem_from_source = build_golem_from_source) dock$CMD( glue::glue( @@ -318,7 +322,8 @@ dock_from_desc <- function( AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + build_golem_from_source = FALSE ){ @@ -423,14 +428,24 @@ dock_from_desc <- function( dock - - - - dock$COPY( + if ( !build_golem_from_source){ + # we use a already builded tar.gz file + dock$COPY( from = paste0(read.dcf(path)[1], "_*.tar.gz"), to = "/app.tar.gz" ) dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") + } else { + # + dock$RUN("mkdir /build_zone") + dock$ADD(from = ".",to = "/build_zone") + dock$WORKDIR("/build_zone") + #dock$RUN("R -e 'setwd(\"/build_zone\");devtools::build(path = \".\")'") + dock$RUN("R -e 'remotes::install_local()'") + + + + } dock } \ No newline at end of file diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd index 2655fcf5..f23be3ac 100644 --- a/man/dock_from_desc.Rd +++ b/man/dock_from_desc.Rd @@ -7,7 +7,7 @@ dock_from_desc(path = "DESCRIPTION", FROM = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) + expand = FALSE, build_golem_from_source = FALSE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 910db9af..a1306e72 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -10,7 +10,8 @@ add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", sysreqs = TRUE, - repos = "https://cran.rstudio.com/", expand = FALSE) + repos = "https://cran.rstudio.com/", expand = FALSE, + build_golem_from_source = FALSE) add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", From 179b9de35ac6ae76f629160280a445646d2b24f0 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 21:38:28 +0100 Subject: [PATCH 113/211] fix #269 --- R/add_deploy_helpers.R | 13 ++++++------- man/dockerfiles.Rd | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 35eccd6d..cfb6c0d5 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -166,9 +166,6 @@ add_dockerfile <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - - - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos,expand = expand,build_golem_from_source = build_golem_from_source) dock$EXPOSE(port) @@ -178,7 +175,7 @@ add_dockerfile <- function( ) ) dock$write(output) - alert_build(path, output) + alert_build(path, output,build_golem_from_source=build_golem_from_source) } @@ -213,7 +210,7 @@ add_dockerfile_shinyproxy <- function( )) dock$write(output) - alert_build(path, output) + alert_build(path, output,build_golem_from_source=build_golem_from_source) usethis::use_build_ignore(files = output) @@ -253,7 +250,7 @@ add_dockerfile_heroku <- function( ) dock$write(output) - alert_build(path, output) + alert_build(path, output,build_golem_from_source=build_golem_from_source) apps_h <- gsub( "\\.", "-", @@ -284,15 +281,17 @@ add_dockerfile_heroku <- function( } -alert_build <- function(path, output){ +alert_build <- function(path, output ,build_golem_from_source){ cat_green_tick( glue("Dockerfile created at {output}") ) + if ( ! build_golem_from_source){ cat_red_bullet( glue::glue( "Be sure to put your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" ) ) + } } #' Create Dockerfile from DESCRIPTION diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index a1306e72..44618fda 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -17,13 +17,13 @@ add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) + expand = FALSE, build_golem_from_source = FALSE) add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) + expand = FALSE, build_golem_from_source = FALSE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} From ab20717d840471aecc40e7d474dd803e74279e37 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 21:41:31 +0100 Subject: [PATCH 114/211] update News --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 1bd6e902..d136caa2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ + `add_dockerfile()` was completely refactore. 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_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). + We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). From 77cb35939e43e2ff1db6a253ce4bb256682c3014 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 22:11:25 +0100 Subject: [PATCH 115/211] #fix 273 add_dockerfile() now run devtools::build() by default --- R/add_deploy_helpers.R | 23 +++++++++++++++++------ man/dock_from_desc.Rd | 3 ++- man/dockerfiles.Rd | 8 +++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index cfb6c0d5..96a42246 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -157,7 +157,8 @@ add_dockerfile <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - build_golem_from_source = FALSE + build_golem_from_source = FALSE, + update_tar_gz = TRUE # , function_to_launch = "run_app" ) { @@ -194,7 +195,8 @@ add_dockerfile_shinyproxy <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - build_golem_from_source = FALSE + build_golem_from_source = FALSE, + update_tar_gz = TRUE ){ where <- file.path(pkg, output) @@ -233,7 +235,8 @@ add_dockerfile_heroku <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - build_golem_from_source = FALSE + build_golem_from_source = FALSE, + update_tar_gz = TRUE ){ where <- file.path(pkg, output) @@ -288,7 +291,7 @@ alert_build <- function(path, output ,build_golem_from_source){ if ( ! build_golem_from_source){ cat_red_bullet( glue::glue( - "Be sure to put your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" + "Be sure to keep your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" ) ) } @@ -322,7 +325,8 @@ dock_from_desc <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - build_golem_from_source = FALSE + build_golem_from_source = FALSE, + update_tar_gz = TRUE ){ @@ -427,8 +431,15 @@ dock_from_desc <- function( dock - if ( !build_golem_from_source){ + if ( !build_golem_from_source & update_tar_gz){ # we use a already builded tar.gz file + ancienne_version <- list.files(pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"),full.names = TRUE) + cat_red_bullet(glue::glue("We remove {paste(ancienne_version,collapse = ", ")} from folder")) + lapply(ancienne_version,file.remove) + lapply(ancienne_version,unlink,force=TRUE) + cat_green_tick(glue::glue(" {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz created.")) + devtools::build(path = ".") + dock$COPY( from = paste0(read.dcf(path)[1], "_*.tar.gz"), to = "/app.tar.gz" diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd index f23be3ac..292a57d3 100644 --- a/man/dock_from_desc.Rd +++ b/man/dock_from_desc.Rd @@ -7,7 +7,8 @@ dock_from_desc(path = "DESCRIPTION", FROM = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, build_golem_from_source = FALSE) + expand = FALSE, build_golem_from_source = FALSE, + update_tar_gz = TRUE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 44618fda..403a0642 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -11,19 +11,21 @@ add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - build_golem_from_source = FALSE) + build_golem_from_source = FALSE, update_tar_gz = TRUE) add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, build_golem_from_source = FALSE) + expand = FALSE, build_golem_from_source = FALSE, + update_tar_gz = TRUE) add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, build_golem_from_source = FALSE) + expand = FALSE, build_golem_from_source = FALSE, + update_tar_gz = TRUE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} From 4b34ee91a1edbe0040d5a1521a4d8bba10b292a5 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 22:17:03 +0100 Subject: [PATCH 116/211] update doc --- R/add_deploy_helpers.R | 2 ++ man/dockerfiles.Rd | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 96a42246..442a16b2 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -122,6 +122,8 @@ add_shinyserver_file <- function( #' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line +#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it +#' @param update_tar_gz boolean, if `TRUE` and build_golem_from_source is also `TRUE` an updated tar.gz Package is created #' @export #' @rdname dockerfiles #' @importFrom desc desc_get_deps diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 403a0642..f81cd46d 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -50,6 +50,10 @@ Default is 0.0.0.0.} \item{repos}{character vector, the base URL of the repositories} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} + +\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} + +\item{update_tar_gz}{boolean, if \code{TRUE} and build_golem_from_source is also \code{TRUE} an updated tar.gz Package is created} } \description{ Build a container containing your Shiny App. \code{add_dockerfile()} creates From 9ec6d1dd4b0e423ce7c068896ef1b50c3d34548b Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 22:17:52 +0100 Subject: [PATCH 117/211] update doc --- R/add_deploy_helpers.R | 2 ++ man/dockerfiles.Rd | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index cfb6c0d5..a8d389d7 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -122,6 +122,8 @@ add_shinyserver_file <- function( #' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line +#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it +#' @param update_tar_gz boolean, if `TRUE` and build_golem_from_source is also `TRUE` an updated tar.gz Package is created #' @export #' @rdname dockerfiles #' @importFrom desc desc_get_deps diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 44618fda..4c8fe151 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -48,6 +48,10 @@ Default is 0.0.0.0.} \item{repos}{character vector, the base URL of the repositories} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} + +\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} + +\item{update_tar_gz}{boolean, if \code{TRUE} and build_golem_from_source is also \code{TRUE} an updated tar.gz Package is created} } \description{ Build a container containing your Shiny App. \code{add_dockerfile()} creates From 7590f1e801a85bb0c0d37f45f05283d23f4f29b2 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 22:36:46 +0100 Subject: [PATCH 118/211] correct doc --- R/add_deploy_helpers.R | 2 +- man/dock_from_desc.Rd | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index a8d389d7..76048dbe 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -306,7 +306,7 @@ alert_build <- function(path, output ,build_golem_from_source){ #' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line -#' +#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd index f23be3ac..72d823de 100644 --- a/man/dock_from_desc.Rd +++ b/man/dock_from_desc.Rd @@ -22,6 +22,8 @@ with \code{R.Version()$major} and \code{R.Version()$minor}.} \item{repos}{character vector, the base URL of the repositories} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} + +\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} } \description{ Create Dockerfile from DESCRIPTION From b0ae2a2acd1d1c93497761d444bbc3221427bf68 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 9 Nov 2019 22:37:57 +0100 Subject: [PATCH 119/211] re update doc --- R/add_deploy_helpers.R | 3 ++- man/dock_from_desc.Rd | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 442a16b2..064128d2 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -309,7 +309,8 @@ alert_build <- function(path, output ,build_golem_from_source){ #' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line -#' +#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it +#' @param update_tar_gz boolean, if `TRUE` and build_golem_from_source is also `TRUE` an updated tar.gz Package is created #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd index 292a57d3..8202c3f3 100644 --- a/man/dock_from_desc.Rd +++ b/man/dock_from_desc.Rd @@ -23,6 +23,10 @@ with \code{R.Version()$major} and \code{R.Version()$minor}.} \item{repos}{character vector, the base URL of the repositories} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} + +\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} + +\item{update_tar_gz}{boolean, if \code{TRUE} and build_golem_from_source is also \code{TRUE} an updated tar.gz Package is created} } \description{ Create Dockerfile from DESCRIPTION From 03f95811bf2a9c32525d912cc35647c5fdd9b113 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 10 Nov 2019 09:00:08 +0100 Subject: [PATCH 120/211] fix #275 add_dockerfile* now open the Dockerfile at the end. --- NAMESPACE | 1 + R/add_deploy_helpers.R | 35 ++++++++++++++++++++++++++++++----- man/dockerfiles.Rd | 8 +++++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 617b3146..24a712c0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -76,6 +76,7 @@ importFrom(roxygen2,roxygenise) importFrom(rstudioapi,getSourceEditorContext) importFrom(rstudioapi,isAvailable) importFrom(rstudioapi,modifyRange) +importFrom(rstudioapi,navigateToFile) importFrom(rstudioapi,openProject) importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 908ee512..ead49fbe 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -122,10 +122,12 @@ add_shinyserver_file <- function( #' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line +#' @param open boolean, default is `TRUE` open the Dockerfile file #' @export #' @rdname dockerfiles #' @importFrom desc desc_get_deps #' @importFrom dockerfiler Dockerfile +#' @importFrom rstudioapi navigateToFile isAvailable #' @examples #' \donttest{ #' # Add a standard Dockerfile @@ -156,7 +158,8 @@ add_dockerfile <- function( host = "0.0.0.0", sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + open = TRUE # , function_to_launch = "run_app" ) { @@ -176,6 +179,13 @@ add_dockerfile <- function( ) ) dock$write(output) + if (open) { + if (rstudioapi::isAvailable()) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } + } alert_build(path, output) } @@ -194,7 +204,8 @@ add_dockerfile_shinyproxy <- function( as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + open = TRUE ){ where <- file.path(pkg, output) @@ -211,7 +222,13 @@ add_dockerfile_shinyproxy <- function( dock$write(output) alert_build(path, output) - + if (open) { + if (rstudioapi::isAvailable()) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } + } usethis::use_build_ignore(files = output) invisible(output) @@ -232,7 +249,8 @@ add_dockerfile_heroku <- function( as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + open = TRUE ){ where <- file.path(pkg, output) @@ -274,7 +292,14 @@ add_dockerfile_heroku <- function( cat_red_bullet( glue("You can replace {apps_h} with another app name.") ) - + browser() + if (open) { + if (rstudioapi::isAvailable()) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } + } usethis::use_build_ignore(files = output) invisible(output) diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 910db9af..ef3b5d7c 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -10,19 +10,19 @@ add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", sysreqs = TRUE, - repos = "https://cran.rstudio.com/", expand = FALSE) + repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE) add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) + expand = FALSE, open = TRUE) add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) + expand = FALSE, open = TRUE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -47,6 +47,8 @@ Default is 0.0.0.0.} \item{repos}{character vector, the base URL of the repositories} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} + +\item{open}{boolean, default is \code{TRUE} open the Dockerfile file} } \description{ Build a container containing your Shiny App. \code{add_dockerfile()} creates From aea8aa235a8e429ad5f7e1a54db7edbbf460ac20 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 11 Nov 2019 00:11:10 +0100 Subject: [PATCH 121/211] fix #279 --- R/get_sysreqs.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 1f660036..f60807ad 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -41,6 +41,6 @@ get_batch_sysreqs <- function(all_deps,quiet=TRUE){ - sort(unique(out[!is.na(out)])) + unique(out[!is.na(out)]) } From fa0858b4c6474d7f0e567842a13b0eabcbb196d7 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 12 Nov 2019 16:26:39 +0100 Subject: [PATCH 122/211] prevent random name generator from have ui or server inside it Related to #281 --- tests/testthat/helper-config.R | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 20a09493..56e0de2b 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -31,13 +31,18 @@ expect_exists <- function(fls) { invisible(act$val) } +# We prevent the random name from having +# ui or server inside it +safe_let <- function(){ + letters[-c(5,9,18,19,21,22)] +} ## fake package fakename <- sprintf( "%s%s", - paste0(sample(letters, 10, TRUE), collapse = ""), + paste0(sample(safe_let(), 10, TRUE), collapse = ""), gsub("[ :-]", "", Sys.time()) - ) +) tpdir <- tempdir() unlink(file.path(tpdir,fakename), recursive = TRUE) @@ -45,12 +50,12 @@ create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) ## random dir -randir <- paste0(sample(letters, 10, TRUE), collapse = "") +randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") fp <- file.path("inst/app", randir) dir.create(file.path(pkg, fp), recursive = TRUE) rand_name <- function(){ - paste0(sample(letters, 10, TRUE), collapse = "") + paste0(sample(safe_let(), 10, TRUE), collapse = "") } orig_test <- set_golem_wd(pkg) From ddc41a25686fe41de1dcf464f7969d693d2c4390 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 15 Nov 2019 12:34:14 +0100 Subject: [PATCH 123/211] added a title to UI and server --- inst/shinyexample/R/app_server.R | 2 ++ inst/shinyexample/R/app_ui.R | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/inst/shinyexample/R/app_server.R b/inst/shinyexample/R/app_server.R index 1856773d..11bb8c98 100644 --- a/inst/shinyexample/R/app_server.R +++ b/inst/shinyexample/R/app_server.R @@ -1,3 +1,5 @@ +#' App Server +#' #' @import shiny app_server <- function(input, output,session) { # List the first level callModules here diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 8fade0ed..c7c763a9 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,6 +1,4 @@ -#' ui -#' -#' @param request needed for bookmarking +#' Application UI #' #' @import shiny app_ui <- function(request) { @@ -13,9 +11,8 @@ app_ui <- function(request) { ) ) } - + #' @import shiny -#' @importFrom golem app_sys golem_add_external_resources <- function(){ golem::add_resource_path( From 859779a3a8a78f03a7c525824e23c91bc8bc2c34 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 15 Nov 2019 12:34:48 +0100 Subject: [PATCH 124/211] reindent things in the desc file --- R/desc.R | 58 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/R/desc.R b/R/desc.R index a3db4d42..8658f51b 100644 --- a/R/desc.R +++ b/R/desc.R @@ -22,21 +22,57 @@ fill_desc <- function( author_email, repo_url = NULL, pkg = get_golem_wd() - ){ +){ + path <- normalizePath(pkg) + desc <- desc::description$new( file = file.path(path, "DESCRIPTION") ) - desc$set("Authors@R", glue("person('{author_first_name}', '{author_last_name}', email = '{author_email}', role = c('cre', 'aut'))")) - desc$del("Maintainer") - desc$set_version("0.0.0.9000") - desc$set(Package = pkg_name) - desc$set(Title = pkg_title) - desc$set(Description = pkg_description) - if_not_null(repo_url, desc$set("URL", repo_url)) - if_not_null(repo_url, desc$set("BugReports", glue("{repo_url}/issues"))) - desc$write(file = "DESCRIPTION") - cat_bullet("DESCRIPTION file modified", bullet = "tick", bullet_col = "green") + desc$set( + "Authors@R", + glue("person('{author_first_name}', '{author_last_name}', email = '{author_email}', role = c('cre', 'aut'))") + ) + desc$del( + keys = "Maintainer" + ) + desc$set_version( + version = "0.0.0.9000" + ) + desc$set( + Package = pkg_name + ) + desc$set( + Title = pkg_title + ) + desc$set( + Description = pkg_description + ) + if_not_null( + repo_url, + desc$set( + "URL", + repo_url + ) + ) + if_not_null( + repo_url, + desc$set( + "BugReports", + glue("{repo_url}/issues" + ) + ) + ) + + desc$write( + file = "DESCRIPTION" + ) + + cat_bullet( + "DESCRIPTION file modified", + bullet = "tick", + bullet_col = "green" + ) } From a963093c3c4bdbdb2feb0f94ba17c938e518cc96 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 15 Nov 2019 12:37:24 +0100 Subject: [PATCH 125/211] {golem} now uses {config} This commit bundles a series of change for relying on the {config} package inside {golem}. There is now a file called `golem-config.yml` in the `inst/` folder, which contains the configuration for your app and that can be used for configuration in a production environment. See the `config` Vignette for more information. --- DESCRIPTION | 20 +- NAMESPACE | 10 +- R/config.R | 103 +++++++++ R/create_golem.R | 35 ++- R/options.R | 295 ++++++++++++++++++------ R/utils.R | 56 ++++- devtools_history.R | 1 + inst/shinyexample/DESCRIPTION | 1 + inst/shinyexample/NAMESPACE | 2 +- inst/shinyexample/R/app_config.R | 18 ++ inst/shinyexample/inst/golem-config.yml | 8 + man/amend_golem_config.Rd | 25 ++ man/golem_name.Rd | 26 --- man/golem_opts.Rd | 83 +++++++ man/golem_wd.Rd | 29 --- man/set_golem_options.Rd | 22 -- tests/testthat/helper-config.R | 7 +- tests/testthat/test-config.R | 35 +++ tests/testthat/test-use_recomended.R | 12 +- vignettes/config.Rmd | 140 +++++++++++ 20 files changed, 750 insertions(+), 178 deletions(-) create mode 100644 R/config.R create mode 100644 inst/shinyexample/R/app_config.R create mode 100644 inst/shinyexample/inst/golem-config.yml create mode 100644 man/amend_golem_config.Rd delete mode 100644 man/golem_name.Rd create mode 100644 man/golem_opts.Rd delete mode 100644 man/golem_wd.Rd delete mode 100644 man/set_golem_options.Rd create mode 100644 tests/testthat/test-config.R create mode 100644 vignettes/config.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index d940ff37..bfddb9c1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,10 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9500 -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. +Version: 0.1.0.9600 Authors@R: c(person(given = "Vincent", family = "Guyader", @@ -31,7 +27,11 @@ Authors@R: role = "ctb", email = "nnovica@gmail.com"), person(given = "ThinkR", - role = "cph")) + 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. License: MIT + file LICENSE URL: https://github.com/ThinkR-open/golem BugReports: https://github.com/ThinkR-open/golem/issues @@ -40,14 +40,17 @@ Depends: Imports: attempt (>= 0.3.0), cli, + config, crayon, desc, dockerfiler, + fs, glue, htmltools, jsonlite, magrittr, pkgload, + purrr, remotes, rlang, roxygen2, @@ -59,12 +62,11 @@ Imports: tools, usethis, utils, - yesno, - purrr + yaml, + yesno Suggests: covr, devtools, - DT, knitr, pkgdown, rcmdcheck, diff --git a/NAMESPACE b/NAMESPACE index 617b3146..6516e6c1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,9 +15,9 @@ export(add_shinyappsio_file) export(add_shinyserver_file) export(add_ui_server_files) export(add_utils) +export(amend_golem_config) export(app_dev) export(app_prod) -export(app_sys) export(browser_button) export(browser_dev) export(cat_dev) @@ -31,6 +31,7 @@ export(favicon) export(fill_desc) export(get_golem_name) export(get_golem_options) +export(get_golem_version) export(get_golem_wd) export(get_sysreqs) export(insert_ns) @@ -39,7 +40,9 @@ export(make_dev) export(message_dev) export(print_dev) export(remove_favicon) +export(set_golem_name) export(set_golem_options) +export(set_golem_version) export(set_golem_wd) export(use_external_css_file) export(use_external_js_file) @@ -51,11 +54,13 @@ export(use_utils_ui) export(warning_dev) export(with_golem_options) importFrom(attempt,attempt) +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(config,get) importFrom(desc,desc_get_deps) importFrom(desc,description) importFrom(dockerfiler,Dockerfile) @@ -84,6 +89,7 @@ importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) importFrom(tools,file_path_sans_ext) +importFrom(usethis,proj_set) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) importFrom(usethis,use_testthat) @@ -94,4 +100,6 @@ importFrom(utils,getFromNamespace) importFrom(utils,installed.packages) importFrom(utils,packageVersion) importFrom(utils,sessionInfo) +importFrom(yaml,read_yaml) +importFrom(yaml,write_yaml) importFrom(yesno,yesno) diff --git a/R/config.R b/R/config.R new file mode 100644 index 00000000..507c5033 --- /dev/null +++ b/R/config.R @@ -0,0 +1,103 @@ +guess_where_config <- function( + path +){ + # Trying the path + path <- file.path( + path, "inst/golem-config.yml" + ) + if (file.exists(path)) return(normalizePath(path)) + # Trying maybe in the wd + path <- "golem-config.yml" + if (file.exists(path)) return(normalizePath(path)) + # Trying with pkgpath + path <- attempt::attempt({ + file.path( + pkgload::pkg_path(), + "inst/golem-config.yml" + ) + }) + if ( + !attempt::is_try_error(path) & + file.exists(path) + ) { + return( + normalizePath(path) + ) + } + return(NULL) +} + +get_current_config <- function( + 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 <- file.path( + path, "inst/golem-config.yml" + ) + } + + if (!file.exists(path_conf)){ + ask <- yesno::yesno( + sprintf( + "The %s file doesn't exist, create?", + basename(path_conf) + ) + ) + # Return early if the user doesn't allow + if (!ask) return(FALSE) + + fs::file_copy( + path = golem_sys("shinyexample/inst/golem-config.yml"), + new_path = file.path( + path, "inst/golem-config.yml" + ) + ) + fs::file_copy( + path = golem_sys("shinyexample/R/app_config.R"), + new_path = file.path( + path, "R/app_config.R" + ) + ) + replace_word( + "R/app_config.R", + "shinyexample", + pkgload::pkg_name() + ) + if (set_options){ + set_golem_options() + } + } + + return(path_conf) + +} + +#' 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 +amend_golem_config <- function( + key, + value, + config = "default", + pkg = get_golem_wd(), + talkative = TRUE +){ + conf_path <- get_current_config(pkg) + conf <- yaml::read_yaml(conf_path) + conf[[config]][[key]] <- value + yaml::write_yaml( + conf, + conf_path + ) + invisible(TRUE) +} \ No newline at end of file diff --git a/R/create_golem.R b/R/create_golem.R index 90f7fac1..2d6a96b4 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -30,10 +30,10 @@ create_golem <- function( package_name <- basename(getwd()) } - - 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)){ @@ -44,7 +44,9 @@ create_golem <- function( return(invisible(NULL)) } } + dir.create(path, recursive = TRUE, showWarnings = FALSE) + cat_green_tick("Created package directory") from <- golem_sys("shinyexample") ll <- list.files(path = from, full.names = TRUE, all.files = TRUE,no.. = TRUE) @@ -81,7 +83,17 @@ create_golem <- function( }, silent=TRUE) } - + cat_green_tick("Copied app skeleton") + + yml_path <- file.path(path, "inst/golem-config.yml") + + conf <- yaml::read_yaml(yml_path) + conf$default$golem_wd <- normalizePath(path) + conf$default$golem_name <- package_name + conf$default$golem_version <- "0.0.0.9000" + yaml::write_yaml(conf, yml_path) + + cat_green_tick("Configured app") if ( without_comments == TRUE ) { files <- list.files( @@ -96,18 +108,23 @@ create_golem <- function( } } + cat_rule("Done") - cat_line(paste0("A new golem package ", package_name, " was created in ", get_golem_wd(), "/", package_name, - " directory.\n", - "To continue work on your package start editing the 01_start.R file")) - - + 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 ( open & rstudioapi::isAvailable() ) { rstudioapi::openProject(path = path) } - return( invisible( normalizePath(path) diff --git a/R/options.R b/R/options.R index de7b0807..2b5e8668 100644 --- a/R/options.R +++ b/R/options.R @@ -1,102 +1,259 @@ -#' {golem} options +#' `{golem}` options #' -#' Set a series of options to be used internally by `{golem}`. +#' 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 `pkgload::pkg_path()`, the current directory when starting a golem. +#' + `set_golem_name()` defaults `pkgload::pkg_name()` +#' + `set_golem_version()` defaults `pkgload::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 +#' @importFrom yaml read_yaml write_yaml +#' @importFrom usethis proj_set set_golem_options <- function( golem_name = pkgload::pkg_name(), golem_version = pkgload::pkg_version(), golem_wd = pkgload::pkg_path(), - app_prod = FALSE + app_prod = FALSE, + talkative = TRUE ){ - cli::cat_rule("Setting {golem} options") - options("golem.pkg.name" = golem_name) - cat_green_tick(sprintf("Setting options('golem.pkg.name') to %s", golem_name)) - options("golem.pkg.version" = golem_version) - cat_green_tick(sprintf("Setting options('golem.pkg.version') to %s", golem_version)) - set_golem_wd(golem_wd, FALSE) - cat_green_tick(sprintf("Setting options('golem.wd') to %s", golem_wd)) - cat_line("You can change golem working directory with set_golem_wd('path/to/wd')") - options("golem.app.prod" = app_prod) - cat_green_tick(sprintf("Setting options('golem.app.prod') to %s", app_prod)) + + cat_if_talk <- function(..., fun = cat_green_tick){ + if (talkative){ + fun(...) + } + } + + conf_path <- get_current_config(golem_wd, set_options = FALSE) + + stop_if( + conf_path, + isFALSE, + "You need to create golem-config.yml to set the options." + ) + + cat_if_talk("Setting {golem} options in `golem-config.yml`", fun= cli::cat_rule) + + conf <- read_yaml(conf_path) + + # Setting wd + cat_if_talk( + sprintf( + "Setting `golem_wd` to %s", + golem_wd + ) + ) + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + conf$default$golem_wd <- 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) + } -#' Get and set `{golem}` working directory -#' -#' Many `{golem}` functions rely on a specific working directory, -#' most of the time the root of the package. This working directory -#' is set by `set_golem_options` or the first time you create a file. -#' It default to `"."`, the current directory when starting a golem. -#' You can use these two functions if you need to manipulate this -#' directory. -#' -#' @param path The path to set the golem working directory. -#' Note that it will be passed to `normalizePath`. -#' @param talkative Should the function print where the -#' new path is defined? -#' -#' @return The path to the working directory. -#' @export -#' @rdname golem_wd -get_golem_wd <- function(){ - if (is.null(getOption("golem.wd"))){ - cat_red_bullet("Couldn't find golem working directory") - cat_green_tick("Definining golem working directory as `.`") - cat_line("You can change golem working directory with set_golem_wd('path/to/wd')") - set_golem_wd(".") +#' @importFrom yaml read_yaml write_yaml +set_golem_things <- function( + key, + value, + path, + talkative +){ + conf_path <- get_current_config(path, set_options = FALSE) + + cat_if_talk <- function(..., fun = cat_green_tick){ + if (talkative){ + fun(...) + } } - getOption("golem.wd") + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + conf <- read_yaml(conf_path) + conf$default[[key]] <- value + write_yaml( + conf, + conf_path + ) + + invisible(path) } - #' @export -#' @rdname golem_wd +#' @rdname golem_opts set_golem_wd <- function( - path, + path = pkgload::pkg_path(), talkative = TRUE ){ path <- normalizePath(path, winslash = "/") - if (talkative){ - cat_green_tick( - sprintf("Definining golem working directory as `%s`", path) - ) - } - options("golem.wd" = path) - invisible(path) + set_golem_things( + "golem_wd", + path, + path, + talkative = talkative + ) + } +#' @export +#' @rdname golem_opts +set_golem_name <- function( + name = pkgload::pkg_name(), + path = pkgload::pkg_path(), + talkative = TRUE +){ + path <- normalizePath(path, winslash = "/") + set_golem_things( + "golem_name", + name, + path, + talkative = talkative + ) + +} -#' A function to return the golem name to be used elsewhere -#' in the package. -#' -#' @return The name of the golem. #' @export -#' @rdname golem_name -#' @importFrom pkgload pkg_name +#' @rdname golem_opts +set_golem_version <- function( + version = pkgload::pkg_version(), + path = pkgload::pkg_path(), + talkative = TRUE +){ + path <- normalizePath(path, winslash = "/") + set_golem_things( + "golem_version", + as.character(version), + path, + talkative = talkative + ) + +} -get_golem_name <- function(){ - if (is.null(getOption("golem_name"))){ - options("golem.pkg.name" = pkg_name()) - } - getOption("golem.pkg.name") +#' @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) + + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) + +} + + +#' @export +#' @rdname golem_opts +get_golem_wd <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = pkgload::pkg_path() +){ + get_golem_things( + value = "golem_wd", + config = config, + use_parent = use_parent, + path = path + ) } -#' A wrapper around `system.file(..., package = get_golem_name())` -#' -#' This function allows to use `app_sys()` instead of using the -#' `system.file(, package = "pkg")`. -#' -#' @inheritParams base::system.file -#' @return The path to the current golem. #' @export -#' @rdname golem_name +#' @rdname golem_opts +get_golem_name <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = pkgload::pkg_path() +){ + get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) +} #' @export -app_sys <- function(...) { - getFromNamespace("shim_system.file", "pkgload")(..., package = get_golem_name()) +#' @rdname golem_opts +get_golem_version <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = pkgload::pkg_path() +){ + get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) } + diff --git a/R/utils.R b/R/utils.R index c06d58ea..841d9f62 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,9 +1,16 @@ -golem_sys <- function(..., lib.loc = NULL, mustWork = FALSE){ - system.file(..., package = "golem", lib.loc = lib.loc, mustWork = mustWork) +golem_sys <- function( + ..., + lib.loc = NULL, + mustWork = FALSE +){ + system.file( + ..., + package = "golem", + lib.loc = lib.loc, + mustWork = mustWork + ) } - - # from usethis https://github.com/r-lib/usethis/ darkgrey <- function(x) { x <- crayon::make_style("darkgrey")(x) @@ -12,6 +19,47 @@ darkgrey <- function(x) { dir_not_exist <- Negate(dir.exists) file_not_exist <- Negate(file.exists) +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) + } else if (type == "directory"){ + dont_exist <- dir_not_exist(path) + } + # If it doesn't exist, ask if we are allowed + # to create it + if (dont_exist){ + ask <- yesno::yesno( + sprintf( + "The %s %s doesn't exist, create?", + basename(path), + type + ) + ) + # Return early if the user doesn't allow + if (!ask) return(FALSE) + + } else { + return(TRUE) + } + + # Create the file + if (type == "file"){ + fs::file_create(path) + write(content, path, append = TRUE) + } else if (type == "directory"){ + fs::dir_create(path, recurse = TRUE) + } + # TRUE means that file exists (either + # created or already there) + return(TRUE) +} + create_dir_if_needed <- function( path, auto_create diff --git a/devtools_history.R b/devtools_history.R index b49f80e5..49d36e71 100644 --- a/devtools_history.R +++ b/devtools_history.R @@ -26,6 +26,7 @@ usethis::use_test("reload") usethis::use_test("js") usethis::use_test("aaadependencies") usethis::use_test("create_golem") +usethis::use_test("config") # Travis usethis::use_travis() diff --git a/inst/shinyexample/DESCRIPTION b/inst/shinyexample/DESCRIPTION index fd7f00da..7945e0c1 100644 --- a/inst/shinyexample/DESCRIPTION +++ b/inst/shinyexample/DESCRIPTION @@ -9,6 +9,7 @@ Authors@R: Description: What the package does (one paragraph). License: What license is it under? Imports: + config, golem, shiny Encoding: UTF-8 diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index 064445b7..bf248860 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -1,7 +1,7 @@ # Generated by roxygen2: do not edit by hand export(run_app) +import(config) import(shiny) -importFrom(golem,app_sys) importFrom(golem,with_golem_options) importFrom(shiny,shinyApp) diff --git a/inst/shinyexample/R/app_config.R b/inst/shinyexample/R/app_config.R new file mode 100644 index 00000000..6408d6ed --- /dev/null +++ b/inst/shinyexample/R/app_config.R @@ -0,0 +1,18 @@ +app_sys <- function(...){ + system.file(..., package = "shinyexample") +} + +#' @import config +get_golem_config <- function( + value, + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE +){ + config::get( + value = value, + config = config, + # Modify this if your config file is somewhere else: + file = app_sys("golem-config.yml"), + use_parent = use_parent + ) +} \ No newline at end of file diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml new file mode 100644 index 00000000..00ae9a47 --- /dev/null +++ b/inst/shinyexample/inst/golem-config.yml @@ -0,0 +1,8 @@ +default: + golem_name: shinyexample + golem_version: 0.0.0.9000 + app_prod: no + +production: + app_prod: yes + diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd new file mode 100644 index 00000000..5ec8185c --- /dev/null +++ b/man/amend_golem_config.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{amend_golem_config} +\alias{amend_golem_config} +\title{Amend golem config file} +\usage{ +amend_golem_config(key, value, config = "default", + pkg = get_golem_wd(), talkative = TRUE) +} +\arguments{ +\item{key}{key of the value to add in \code{config}} + +\item{value}{Name of value (\code{NULL} to read all values)} + +\item{config}{Name of configuration to read from. Defaults to +the value of the \code{R_CONFIG_NAME} environment variable +("default" if the variable does not exist).} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{talkative}{Should the messages be printed to the console?} +} +\description{ +Amend golem config file +} diff --git a/man/golem_name.Rd b/man/golem_name.Rd deleted file mode 100644 index e7f7b956..00000000 --- a/man/golem_name.Rd +++ /dev/null @@ -1,26 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{get_golem_name} -\alias{get_golem_name} -\alias{app_sys} -\title{A function to return the golem name to be used elsewhere -in the package.} -\usage{ -get_golem_name() - -app_sys(...) -} -\arguments{ -\item{...}{character vectors, specifying subdirectory and file(s) - within some package. The default, none, returns the - root of the package. Wildcards are not supported.} -} -\value{ -The name of the golem. - -The path to the current golem. -} -\description{ -This function allows to use \code{app_sys()} instead of using the -\code{system.file(, package = "pkg")}. -} diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd new file mode 100644 index 00000000..d9ead083 --- /dev/null +++ b/man/golem_opts.Rd @@ -0,0 +1,83 @@ +% 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} +\alias{get_golem_wd} +\alias{get_golem_name} +\alias{get_golem_version} +\title{\code{{golem}} options} +\usage{ +set_golem_options(golem_name = pkgload::pkg_name(), + golem_version = pkgload::pkg_version(), + golem_wd = pkgload::pkg_path(), app_prod = FALSE, talkative = TRUE) + +set_golem_wd(path = pkgload::pkg_path(), talkative = TRUE) + +set_golem_name(name = pkgload::pkg_name(), path = pkgload::pkg_path(), + talkative = TRUE) + +set_golem_version(version = pkgload::pkg_version(), + path = pkgload::pkg_path(), talkative = TRUE) + +get_golem_wd(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, path = pkgload::pkg_path()) + +get_golem_name(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, path = pkgload::pkg_path()) + +get_golem_version(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, path = pkgload::pkg_path()) +} +\arguments{ +\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{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{name}{The name of the app} + +\item{version}{The version of the app} + +\item{config}{Name of configuration to read from. Defaults to +the value of the \code{R_CONFIG_NAME} environment variable +("default" if the variable does not exist).} + +\item{use_parent}{\code{TRUE} to scan parent directories for +configuration files if the specified config file isn't found.} +} +\description{ +Set and get a series of options to be used with \code{{golem}}. +These options are found inside the \code{golem-config.yml} file, found in most cases +inside the \code{inst} folder. +} +\section{Set Functions}{ + +\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{pkgload::pkg_path()}, the current directory when starting a golem. +\item \code{set_golem_name()} defaults \code{pkgload::pkg_name()} +\item \code{set_golem_version()} defaults \code{pkgload::pkg_version()} +} +} + +\section{Get Functions}{ + +Reads the information from \code{golem-config.yml} +\itemize{ +\item \code{get_golem_wd()} +\item \code{get_golem_name()} +\item \code{get_golem_version()} +} +} + diff --git a/man/golem_wd.Rd b/man/golem_wd.Rd deleted file mode 100644 index e4bd279d..00000000 --- a/man/golem_wd.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{get_golem_wd} -\alias{get_golem_wd} -\alias{set_golem_wd} -\title{Get and set \code{{golem}} working directory} -\usage{ -get_golem_wd() - -set_golem_wd(path, talkative = TRUE) -} -\arguments{ -\item{path}{The path to set the golem working directory. -Note that it will be passed to \code{normalizePath}.} - -\item{talkative}{Should the function print where the -new path is defined?} -} -\value{ -The path to the working directory. -} -\description{ -Many \code{{golem}} functions rely on a specific working directory, -most of the time the root of the package. This working directory -is set by \code{set_golem_options} or the first time you create a file. -It default to \code{"."}, the current directory when starting a golem. -You can use these two functions if you need to manipulate this -directory. -} diff --git a/man/set_golem_options.Rd b/man/set_golem_options.Rd deleted file mode 100644 index 5942ae8f..00000000 --- a/man/set_golem_options.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\title{{golem} options} -\usage{ -set_golem_options(golem_name = pkgload::pkg_name(), - golem_version = pkgload::pkg_version(), - golem_wd = pkgload::pkg_path(), app_prod = FALSE) -} -\arguments{ -\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?} -} -\description{ -Set a series of options to be used internally by \code{{golem}}. -} diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 20a09493..d5cd87ca 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -39,7 +39,7 @@ fakename <- sprintf( gsub("[ :-]", "", Sys.time()) ) -tpdir <- tempdir() +tpdir <- normalizePath(tempdir()) unlink(file.path(tpdir,fakename), recursive = TRUE) create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) @@ -53,4 +53,9 @@ rand_name <- function(){ paste0(sample(letters, 10, TRUE), collapse = "") } +withr::with_dir(pkg, { + set_golem_options() +}) + orig_test <- set_golem_wd(pkg) +usethis::proj_set(pkg) diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R new file mode 100644 index 00000000..17379912 --- /dev/null +++ b/tests/testthat/test-config.R @@ -0,0 +1,35 @@ +test_that("config works", { + with_dir(pkg, { + expect_equal(get_golem_name(), fakename) + expect_equal(get_golem_version(), "0.0.0.9000") + expect_equal(get_golem_wd(), pkg) + amend_golem_config( + key = "where", + value = "indev" + ) + amend_golem_config( + key = "where", + value = "inprod", + config = "production" + ) + pkgload::load_all() + expect_equal(get_golem_config("where"), "indev") + expect_equal(get_golem_config("where", config = "production"), "inprod") + where_conf <- withr::with_envvar( + c("R_CONFIG_ACTIVE" = "production"), { + get_golem_config("where") + } + ) + 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(get_golem_wd(), normalizePath("inst")) + set_golem_wd(pkg) + }) +}) diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 7e838fa0..2c835c94 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -2,20 +2,18 @@ context("tests use_recomended functions") test_that("test use_recommended_deps",{ with_dir(pkg,{ - use_recommended_deps(pkg = pkg) + use_recommended_deps() packages <- c('shiny', 'DT', 'attempt', 'glue', 'golem', 'htmltools') - desc <- readLines("DESCRIPTION") - start <- grep("Imports:", desc) + 1 - desc <- desc[start:length(desc)] - test <- all(purrr::map_lgl(packages,function(x){any(grepl(x,desc))})) - expect_true(test) + deps <- desc::desc_get_deps(file = "DESCRIPTION") + test <- purrr::map_lgl(packages, ~ .x %in% deps$package) + expect_true(all(test)) }) }) test_that("test use_recommended_tests",{ with_dir(pkg,{ - use_recommended_tests(pkg = pkg) + use_recommended_tests() expect_true(dir.exists("tests")) expect_true(file.exists("tests/testthat/test-golem-recommended.R")) }) diff --git a/vignettes/config.Rmd b/vignettes/config.Rmd new file mode 100644 index 00000000..11556d68 --- /dev/null +++ b/vignettes/config.Rmd @@ -0,0 +1,140 @@ +--- +title: "Using golem config" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{config} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +library(golem) +x <- file.path( + tempdir(), + "golex" +) +unlink(x, TRUE, TRUE) + +create_golem(x, package_name = "golex", open = FALSE) +knitr::opts_knit$set(root.dir = x) +``` + +```{r setup} +library(golem) +``` + +## 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. + +By default, this file contains the name of your app, its version, and the default working directory (which is the root of your package). + +This config file is based on the [`{config}`](https://github.com/rstudio/config) format, which allows you to create configuration files for different application contexts. Please refer to this package documentation for more information. + +## Setting `golem-config` + +Here is what the default config file looks like: + +```{r echo = FALSE, comment= ""} +glue::as_glue( + readLines( + "inst/golem-config.yml" + ) +) +``` + +The good news is that if you don't want/need to use `{config}`, you can safely ignore this file, __just leave it where it is: it is used internally by the `{golem}` functions__. + +These options are globally set with: + +```{r} +set_golem_options() +``` + +The functions reading the options in this config file are: + +```{r} +get_golem_name() +get_golem_wd() +get_golem_version() +``` + +You can set these with: + +```{r} +set_golem_name("this") +set_golem_wd(".") +set_golem_version("0.0.1") +``` + + +## Using `golem-config` + +If you're already familiar with the `{config}` package, you can use this file just as any config file. + +`{golem}` comes with an `amend_golem_config()` function to add elements to it. + +```{r} +amend_golem_config( + key = "where", + value = "indev" +) +amend_golem_config( + key = "where", + value = "inprod", + config = "production" +) +``` + +Will result in a `golem-config.yml` file as such: + +```{r echo = FALSE, comment= ""} +glue::as_glue( + readLines( + file.path(x, "inst/golem-config.yml") + ) +) +``` + +## `app_config.R` + +In `R/app_config.R`, you'll find a `get_golem_config()` function that allows you to retrieve config from this config file: + +```{r} +pkgload::load_all() +get_golem_config( + "where" +) +get_golem_config( + "where", + config = "production" +) +``` + +Or using the env var (default `{config}` behavior): + +```{r} +Sys.setenv("R_CONFIG_ACTIVE" = "production") +get_golem_config("where") +``` + +## `golem_config` vs `golem_options` + +There is two ways to configure golem apps: + ++ The `golem_opts` in the `run_app()` function ++ The `golem-config.yml` file + +The big difference between these two is that the golem options from `run_app()` are meant to be configured during runtime: you'll be doing `run_app(val = "this")`, whereas the `golem-config` is meant to be used in the backend, and will not be linked to the parameters passed to `run_app()` (even if this is technically possible, this is not the main objective),. + +It's also linked to the `R_CONFIG_ACTIVE` environment variable, just as any `{config}` file. + +The idea is also that the `golem-config.yml` file is sharable across `{golem}` projects (`golem_opts` are application specific), and will be tracked by version control systems. + +## Note for pre `{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 From 7776ad74eb040555572375700c5b8cf384742e27 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 15 Nov 2019 12:40:50 +0100 Subject: [PATCH 126/211] explicit dependencies in config.R --- NAMESPACE | 3 +++ R/config.R | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 6516e6c1..86ae7c9f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -54,6 +54,7 @@ export(use_utils_ui) export(warning_dev) export(with_golem_options) importFrom(attempt,attempt) +importFrom(attempt,is_try_error) importFrom(attempt,stop_if) importFrom(attempt,stop_if_not) importFrom(attempt,without_warning) @@ -64,6 +65,7 @@ importFrom(config,get) importFrom(desc,desc_get_deps) importFrom(desc,description) importFrom(dockerfiler,Dockerfile) +importFrom(fs,file_copy) importFrom(glue,glue) importFrom(htmltools,includeScript) importFrom(htmltools,save_html) @@ -72,6 +74,7 @@ importFrom(jsonlite,fromJSON) importFrom(magrittr,"%>%") importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) +importFrom(pkgload,pkg_path) importFrom(purrr,map) importFrom(remotes,dev_package_deps) importFrom(remotes,package_deps) diff --git a/R/config.R b/R/config.R index 507c5033..76b11188 100644 --- a/R/config.R +++ b/R/config.R @@ -1,3 +1,5 @@ +#' @importFrom attempt attempt is_try_error +#' @importFrom pkgload pkg_path guess_where_config <- function( path ){ @@ -10,14 +12,14 @@ guess_where_config <- function( path <- "golem-config.yml" if (file.exists(path)) return(normalizePath(path)) # Trying with pkgpath - path <- attempt::attempt({ + path <- attempt({ file.path( - pkgload::pkg_path(), + pkg_path(), "inst/golem-config.yml" ) }) if ( - !attempt::is_try_error(path) & + !is_try_error(path) & file.exists(path) ) { return( @@ -27,6 +29,9 @@ guess_where_config <- function( return(NULL) } +#' @importFrom yesno yesno +#' @importFrom fs file_copy +#' @importFrom pkgload pkg_name get_current_config <- function( path = ".", set_options = TRUE @@ -42,7 +47,7 @@ get_current_config <- function( } if (!file.exists(path_conf)){ - ask <- yesno::yesno( + ask <- yesno( sprintf( "The %s file doesn't exist, create?", basename(path_conf) @@ -51,13 +56,13 @@ get_current_config <- function( # Return early if the user doesn't allow if (!ask) return(FALSE) - fs::file_copy( + file_copy( path = golem_sys("shinyexample/inst/golem-config.yml"), new_path = file.path( path, "inst/golem-config.yml" ) ) - fs::file_copy( + file_copy( path = golem_sys("shinyexample/R/app_config.R"), new_path = file.path( path, "R/app_config.R" @@ -66,7 +71,7 @@ get_current_config <- function( replace_word( "R/app_config.R", "shinyexample", - pkgload::pkg_name() + pkg_name() ) if (set_options){ set_golem_options() @@ -85,6 +90,7 @@ get_current_config <- function( #' @inheritParams set_golem_options #' #' @export +#' @importFrom yaml read_yaml write_yaml amend_golem_config <- function( key, value, @@ -93,9 +99,9 @@ amend_golem_config <- function( talkative = TRUE ){ conf_path <- get_current_config(pkg) - conf <- yaml::read_yaml(conf_path) + conf <- read_yaml(conf_path) conf[[config]][[key]] <- value - yaml::write_yaml( + write_yaml( conf, conf_path ) From b397a66dfc74a10caf5c2ea6bec3beba6b7b45ff Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 15 Nov 2019 13:15:37 +0100 Subject: [PATCH 127/211] typo in use_files --- R/use_files.R | 2 +- man/use_files.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/use_files.R b/R/use_files.R index 5ef60f49..b0aebfa8 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -1,6 +1,6 @@ #' Use Files #' -#' These functions download files from external sources and install them inside the approriate directory. +#' These functions download files from external sources and install them inside the appropriate directory. #' #' @inheritParams add_module #' @param url String representation of URL for the file to be downloaded diff --git a/man/use_files.Rd b/man/use_files.Rd index b6e3d472..4444d963 100644 --- a/man/use_files.Rd +++ b/man/use_files.Rd @@ -25,5 +25,5 @@ use_external_css_file(url, name, pkg = get_golem_wd(), \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} } \description{ -These functions download files from external sources and install them inside the approriate directory. +These functions download files from external sources and install them inside the appropriate directory. } From bc97baaca05724690aed030bea1826b5276d56f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinicius=20Melqu=C3=ADades=20de=20Sousa?= Date: Fri, 15 Nov 2019 10:46:56 -0300 Subject: [PATCH 128/211] remove space in shinyproxy dockerfile There were a white space in the CMD command in the shinyproxy dockerfile, between "options()" and "run_app()" call. The space make the argument be ignored when running the container. --- R/add_deploy_helpers.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 908ee512..13fd4a09 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -206,7 +206,7 @@ add_dockerfile_shinyproxy <- function( dock$EXPOSE(3838) dock$CMD(glue::glue( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0'); {read.dcf(path)[1]}::run_app()\"]" + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');{read.dcf(path)[1]}::run_app()\"]" )) dock$write(output) @@ -433,4 +433,4 @@ dock_from_desc <- function( dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") dock -} \ No newline at end of file +} From 4e3afbd3f20498c160d1ca27b470560441f0c50c Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 15 Nov 2019 22:07:25 +0100 Subject: [PATCH 129/211] the default wd for dev now relies on here::here() instead of full path. --- NAMESPACE | 1 - R/config.R | 37 ++++++++----- R/create_golem.R | 7 ++- R/options.R | 74 +++++++++++++++++++------- R/utils.R | 7 +++ man/golem_opts.Rd | 11 ++-- tests/testthat/helper-config.R | 6 +-- tests/testthat/test-add_dockerfile.R | 9 +++- tests/testthat/test-config.R | 2 +- vignettes/{config.Rmd => f_config.Rmd} | 4 ++ 10 files changed, 108 insertions(+), 50 deletions(-) rename vignettes/{config.Rmd => f_config.Rmd} (87%) diff --git a/NAMESPACE b/NAMESPACE index 86ae7c9f..c827fd2c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -55,7 +55,6 @@ export(warning_dev) export(with_golem_options) importFrom(attempt,attempt) importFrom(attempt,is_try_error) -importFrom(attempt,stop_if) importFrom(attempt,stop_if_not) importFrom(attempt,without_warning) importFrom(cli,cat_bullet) diff --git a/R/config.R b/R/config.R index 76b11188..1414a330 100644 --- a/R/config.R +++ b/R/config.R @@ -1,29 +1,29 @@ #' @importFrom attempt attempt is_try_error #' @importFrom pkgload pkg_path guess_where_config <- function( - path + path = "." ){ # Trying the path - path <- file.path( + ret_path <- file.path( path, "inst/golem-config.yml" ) - if (file.exists(path)) return(normalizePath(path)) + if (file.exists(ret_path)) return(normalizePath(ret_path)) # Trying maybe in the wd - path <- "golem-config.yml" - if (file.exists(path)) return(normalizePath(path)) + ret_path <- "golem-config.yml" + if (file.exists(ret_path)) return(normalizePath(ret_path)) # Trying with pkgpath - path <- attempt({ + ret_path <- attempt({ file.path( pkg_path(), "inst/golem-config.yml" ) }) if ( - !is_try_error(path) & - file.exists(path) + !is_try_error(ret_path) & + file.exists(ret_path) ) { return( - normalizePath(path) + normalizePath(ret_path) ) } return(NULL) @@ -38,7 +38,7 @@ get_current_config <- function( ){ # We check wether we can guess where the config file is - path_conf <- guess_where_config(path) + path_conf <- guess_where_config(path) # We default to inst/ if this doesn't exist if (is.null(path_conf)){ path_conf <- file.path( @@ -54,7 +54,7 @@ get_current_config <- function( ) ) # Return early if the user doesn't allow - if (!ask) return(FALSE) + if (!ask) return(NULL) file_copy( path = golem_sys("shinyexample/inst/golem-config.yml"), @@ -69,7 +69,9 @@ get_current_config <- function( ) ) replace_word( - "R/app_config.R", + file.path( + path, "R/app_config.R" + ), "shinyexample", pkg_name() ) @@ -78,7 +80,9 @@ get_current_config <- function( } } - return(path_conf) + return( + invisible(path_conf) + ) } @@ -99,7 +103,12 @@ amend_golem_config <- function( talkative = TRUE ){ conf_path <- get_current_config(pkg) - conf <- read_yaml(conf_path) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + conf <- read_yaml(conf_path, eval.expr = TRUE) conf[[config]][[key]] <- value write_yaml( conf, diff --git a/R/create_golem.R b/R/create_golem.R index 2d6a96b4..49e69461 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -45,9 +45,11 @@ create_golem <- function( } } + cat_rule("Creating dir") dir.create(path, recursive = TRUE, showWarnings = FALSE) cat_green_tick("Created package directory") + cat_rule("Copying package skeleton") from <- golem_sys("shinyexample") ll <- list.files(path = from, full.names = TRUE, all.files = TRUE,no.. = TRUE) # remove `..` @@ -85,10 +87,11 @@ create_golem <- function( } 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) - conf$default$golem_wd <- normalizePath(path) + conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) + conf$dev$golem_wd <- "!expr here::here()" conf$default$golem_name <- package_name conf$default$golem_version <- "0.0.0.9000" yaml::write_yaml(conf, yml_path) diff --git a/R/options.R b/R/options.R index 2b5e8668..d8a49d7d 100644 --- a/R/options.R +++ b/R/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 `pkgload::pkg_path()`, the current directory when starting a golem. +#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. #' + `set_golem_name()` defaults `pkgload::pkg_name()` #' + `set_golem_version()` defaults `pkgload::pkg_version()` #' @@ -30,7 +30,7 @@ #' @rdname golem_opts #' #' @export -#' @importFrom attempt stop_if +#' @importFrom attempt stop_if_not #' @importFrom yaml read_yaml write_yaml #' @importFrom usethis proj_set set_golem_options <- function( @@ -48,29 +48,39 @@ set_golem_options <- function( } conf_path <- get_current_config(golem_wd, set_options = FALSE) - + stop_if( conf_path, - isFALSE, - "You need to create golem-config.yml to set the options." + is.null, + "Unable to retrieve golem config file." ) - cat_if_talk("Setting {golem} options in `golem-config.yml`", fun= cli::cat_rule) + cat_if_talk( + "Setting {golem} options in `golem-config.yml`", + fun= cli::cat_rule + ) - conf <- read_yaml(conf_path) + conf <- read_yaml(conf_path, eval.expr = TRUE) # Setting wd + if (golem_wd == here::here()){ + path <- "here::here()" + attr(path, "tag") <- "!expr" + } else { + path <- golem_wd + } + cat_if_talk( sprintf( "Setting `golem_wd` to %s", - golem_wd + path ) ) cat_if_talk( "You can change golem working directory with set_golem_wd('path/to/wd')", fun = cat_line ) - conf$default$golem_wd <- golem_wd + conf$dev$golem_wd <- path # Setting name of the golem cat_if_talk( @@ -105,7 +115,10 @@ set_golem_options <- function( conf_path ) - cat_if_talk("Setting {usethis} project as `golem_wd`", fun= cli::cat_rule) + cat_if_talk( + "Setting {usethis} project as `golem_wd`", + fun = cli::cat_rule + ) proj_set(golem_wd) } @@ -115,10 +128,15 @@ set_golem_things <- function( key, value, path, - talkative + 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(...) @@ -133,9 +151,9 @@ set_golem_things <- function( ) ) - conf <- read_yaml(conf_path) - conf$default[[key]] <- value - write_yaml( + conf <- read_yaml(conf_path, eval.expr = TRUE) + conf[[config]][[key]] <- value + write_yaml( conf, conf_path ) @@ -150,13 +168,23 @@ set_golem_wd <- function( talkative = TRUE ){ path <- normalizePath(path, winslash = "/") + # Setting wd + + if (path == here::here()){ + path <- "here::here()" + attr(path, "tag") <- "!expr" + } + set_golem_things( "golem_wd", path, path, - talkative = talkative + talkative = talkative, + config = "dev" ) - + + invisible(path) + } #' @export @@ -174,6 +202,8 @@ set_golem_name <- function( talkative = talkative ) + invisible(name) + } #' @export @@ -191,6 +221,7 @@ set_golem_version <- function( talkative = talkative ) + invisible(version) } #' @importFrom config get @@ -201,7 +232,11 @@ get_golem_things <- function( 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, @@ -215,13 +250,12 @@ get_golem_things <- function( #' @export #' @rdname golem_opts get_golem_wd <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), use_parent = TRUE, path = pkgload::pkg_path() ){ get_golem_things( value = "golem_wd", - config = config, + config = "dev", use_parent = use_parent, path = path ) diff --git a/R/utils.R b/R/utils.R index 841d9f62..1a07a64f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -19,6 +19,13 @@ darkgrey <- function(x) { dir_not_exist <- Negate(dir.exists) file_not_exist <- Negate(file.exists) +is_package <- function(path){ + x <- attempt::attempt({ + pkgload::pkg_path() + }) + !attempt::is_try_error(x) +} + create_if_needed <- function( path, type = c("file", "directory"), diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index d9ead083..4b3e4428 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -22,8 +22,7 @@ set_golem_name(name = pkgload::pkg_name(), path = pkgload::pkg_path(), set_golem_version(version = pkgload::pkg_version(), path = pkgload::pkg_path(), talkative = TRUE) -get_golem_wd(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, path = pkgload::pkg_path()) +get_golem_wd(use_parent = TRUE, path = pkgload::pkg_path()) get_golem_name(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), use_parent = TRUE, path = pkgload::pkg_path()) @@ -49,12 +48,12 @@ Note that it will be passed to \code{normalizePath}.} \item{version}{The version of the app} +\item{use_parent}{\code{TRUE} to scan parent directories for +configuration files if the specified config file isn't found.} + \item{config}{Name of configuration to read from. Defaults to the value of the \code{R_CONFIG_NAME} environment variable ("default" if the variable does not exist).} - -\item{use_parent}{\code{TRUE} to scan parent directories for -configuration files if the specified config file isn't found.} } \description{ Set and get a series of options to be used with \code{{golem}}. @@ -65,7 +64,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{pkgload::pkg_path()}, the current directory when starting a golem. +\item \code{set_golem_wd()} defaults to \code{here::here()}, which is the package root when starting a golem. \item \code{set_golem_name()} defaults \code{pkgload::pkg_name()} \item \code{set_golem_version()} defaults \code{pkgload::pkg_version()} } diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index d5cd87ca..f868ff1d 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -1,8 +1,6 @@ ### lib library(withr) - ### Funs - remove_file <- function(path){ if (file.exists(path)) unlink(path, force = TRUE) } @@ -55,7 +53,7 @@ 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) -usethis::proj_set(pkg) diff --git a/tests/testthat/test-add_dockerfile.R b/tests/testthat/test-add_dockerfile.R index b525eec9..8ddf4a8b 100644 --- a/tests/testthat/test-add_dockerfile.R +++ b/tests/testthat/test-add_dockerfile.R @@ -3,9 +3,14 @@ context("function add_dockerfile") test_that("add_dockerfile", { with_dir(pkg, { unlink("Dockerfile", force = TRUE) - output <- testthat::capture_output(add_dockerfile(pkg = pkg, sysreqs = FALSE)) + output <- testthat::capture_output( + add_dockerfile(pkg = pkg, sysreqs = FALSE) + ) expect_exists("Dockerfile") - test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") + test <- stringr::str_detect( + output, + "Dockerfile created at Dockerfile" + ) expect_true(test) remove_files("Dockerfile") }) diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index 17379912..36c34018 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -12,7 +12,7 @@ test_that("config works", { value = "inprod", config = "production" ) - pkgload::load_all() + pkgload::load_all(path = pkg) expect_equal(get_golem_config("where"), "indev") expect_equal(get_golem_config("where", config = "production"), "inprod") where_conf <- withr::with_envvar( diff --git a/vignettes/config.Rmd b/vignettes/f_config.Rmd similarity index 87% rename from vignettes/config.Rmd rename to vignettes/f_config.Rmd index 11556d68..6a20817d 100644 --- a/vignettes/config.Rmd +++ b/vignettes/f_config.Rmd @@ -47,6 +47,10 @@ glue::as_glue( ) ``` ++ default/golem_name, default/golem_version, default/app_prod are usable across the whole life of your golem app: while developping, and also when in production. ++ production/app_prod might be used for adding elements that are to be used once the app is in production. ++ dev/golem_wd is in a `dev` config because the only moment you might reliably use this config is while developping your app. Use the `app_sys()` function if you want to rely on the package path once the app is deployed. + The good news is that if you don't want/need to use `{config}`, you can safely ignore this file, __just leave it where it is: it is used internally by the `{golem}` functions__. These options are globally set with: From a52b671c5d6c9236ad15cd4369a280c6093f78f6 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 15 Nov 2019 22:14:56 +0100 Subject: [PATCH 130/211] forgot attr in golem_wd on golem creation --- R/create_golem.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/create_golem.R b/R/create_golem.R index 49e69461..97c79d07 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -91,7 +91,10 @@ create_golem <- function( yml_path <- file.path(path, "inst/golem-config.yml") conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - conf$dev$golem_wd <- "!expr here::here()" + + path <- "here::here()" + attr(path, "tag") <- "!expr" + conf$dev$golem_wd <- path conf$default$golem_name <- package_name conf$default$golem_version <- "0.0.0.9000" yaml::write_yaml(conf, yml_path) From 2a0520523098e8348745f5e29c2c6940d8d539dd Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Fri, 15 Nov 2019 23:23:41 +0100 Subject: [PATCH 131/211] remove browser --- R/add_deploy_helpers.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index ead49fbe..115131f5 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -292,7 +292,6 @@ add_dockerfile_heroku <- function( cat_red_bullet( glue("You can replace {apps_h} with another app name.") ) - browser() if (open) { if (rstudioapi::isAvailable()) { rstudioapi::navigateToFile(output) From ad16f210c4dcc185936addd7872c7b99c8177de0 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Fri, 15 Nov 2019 23:32:07 +0100 Subject: [PATCH 132/211] merge with dev --- DESCRIPTION | 20 +- Dockerfile | 38 +++ NAMESPACE | 13 +- NEWS.md | 2 + R/add_deploy_helpers.R | 60 +++-- R/create_golem.R | 41 ++- R/desc.R | 58 ++++- R/get_sysreqs.R | 2 +- R/options.R | 325 +++++++++++++++++++----- R/use_files.R | 2 +- R/utils.R | 63 ++++- devtools_history.R | 1 + inst/shinyexample/DESCRIPTION | 1 + inst/shinyexample/NAMESPACE | 2 +- inst/shinyexample/R/app_config.R | 18 ++ inst/shinyexample/R/app_server.R | 2 + inst/shinyexample/R/app_ui.R | 7 +- inst/shinyexample/inst/golem-config.yml | 8 + man/amend_golem_config.Rd | 25 ++ man/dock_from_desc.Rd | 4 +- man/dockerfiles.Rd | 13 +- man/golem_name.Rd | 26 -- man/golem_opts.Rd | 82 ++++++ man/golem_wd.Rd | 29 --- man/set_golem_options.Rd | 22 -- man/use_files.Rd | 2 +- tests/testthat/helper-config.R | 24 +- tests/testthat/test-add_dockerfile.R | 9 +- tests/testthat/test-config.R | 35 +++ tests/testthat/test-use_recomended.R | 12 +- vignettes/f_config.Rmd | 144 +++++++++++ 31 files changed, 863 insertions(+), 227 deletions(-) create mode 100644 Dockerfile create mode 100644 inst/shinyexample/R/app_config.R create mode 100644 inst/shinyexample/inst/golem-config.yml create mode 100644 man/amend_golem_config.Rd delete mode 100644 man/golem_name.Rd create mode 100644 man/golem_opts.Rd delete mode 100644 man/golem_wd.Rd delete mode 100644 man/set_golem_options.Rd create mode 100644 tests/testthat/test-config.R create mode 100644 vignettes/f_config.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index d940ff37..bfddb9c1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,10 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9500 -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. +Version: 0.1.0.9600 Authors@R: c(person(given = "Vincent", family = "Guyader", @@ -31,7 +27,11 @@ Authors@R: role = "ctb", email = "nnovica@gmail.com"), person(given = "ThinkR", - role = "cph")) + 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. License: MIT + file LICENSE URL: https://github.com/ThinkR-open/golem BugReports: https://github.com/ThinkR-open/golem/issues @@ -40,14 +40,17 @@ Depends: Imports: attempt (>= 0.3.0), cli, + config, crayon, desc, dockerfiler, + fs, glue, htmltools, jsonlite, magrittr, pkgload, + purrr, remotes, rlang, roxygen2, @@ -59,12 +62,11 @@ Imports: tools, usethis, utils, - yesno, - purrr + yaml, + yesno Suggests: covr, devtools, - DT, knitr, pkgdown, rcmdcheck, diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f6907016 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM rocker/r-ver:3.5.1 +RUN echo "options(repos = c(CRAN = 'https://cran.rstudio.com/'), download.file.method = 'libcurl')" >> /usr/local/lib/R/etc/Rprofile.site +RUN R -e 'install.packages("remotes")' +RUN R -e 'remotes::install_github("r-lib/remotes", ref = "97bbf81")' +RUN Rscript -e 'remotes::install_version("attempt", version = "0.3.0")' +RUN Rscript -e 'remotes::install_version("cli", version = "1.1.0")' +RUN Rscript -e 'remotes::install_version("crayon", version = "1.3.4")' +RUN Rscript -e 'remotes::install_version("dockerfiler", version = "0.1.3")' +RUN Rscript -e 'remotes::install_version("glue", version = "1.3.1")' +RUN Rscript -e 'remotes::install_version("htmltools", version = "0.4.0")' +RUN Rscript -e 'remotes::install_version("jsonlite", version = "1.6")' +RUN Rscript -e 'remotes::install_version("magrittr", version = "1.5")' +RUN Rscript -e 'remotes::install_version("pkgload", version = "1.0.2")' +RUN Rscript -e 'remotes::install_version("remotes", version = "2.1.0")' +RUN Rscript -e 'remotes::install_version("rlang", version = "0.4.0")' +RUN Rscript -e 'remotes::install_version("roxygen2", version = "6.1.1")' +RUN Rscript -e 'remotes::install_version("rsconnect", version = "0.8.15")' +RUN Rscript -e 'remotes::install_version("rstudioapi", version = "0.10")' +RUN Rscript -e 'remotes::install_version("shiny", version = "1.4.0")' +RUN Rscript -e 'remotes::install_version("testthat", version = "2.2.1")' +RUN Rscript -e 'remotes::install_version("usethis", version = "1.5.1")' +RUN Rscript -e 'remotes::install_version("yesno", version = "0.1.0")' +RUN Rscript -e 'remotes::install_version("purrr", version = "0.3.3")' +RUN Rscript -e 'remotes::install_version("covr", version = "3.3.2")' +RUN Rscript -e 'remotes::install_version("devtools", version = "2.2.1")' +RUN Rscript -e 'remotes::install_version("DT", version = "0.9")' +RUN Rscript -e 'remotes::install_version("knitr", version = "1.25")' +RUN Rscript -e 'remotes::install_version("pkgdown", version = "1.4.1")' +RUN Rscript -e 'remotes::install_version("rcmdcheck", version = "1.3.3")' +RUN Rscript -e 'remotes::install_version("rmarkdown", version = "1.16")' +RUN Rscript -e 'remotes::install_version("spelling", version = "2.1")' +RUN Rscript -e 'remotes::install_version("stringr", version = "1.4.0")' +RUN Rscript -e 'remotes::install_version("withr", version = "2.1.2")' +RUN Rscript -e 'remotes::install_github("r-lib/desc@42b9578f374bf685610b1efb05315927ae236d5b")' +RUN Rscript -e 'remotes::install_github("rstudio/packrat@c0a67564df0bff16c952117050f510f2c8eace0a")' +COPY golem_*.tar.gz /app.tar.gz +RUN R -e 'remotes::install_local("/app.tar.gz")' +CMD R -e "options('shiny.port'=$PORT,shiny.host='0.0.0.0');golem::run_app()" diff --git a/NAMESPACE b/NAMESPACE index 24a712c0..11fc2e92 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,9 +15,9 @@ export(add_shinyappsio_file) export(add_shinyserver_file) export(add_ui_server_files) export(add_utils) +export(amend_golem_config) export(app_dev) export(app_prod) -export(app_sys) export(browser_button) export(browser_dev) export(cat_dev) @@ -31,6 +31,7 @@ export(favicon) export(fill_desc) export(get_golem_name) export(get_golem_options) +export(get_golem_version) export(get_golem_wd) export(get_sysreqs) export(insert_ns) @@ -39,7 +40,9 @@ export(make_dev) export(message_dev) export(print_dev) export(remove_favicon) +export(set_golem_name) export(set_golem_options) +export(set_golem_version) export(set_golem_wd) export(use_external_css_file) export(use_external_js_file) @@ -51,14 +54,18 @@ export(use_utils_ui) export(warning_dev) export(with_golem_options) importFrom(attempt,attempt) +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(config,get) importFrom(desc,desc_get_deps) importFrom(desc,description) importFrom(dockerfiler,Dockerfile) +importFrom(fs,file_copy) importFrom(glue,glue) importFrom(htmltools,includeScript) importFrom(htmltools,save_html) @@ -67,6 +74,7 @@ importFrom(jsonlite,fromJSON) importFrom(magrittr,"%>%") importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) +importFrom(pkgload,pkg_path) importFrom(purrr,map) importFrom(remotes,dev_package_deps) importFrom(remotes,package_deps) @@ -85,6 +93,7 @@ importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) importFrom(tools,file_path_sans_ext) +importFrom(usethis,proj_set) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) importFrom(usethis,use_testthat) @@ -95,4 +104,6 @@ importFrom(utils,getFromNamespace) importFrom(utils,installed.packages) importFrom(utils,packageVersion) importFrom(utils,sessionInfo) +importFrom(yaml,read_yaml) +importFrom(yaml,write_yaml) importFrom(yesno,yesno) diff --git a/NEWS.md b/NEWS.md index 1bd6e902..d136caa2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ + `add_dockerfile()` was completely refactore. 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_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). + We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 115131f5..c76b0776 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -123,6 +123,8 @@ add_shinyserver_file <- function( #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line #' @param open boolean, default is `TRUE` open the Dockerfile file +#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it +#' @param update_tar_gz boolean, if `TRUE` and build_golem_from_source is also `TRUE` an updated tar.gz Package is created #' @export #' @rdname dockerfiles #' @importFrom desc desc_get_deps @@ -159,7 +161,9 @@ add_dockerfile <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - open = TRUE + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = FALSE # , function_to_launch = "run_app" ) { @@ -168,10 +172,8 @@ add_dockerfile <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) - - - - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos,expand = expand) + dock <- dock_from_desc(path = path, FROM = from, AS = as, + sysreqs = sysreqs, repos = repos,expand = expand,build_golem_from_source = build_golem_from_source) dock$EXPOSE(port) dock$CMD( glue::glue( @@ -186,7 +188,9 @@ add_dockerfile <- function( try(file.edit(output)) } } - alert_build(path, output) + alert_build(path = path, + output = output, + build_golem_from_source=build_golem_from_source) } @@ -205,7 +209,9 @@ add_dockerfile_shinyproxy <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - open = TRUE + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = FALSE ){ where <- file.path(pkg, output) @@ -213,7 +219,7 @@ add_dockerfile_shinyproxy <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) dock <- dock_from_desc(path = path, FROM = from, AS = as, - sysreqs = sysreqs, repos = repos, expand = expand) + sysreqs = sysreqs, repos = repos, expand = expand,build_golem_from_source=build_golem_from_source) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -221,7 +227,6 @@ add_dockerfile_shinyproxy <- function( )) dock$write(output) - alert_build(path, output) if (open) { if (rstudioapi::isAvailable()) { rstudioapi::navigateToFile(output) @@ -229,6 +234,8 @@ add_dockerfile_shinyproxy <- function( try(file.edit(output)) } } + alert_build(path, output,build_golem_from_source=build_golem_from_source) + usethis::use_build_ignore(files = output) invisible(output) @@ -250,7 +257,9 @@ add_dockerfile_heroku <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - open = TRUE + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = FALSE ){ where <- file.path(pkg, output) @@ -258,7 +267,7 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, expand = expand) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, expand = expand,build_golem_from_source = build_golem_from_source) dock$CMD( glue::glue( @@ -267,7 +276,7 @@ add_dockerfile_heroku <- function( ) dock$write(output) - alert_build(path, output) + alert_build(path = path,output = output,build_golem_from_source=build_golem_from_source) apps_h <- gsub( "\\.", "-", @@ -304,15 +313,17 @@ add_dockerfile_heroku <- function( } -alert_build <- function(path, output){ +alert_build <- function(path, output ,build_golem_from_source){ cat_green_tick( glue("Dockerfile created at {output}") ) + if ( ! build_golem_from_source){ cat_red_bullet( glue::glue( "Be sure to put your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" ) ) + } } #' Create Dockerfile from DESCRIPTION @@ -325,7 +336,7 @@ alert_build <- function(path, output){ #' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line -#' +#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps @@ -342,7 +353,8 @@ dock_from_desc <- function( AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE + expand = FALSE, + build_golem_from_source = FALSE ){ @@ -447,14 +459,24 @@ dock_from_desc <- function( dock - - - - dock$COPY( + if ( !build_golem_from_source){ + # we use a already builded tar.gz file + dock$COPY( from = paste0(read.dcf(path)[1], "_*.tar.gz"), to = "/app.tar.gz" ) dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") + } else { + # + dock$RUN("mkdir /build_zone") + dock$ADD(from = ".",to = "/build_zone") + dock$WORKDIR("/build_zone") + #dock$RUN("R -e 'setwd(\"/build_zone\");devtools::build(path = \".\")'") + dock$RUN("R -e 'remotes::install_local()'") + + + + } dock } \ No newline at end of file diff --git a/R/create_golem.R b/R/create_golem.R index 90f7fac1..97c79d07 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -30,10 +30,10 @@ create_golem <- function( package_name <- basename(getwd()) } - - 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)){ @@ -44,8 +44,12 @@ create_golem <- function( return(invisible(NULL)) } } + + cat_rule("Creating dir") dir.create(path, recursive = TRUE, showWarnings = FALSE) + cat_green_tick("Created package directory") + cat_rule("Copying package skeleton") from <- golem_sys("shinyexample") ll <- list.files(path = from, full.names = TRUE, all.files = TRUE,no.. = TRUE) # remove `..` @@ -81,7 +85,21 @@ create_golem <- function( }, 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) + + path <- "here::here()" + attr(path, "tag") <- "!expr" + conf$dev$golem_wd <- path + conf$default$golem_name <- package_name + conf$default$golem_version <- "0.0.0.9000" + yaml::write_yaml(conf, yml_path) + + cat_green_tick("Configured app") if ( without_comments == TRUE ) { files <- list.files( @@ -96,18 +114,23 @@ create_golem <- function( } } + cat_rule("Done") - cat_line(paste0("A new golem package ", package_name, " was created in ", get_golem_wd(), "/", package_name, - " directory.\n", - "To continue work on your package start editing the 01_start.R file")) - - + 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 ( open & rstudioapi::isAvailable() ) { rstudioapi::openProject(path = path) } - return( invisible( normalizePath(path) diff --git a/R/desc.R b/R/desc.R index a3db4d42..8658f51b 100644 --- a/R/desc.R +++ b/R/desc.R @@ -22,21 +22,57 @@ fill_desc <- function( author_email, repo_url = NULL, pkg = get_golem_wd() - ){ +){ + path <- normalizePath(pkg) + desc <- desc::description$new( file = file.path(path, "DESCRIPTION") ) - desc$set("Authors@R", glue("person('{author_first_name}', '{author_last_name}', email = '{author_email}', role = c('cre', 'aut'))")) - desc$del("Maintainer") - desc$set_version("0.0.0.9000") - desc$set(Package = pkg_name) - desc$set(Title = pkg_title) - desc$set(Description = pkg_description) - if_not_null(repo_url, desc$set("URL", repo_url)) - if_not_null(repo_url, desc$set("BugReports", glue("{repo_url}/issues"))) - desc$write(file = "DESCRIPTION") - cat_bullet("DESCRIPTION file modified", bullet = "tick", bullet_col = "green") + desc$set( + "Authors@R", + glue("person('{author_first_name}', '{author_last_name}', email = '{author_email}', role = c('cre', 'aut'))") + ) + desc$del( + keys = "Maintainer" + ) + desc$set_version( + version = "0.0.0.9000" + ) + desc$set( + Package = pkg_name + ) + desc$set( + Title = pkg_title + ) + desc$set( + Description = pkg_description + ) + if_not_null( + repo_url, + desc$set( + "URL", + repo_url + ) + ) + if_not_null( + repo_url, + desc$set( + "BugReports", + glue("{repo_url}/issues" + ) + ) + ) + + desc$write( + file = "DESCRIPTION" + ) + + cat_bullet( + "DESCRIPTION file modified", + bullet = "tick", + bullet_col = "green" + ) } diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 1f660036..f60807ad 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -41,6 +41,6 @@ get_batch_sysreqs <- function(all_deps,quiet=TRUE){ - sort(unique(out[!is.na(out)])) + unique(out[!is.na(out)]) } diff --git a/R/options.R b/R/options.R index de7b0807..d8a49d7d 100644 --- a/R/options.R +++ b/R/options.R @@ -1,102 +1,293 @@ -#' {golem} options +#' `{golem}` options #' -#' Set a series of options to be used internally by `{golem}`. +#' 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 `pkgload::pkg_name()` +#' + `set_golem_version()` defaults `pkgload::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 yaml read_yaml write_yaml +#' @importFrom usethis proj_set set_golem_options <- function( golem_name = pkgload::pkg_name(), golem_version = pkgload::pkg_version(), golem_wd = pkgload::pkg_path(), - app_prod = FALSE + app_prod = FALSE, + talkative = TRUE ){ - cli::cat_rule("Setting {golem} options") - options("golem.pkg.name" = golem_name) - cat_green_tick(sprintf("Setting options('golem.pkg.name') to %s", golem_name)) - options("golem.pkg.version" = golem_version) - cat_green_tick(sprintf("Setting options('golem.pkg.version') to %s", golem_version)) - set_golem_wd(golem_wd, FALSE) - cat_green_tick(sprintf("Setting options('golem.wd') to %s", golem_wd)) - cat_line("You can change golem working directory with set_golem_wd('path/to/wd')") - options("golem.app.prod" = app_prod) - cat_green_tick(sprintf("Setting options('golem.app.prod') to %s", app_prod)) + + cat_if_talk <- function(..., fun = cat_green_tick){ + if (talkative){ + fun(...) + } + } + + conf_path <- get_current_config(golem_wd, set_options = FALSE) + + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + + cat_if_talk( + "Setting {golem} options in `golem-config.yml`", + fun= cli::cat_rule + ) + + conf <- read_yaml(conf_path, eval.expr = TRUE) + + # Setting wd + if (golem_wd == here::here()){ + path <- "here::here()" + attr(path, "tag") <- "!expr" + } else { + path <- golem_wd + } + + 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 + ) + conf$dev$golem_wd <- path + + # 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) + } -#' Get and set `{golem}` working directory -#' -#' Many `{golem}` functions rely on a specific working directory, -#' most of the time the root of the package. This working directory -#' is set by `set_golem_options` or the first time you create a file. -#' It default to `"."`, the current directory when starting a golem. -#' You can use these two functions if you need to manipulate this -#' directory. -#' -#' @param path The path to set the golem working directory. -#' Note that it will be passed to `normalizePath`. -#' @param talkative Should the function print where the -#' new path is defined? -#' -#' @return The path to the working directory. -#' @export -#' @rdname golem_wd -get_golem_wd <- function(){ - if (is.null(getOption("golem.wd"))){ - cat_red_bullet("Couldn't find golem working directory") - cat_green_tick("Definining golem working directory as `.`") - cat_line("You can change golem working directory with set_golem_wd('path/to/wd')") - set_golem_wd(".") +#' @importFrom yaml read_yaml write_yaml +set_golem_things <- function( + 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(...) + } } - getOption("golem.wd") + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + conf <- read_yaml(conf_path, eval.expr = TRUE) + conf[[config]][[key]] <- value + write_yaml( + conf, + conf_path + ) + + invisible(path) } - #' @export -#' @rdname golem_wd +#' @rdname golem_opts set_golem_wd <- function( - path, + path = pkgload::pkg_path(), talkative = TRUE ){ path <- normalizePath(path, winslash = "/") - if (talkative){ - cat_green_tick( - sprintf("Definining golem working directory as `%s`", path) - ) + # Setting wd + + if (path == here::here()){ + path <- "here::here()" + attr(path, "tag") <- "!expr" } - options("golem.wd" = path) + + set_golem_things( + "golem_wd", + path, + path, + talkative = talkative, + config = "dev" + ) + invisible(path) + } +#' @export +#' @rdname golem_opts +set_golem_name <- function( + name = pkgload::pkg_name(), + path = pkgload::pkg_path(), + talkative = TRUE +){ + path <- normalizePath(path, winslash = "/") + set_golem_things( + "golem_name", + name, + path, + talkative = talkative + ) + + invisible(name) + +} -#' A function to return the golem name to be used elsewhere -#' in the package. -#' -#' @return The name of the golem. #' @export -#' @rdname golem_name -#' @importFrom pkgload pkg_name +#' @rdname golem_opts +set_golem_version <- function( + version = pkgload::pkg_version(), + path = pkgload::pkg_path(), + talkative = TRUE +){ + path <- normalizePath(path, winslash = "/") + set_golem_things( + "golem_version", + as.character(version), + path, + talkative = talkative + ) + + invisible(version) +} -get_golem_name <- function(){ - if (is.null(getOption("golem_name"))){ - options("golem.pkg.name" = pkg_name()) - } - getOption("golem.pkg.name") +#' @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 = pkgload::pkg_path() +){ + get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) } -#' A wrapper around `system.file(..., package = get_golem_name())` -#' -#' This function allows to use `app_sys()` instead of using the -#' `system.file(, package = "pkg")`. -#' -#' @inheritParams base::system.file -#' @return The path to the current golem. #' @export -#' @rdname golem_name +#' @rdname golem_opts +get_golem_name <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = pkgload::pkg_path() +){ + get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) +} #' @export -app_sys <- function(...) { - getFromNamespace("shim_system.file", "pkgload")(..., package = get_golem_name()) +#' @rdname golem_opts +get_golem_version <- function( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = pkgload::pkg_path() +){ + get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) } + diff --git a/R/use_files.R b/R/use_files.R index 5ef60f49..b0aebfa8 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -1,6 +1,6 @@ #' Use Files #' -#' These functions download files from external sources and install them inside the approriate directory. +#' These functions download files from external sources and install them inside the appropriate directory. #' #' @inheritParams add_module #' @param url String representation of URL for the file to be downloaded diff --git a/R/utils.R b/R/utils.R index c06d58ea..1a07a64f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,9 +1,16 @@ -golem_sys <- function(..., lib.loc = NULL, mustWork = FALSE){ - system.file(..., package = "golem", lib.loc = lib.loc, mustWork = mustWork) +golem_sys <- function( + ..., + lib.loc = NULL, + mustWork = FALSE +){ + system.file( + ..., + package = "golem", + lib.loc = lib.loc, + mustWork = mustWork + ) } - - # from usethis https://github.com/r-lib/usethis/ darkgrey <- function(x) { x <- crayon::make_style("darkgrey")(x) @@ -12,6 +19,54 @@ darkgrey <- function(x) { dir_not_exist <- Negate(dir.exists) file_not_exist <- Negate(file.exists) +is_package <- function(path){ + x <- attempt::attempt({ + pkgload::pkg_path() + }) + !attempt::is_try_error(x) +} + +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) + } else if (type == "directory"){ + dont_exist <- dir_not_exist(path) + } + # If it doesn't exist, ask if we are allowed + # to create it + if (dont_exist){ + ask <- yesno::yesno( + sprintf( + "The %s %s doesn't exist, create?", + basename(path), + type + ) + ) + # Return early if the user doesn't allow + if (!ask) return(FALSE) + + } else { + return(TRUE) + } + + # Create the file + if (type == "file"){ + fs::file_create(path) + write(content, path, append = TRUE) + } else if (type == "directory"){ + fs::dir_create(path, recurse = TRUE) + } + # TRUE means that file exists (either + # created or already there) + return(TRUE) +} + create_dir_if_needed <- function( path, auto_create diff --git a/devtools_history.R b/devtools_history.R index b49f80e5..49d36e71 100644 --- a/devtools_history.R +++ b/devtools_history.R @@ -26,6 +26,7 @@ usethis::use_test("reload") usethis::use_test("js") usethis::use_test("aaadependencies") usethis::use_test("create_golem") +usethis::use_test("config") # Travis usethis::use_travis() diff --git a/inst/shinyexample/DESCRIPTION b/inst/shinyexample/DESCRIPTION index fd7f00da..7945e0c1 100644 --- a/inst/shinyexample/DESCRIPTION +++ b/inst/shinyexample/DESCRIPTION @@ -9,6 +9,7 @@ Authors@R: Description: What the package does (one paragraph). License: What license is it under? Imports: + config, golem, shiny Encoding: UTF-8 diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index 064445b7..bf248860 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -1,7 +1,7 @@ # Generated by roxygen2: do not edit by hand export(run_app) +import(config) import(shiny) -importFrom(golem,app_sys) importFrom(golem,with_golem_options) importFrom(shiny,shinyApp) diff --git a/inst/shinyexample/R/app_config.R b/inst/shinyexample/R/app_config.R new file mode 100644 index 00000000..6408d6ed --- /dev/null +++ b/inst/shinyexample/R/app_config.R @@ -0,0 +1,18 @@ +app_sys <- function(...){ + system.file(..., package = "shinyexample") +} + +#' @import config +get_golem_config <- function( + value, + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE +){ + config::get( + value = value, + config = config, + # Modify this if your config file is somewhere else: + file = app_sys("golem-config.yml"), + use_parent = use_parent + ) +} \ No newline at end of file diff --git a/inst/shinyexample/R/app_server.R b/inst/shinyexample/R/app_server.R index 1856773d..11bb8c98 100644 --- a/inst/shinyexample/R/app_server.R +++ b/inst/shinyexample/R/app_server.R @@ -1,3 +1,5 @@ +#' App Server +#' #' @import shiny app_server <- function(input, output,session) { # List the first level callModules here diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 8fade0ed..c7c763a9 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,6 +1,4 @@ -#' ui -#' -#' @param request needed for bookmarking +#' Application UI #' #' @import shiny app_ui <- function(request) { @@ -13,9 +11,8 @@ app_ui <- function(request) { ) ) } - + #' @import shiny -#' @importFrom golem app_sys golem_add_external_resources <- function(){ golem::add_resource_path( diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml new file mode 100644 index 00000000..00ae9a47 --- /dev/null +++ b/inst/shinyexample/inst/golem-config.yml @@ -0,0 +1,8 @@ +default: + golem_name: shinyexample + golem_version: 0.0.0.9000 + app_prod: no + +production: + app_prod: yes + diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd new file mode 100644 index 00000000..5ec8185c --- /dev/null +++ b/man/amend_golem_config.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{amend_golem_config} +\alias{amend_golem_config} +\title{Amend golem config file} +\usage{ +amend_golem_config(key, value, config = "default", + pkg = get_golem_wd(), talkative = TRUE) +} +\arguments{ +\item{key}{key of the value to add in \code{config}} + +\item{value}{Name of value (\code{NULL} to read all values)} + +\item{config}{Name of configuration to read from. Defaults to +the value of the \code{R_CONFIG_NAME} environment variable +("default" if the variable does not exist).} + +\item{pkg}{Path to the root of the package. Default is \code{"."}.} + +\item{talkative}{Should the messages be printed to the console?} +} +\description{ +Amend golem config file +} diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd index 2655fcf5..72d823de 100644 --- a/man/dock_from_desc.Rd +++ b/man/dock_from_desc.Rd @@ -7,7 +7,7 @@ dock_from_desc(path = "DESCRIPTION", FROM = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE) + expand = FALSE, build_golem_from_source = FALSE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -22,6 +22,8 @@ with \code{R.Version()$major} and \code{R.Version()$minor}.} \item{repos}{character vector, the base URL of the repositories} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} + +\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} } \description{ Create Dockerfile from DESCRIPTION diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index ef3b5d7c..b3594163 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -10,19 +10,22 @@ add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", sysreqs = TRUE, - repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE) + repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE, + update_tar_gz = TRUE, build_golem_from_source = FALSE) add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, open = TRUE) + expand = FALSE, open = TRUE, update_tar_gz = TRUE, + build_golem_from_source = FALSE) add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, open = TRUE) + expand = FALSE, open = TRUE, update_tar_gz = TRUE, + build_golem_from_source = FALSE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -49,6 +52,10 @@ Default is 0.0.0.0.} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} \item{open}{boolean, default is \code{TRUE} open the Dockerfile file} + +\item{update_tar_gz}{boolean, if \code{TRUE} and build_golem_from_source is also \code{TRUE} an updated tar.gz Package is created} + +\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} } \description{ Build a container containing your Shiny App. \code{add_dockerfile()} creates diff --git a/man/golem_name.Rd b/man/golem_name.Rd deleted file mode 100644 index e7f7b956..00000000 --- a/man/golem_name.Rd +++ /dev/null @@ -1,26 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{get_golem_name} -\alias{get_golem_name} -\alias{app_sys} -\title{A function to return the golem name to be used elsewhere -in the package.} -\usage{ -get_golem_name() - -app_sys(...) -} -\arguments{ -\item{...}{character vectors, specifying subdirectory and file(s) - within some package. The default, none, returns the - root of the package. Wildcards are not supported.} -} -\value{ -The name of the golem. - -The path to the current golem. -} -\description{ -This function allows to use \code{app_sys()} instead of using the -\code{system.file(, package = "pkg")}. -} diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd new file mode 100644 index 00000000..4b3e4428 --- /dev/null +++ b/man/golem_opts.Rd @@ -0,0 +1,82 @@ +% 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} +\alias{get_golem_wd} +\alias{get_golem_name} +\alias{get_golem_version} +\title{\code{{golem}} options} +\usage{ +set_golem_options(golem_name = pkgload::pkg_name(), + golem_version = pkgload::pkg_version(), + golem_wd = pkgload::pkg_path(), app_prod = FALSE, talkative = TRUE) + +set_golem_wd(path = pkgload::pkg_path(), talkative = TRUE) + +set_golem_name(name = pkgload::pkg_name(), path = pkgload::pkg_path(), + talkative = TRUE) + +set_golem_version(version = pkgload::pkg_version(), + path = pkgload::pkg_path(), talkative = TRUE) + +get_golem_wd(use_parent = TRUE, path = pkgload::pkg_path()) + +get_golem_name(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, path = pkgload::pkg_path()) + +get_golem_version(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, path = pkgload::pkg_path()) +} +\arguments{ +\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{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{name}{The name of the app} + +\item{version}{The version of the app} + +\item{use_parent}{\code{TRUE} to scan parent directories for +configuration files if the specified config file isn't found.} + +\item{config}{Name of configuration to read from. Defaults to +the value of the \code{R_CONFIG_NAME} environment variable +("default" if the variable does not exist).} +} +\description{ +Set and get a series of options to be used with \code{{golem}}. +These options are found inside the \code{golem-config.yml} file, found in most cases +inside the \code{inst} folder. +} +\section{Set Functions}{ + +\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_name()} defaults \code{pkgload::pkg_name()} +\item \code{set_golem_version()} defaults \code{pkgload::pkg_version()} +} +} + +\section{Get Functions}{ + +Reads the information from \code{golem-config.yml} +\itemize{ +\item \code{get_golem_wd()} +\item \code{get_golem_name()} +\item \code{get_golem_version()} +} +} + diff --git a/man/golem_wd.Rd b/man/golem_wd.Rd deleted file mode 100644 index e4bd279d..00000000 --- a/man/golem_wd.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{get_golem_wd} -\alias{get_golem_wd} -\alias{set_golem_wd} -\title{Get and set \code{{golem}} working directory} -\usage{ -get_golem_wd() - -set_golem_wd(path, talkative = TRUE) -} -\arguments{ -\item{path}{The path to set the golem working directory. -Note that it will be passed to \code{normalizePath}.} - -\item{talkative}{Should the function print where the -new path is defined?} -} -\value{ -The path to the working directory. -} -\description{ -Many \code{{golem}} functions rely on a specific working directory, -most of the time the root of the package. This working directory -is set by \code{set_golem_options} or the first time you create a file. -It default to \code{"."}, the current directory when starting a golem. -You can use these two functions if you need to manipulate this -directory. -} diff --git a/man/set_golem_options.Rd b/man/set_golem_options.Rd deleted file mode 100644 index 5942ae8f..00000000 --- a/man/set_golem_options.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\title{{golem} options} -\usage{ -set_golem_options(golem_name = pkgload::pkg_name(), - golem_version = pkgload::pkg_version(), - golem_wd = pkgload::pkg_path(), app_prod = FALSE) -} -\arguments{ -\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?} -} -\description{ -Set a series of options to be used internally by \code{{golem}}. -} diff --git a/man/use_files.Rd b/man/use_files.Rd index b6e3d472..4444d963 100644 --- a/man/use_files.Rd +++ b/man/use_files.Rd @@ -25,5 +25,5 @@ use_external_css_file(url, name, pkg = get_golem_wd(), \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} } \description{ -These functions download files from external sources and install them inside the approriate directory. +These functions download files from external sources and install them inside the appropriate directory. } diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 20a09493..b89b505f 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -1,8 +1,6 @@ ### lib library(withr) - ### Funs - remove_file <- function(path){ if (file.exists(path)) unlink(path, force = TRUE) } @@ -31,26 +29,36 @@ expect_exists <- function(fls) { invisible(act$val) } +# We prevent the random name from having +# ui or server inside it +safe_let <- function(){ + letters[-c(5,9,18,19,21,22)] +} ## fake package fakename <- sprintf( "%s%s", - paste0(sample(letters, 10, TRUE), collapse = ""), + paste0(sample(safe_let(), 10, TRUE), collapse = ""), gsub("[ :-]", "", Sys.time()) - ) +) -tpdir <- tempdir() +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(letters, 10, TRUE), collapse = "") +randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") fp <- file.path("inst/app", randir) dir.create(file.path(pkg, fp), recursive = TRUE) rand_name <- function(){ - paste0(sample(letters, 10, TRUE), collapse = "") + paste0(sample(safe_let(), 10, TRUE), collapse = "") } -orig_test <- set_golem_wd(pkg) +withr::with_dir(pkg, { + set_golem_options() + usethis::proj_set(pkg) + orig_test <- set_golem_wd(pkg) +}) + diff --git a/tests/testthat/test-add_dockerfile.R b/tests/testthat/test-add_dockerfile.R index b525eec9..8ddf4a8b 100644 --- a/tests/testthat/test-add_dockerfile.R +++ b/tests/testthat/test-add_dockerfile.R @@ -3,9 +3,14 @@ context("function add_dockerfile") test_that("add_dockerfile", { with_dir(pkg, { unlink("Dockerfile", force = TRUE) - output <- testthat::capture_output(add_dockerfile(pkg = pkg, sysreqs = FALSE)) + output <- testthat::capture_output( + add_dockerfile(pkg = pkg, sysreqs = FALSE) + ) expect_exists("Dockerfile") - test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") + test <- stringr::str_detect( + output, + "Dockerfile created at Dockerfile" + ) expect_true(test) remove_files("Dockerfile") }) diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R new file mode 100644 index 00000000..36c34018 --- /dev/null +++ b/tests/testthat/test-config.R @@ -0,0 +1,35 @@ +test_that("config works", { + with_dir(pkg, { + expect_equal(get_golem_name(), fakename) + expect_equal(get_golem_version(), "0.0.0.9000") + expect_equal(get_golem_wd(), pkg) + amend_golem_config( + key = "where", + value = "indev" + ) + amend_golem_config( + key = "where", + value = "inprod", + config = "production" + ) + pkgload::load_all(path = pkg) + expect_equal(get_golem_config("where"), "indev") + expect_equal(get_golem_config("where", config = "production"), "inprod") + where_conf <- withr::with_envvar( + c("R_CONFIG_ACTIVE" = "production"), { + get_golem_config("where") + } + ) + 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(get_golem_wd(), normalizePath("inst")) + set_golem_wd(pkg) + }) +}) diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 7e838fa0..2c835c94 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -2,20 +2,18 @@ context("tests use_recomended functions") test_that("test use_recommended_deps",{ with_dir(pkg,{ - use_recommended_deps(pkg = pkg) + use_recommended_deps() packages <- c('shiny', 'DT', 'attempt', 'glue', 'golem', 'htmltools') - desc <- readLines("DESCRIPTION") - start <- grep("Imports:", desc) + 1 - desc <- desc[start:length(desc)] - test <- all(purrr::map_lgl(packages,function(x){any(grepl(x,desc))})) - expect_true(test) + deps <- desc::desc_get_deps(file = "DESCRIPTION") + test <- purrr::map_lgl(packages, ~ .x %in% deps$package) + expect_true(all(test)) }) }) test_that("test use_recommended_tests",{ with_dir(pkg,{ - use_recommended_tests(pkg = pkg) + use_recommended_tests() expect_true(dir.exists("tests")) expect_true(file.exists("tests/testthat/test-golem-recommended.R")) }) diff --git a/vignettes/f_config.Rmd b/vignettes/f_config.Rmd new file mode 100644 index 00000000..6a20817d --- /dev/null +++ b/vignettes/f_config.Rmd @@ -0,0 +1,144 @@ +--- +title: "Using golem config" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{config} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +library(golem) +x <- file.path( + tempdir(), + "golex" +) +unlink(x, TRUE, TRUE) + +create_golem(x, package_name = "golex", open = FALSE) +knitr::opts_knit$set(root.dir = x) +``` + +```{r setup} +library(golem) +``` + +## 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. + +By default, this file contains the name of your app, its version, and the default working directory (which is the root of your package). + +This config file is based on the [`{config}`](https://github.com/rstudio/config) format, which allows you to create configuration files for different application contexts. Please refer to this package documentation for more information. + +## Setting `golem-config` + +Here is what the default config file looks like: + +```{r echo = FALSE, comment= ""} +glue::as_glue( + readLines( + "inst/golem-config.yml" + ) +) +``` + ++ default/golem_name, default/golem_version, default/app_prod are usable across the whole life of your golem app: while developping, and also when in production. ++ production/app_prod might be used for adding elements that are to be used once the app is in production. ++ dev/golem_wd is in a `dev` config because the only moment you might reliably use this config is while developping your app. Use the `app_sys()` function if you want to rely on the package path once the app is deployed. + +The good news is that if you don't want/need to use `{config}`, you can safely ignore this file, __just leave it where it is: it is used internally by the `{golem}` functions__. + +These options are globally set with: + +```{r} +set_golem_options() +``` + +The functions reading the options in this config file are: + +```{r} +get_golem_name() +get_golem_wd() +get_golem_version() +``` + +You can set these with: + +```{r} +set_golem_name("this") +set_golem_wd(".") +set_golem_version("0.0.1") +``` + + +## Using `golem-config` + +If you're already familiar with the `{config}` package, you can use this file just as any config file. + +`{golem}` comes with an `amend_golem_config()` function to add elements to it. + +```{r} +amend_golem_config( + key = "where", + value = "indev" +) +amend_golem_config( + key = "where", + value = "inprod", + config = "production" +) +``` + +Will result in a `golem-config.yml` file as such: + +```{r echo = FALSE, comment= ""} +glue::as_glue( + readLines( + file.path(x, "inst/golem-config.yml") + ) +) +``` + +## `app_config.R` + +In `R/app_config.R`, you'll find a `get_golem_config()` function that allows you to retrieve config from this config file: + +```{r} +pkgload::load_all() +get_golem_config( + "where" +) +get_golem_config( + "where", + config = "production" +) +``` + +Or using the env var (default `{config}` behavior): + +```{r} +Sys.setenv("R_CONFIG_ACTIVE" = "production") +get_golem_config("where") +``` + +## `golem_config` vs `golem_options` + +There is two ways to configure golem apps: + ++ The `golem_opts` in the `run_app()` function ++ The `golem-config.yml` file + +The big difference between these two is that the golem options from `run_app()` are meant to be configured during runtime: you'll be doing `run_app(val = "this")`, whereas the `golem-config` is meant to be used in the backend, and will not be linked to the parameters passed to `run_app()` (even if this is technically possible, this is not the main objective),. + +It's also linked to the `R_CONFIG_ACTIVE` environment variable, just as any `{config}` file. + +The idea is also that the `golem-config.yml` file is sharable across `{golem}` projects (`golem_opts` are application specific), and will be tracked by version control systems. + +## Note for pre `{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 From 449343cf64c29cf931654fbad1bab8235371c074 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Fri, 15 Nov 2019 23:42:31 +0100 Subject: [PATCH 133/211] pass build_golem_from_source to TRUE by default --- R/add_deploy_helpers.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 2ded369c..6914d7d0 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -163,7 +163,7 @@ add_dockerfile <- function( expand = FALSE, open = TRUE, update_tar_gz = TRUE, - build_golem_from_source = FALSE + build_golem_from_source = TRUE ) { @@ -210,7 +210,7 @@ add_dockerfile_shinyproxy <- function( expand = FALSE, open = TRUE, update_tar_gz = TRUE, - build_golem_from_source = FALSE + build_golem_from_source = TRUE ){ where <- file.path(pkg, output) @@ -258,7 +258,7 @@ add_dockerfile_heroku <- function( expand = FALSE, open = TRUE, update_tar_gz = TRUE, - build_golem_from_source = FALSE + build_golem_from_source = TRUE ){ where <- file.path(pkg, output) @@ -354,7 +354,7 @@ dock_from_desc <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - build_golem_from_source = FALSE, + build_golem_from_source = TRUE, update_tar_gz = TRUE ){ From ce7971350d981e81a3e22958d63007843cd244ab Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Fri, 15 Nov 2019 23:48:17 +0100 Subject: [PATCH 134/211] correct update_tar_gz issue --- R/add_deploy_helpers.R | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 6914d7d0..b4cb2973 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -172,7 +172,9 @@ add_dockerfile <- function( usethis::use_build_ignore(basename(where)) dock <- dock_from_desc(path = path, FROM = from, AS = as, - sysreqs = sysreqs, repos = repos,expand = expand,build_golem_from_source = build_golem_from_source) + sysreqs = sysreqs, repos = repos,expand = expand, + build_golem_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz) dock$EXPOSE(port) dock$CMD( glue::glue( @@ -218,7 +220,9 @@ add_dockerfile_shinyproxy <- function( if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(basename(where)) dock <- dock_from_desc(path = path, FROM = from, AS = as, - sysreqs = sysreqs, repos = repos, expand = expand,build_golem_from_source=build_golem_from_source) + sysreqs = sysreqs, repos = repos, expand = expand, + build_golem_from_source=build_golem_from_source, + update_tar_gz = update_tar_gz) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -266,7 +270,9 @@ add_dockerfile_heroku <- function( return(invisible(FALSE)) } usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, expand = expand,build_golem_from_source = build_golem_from_source) + dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, + expand = expand,build_golem_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz) dock$CMD( glue::glue( @@ -335,8 +341,8 @@ alert_build <- function(path, output ,build_golem_from_source){ #' @param sysreqs boolean to check the system requirements #' @param repos character vector, the base URL of the repositories #' @param expand boolean, if `TRUE` each system requirement will be known his own RUN line -#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it #' @param update_tar_gz boolean, if `TRUE` and build_golem_from_source is also `TRUE` an updated tar.gz Package is created +#' @param build_golem_from_source boolean, if `TRUE` no tar.gz Package is created and the Dockerfile directly mount the source folder to build it #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps @@ -354,8 +360,8 @@ dock_from_desc <- function( sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, - build_golem_from_source = TRUE, - update_tar_gz = TRUE + update_tar_gz = TRUE, + build_golem_from_source = TRUE ){ @@ -460,14 +466,18 @@ dock_from_desc <- function( dock - if ( !build_golem_from_source & update_tar_gz){ + if ( !build_golem_from_source){ + + if ( update_tar_gz ){ + ancienne_version <- list.files(pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"),full.names = TRUE) + cat_red_bullet(glue::glue("We remove {paste(ancienne_version,collapse = ", ")} from folder")) + lapply(ancienne_version,file.remove) + lapply(ancienne_version,unlink,force=TRUE) + cat_green_tick(glue::glue(" {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz created.")) + devtools::build(path = ".") + } # we use a already builded tar.gz file - ancienne_version <- list.files(pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"),full.names = TRUE) - cat_red_bullet(glue::glue("We remove {paste(ancienne_version,collapse = ", ")} from folder")) - lapply(ancienne_version,file.remove) - lapply(ancienne_version,unlink,force=TRUE) - cat_green_tick(glue::glue(" {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz created.")) - devtools::build(path = ".") + dock$COPY( from = paste0(read.dcf(path)[1], "_*.tar.gz"), From c18f184fdd472b88aeb35ffc2dce0c3f147b367a Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 16 Nov 2019 00:04:18 +0100 Subject: [PATCH 135/211] update Rd --- R/add_deploy_helpers.R | 27 ++++++++------------------- man/dock_from_desc.Rd | 8 ++++---- man/dockerfiles.Rd | 6 +++--- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index b2372053..25c865e8 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -127,6 +127,7 @@ add_shinyserver_file <- function( #' @param update_tar_gz boolean, if `TRUE` and build_golem_from_source is also `TRUE` an updated tar.gz Package is created #' @export #' @rdname dockerfiles +#' @importFrom usethis use_build_ignore #' @importFrom desc desc_get_deps #' @importFrom dockerfiler Dockerfile #' @importFrom rstudioapi navigateToFile isAvailable @@ -318,6 +319,7 @@ add_dockerfile_heroku <- function( } +#' @importFrom glue glue alert_build <- function(path, output ,build_golem_from_source){ cat_green_tick( glue("Dockerfile created at {output}") @@ -436,17 +438,11 @@ dock_from_desc <- function( if ( length(packages_not_on_cran>0)){ - # prepare the install_github - # nn<- lapply( remotes_deps$remote[!remotes_deps$is_cran], function(.){ .[c('repo','username','sha')] }) %>% do.call(rbind,.) %>% as.data.frame() - # nn <- remotes_deps$remote[!remotes_deps$is_cran]%>% - # map_df(~.x[c('repo','username','sha')]) %>% - # mutate(remote = glue::glue("{username}/{repo}@{sha}")) %>% - # pull(remote) nn<- glue::glue("{nn$username}/{nn$repo}@{nn$sha}") pong <- mapply(function(dock, ver, nm){ @@ -457,26 +453,24 @@ dock_from_desc <- function( ) ) }, ver = nn, MoreArgs = list(dock = dock)) - - } - - - dock - - if ( !build_golem_from_source){ if ( update_tar_gz ){ ancienne_version <- list.files(pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"),full.names = TRUE) + + if (length(ancienne_version) > 0){ cat_red_bullet(glue::glue("We remove {paste(ancienne_version,collapse = ", ")} from folder")) lapply(ancienne_version,file.remove) lapply(ancienne_version,unlink,force=TRUE) + } + + cat_green_tick(glue::glue(" {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz created.")) devtools::build(path = ".") } - # we use a already builded tar.gz file + # we use an already builded tar.gz file dock$COPY( @@ -485,15 +479,10 @@ dock_from_desc <- function( ) dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") } else { - # dock$RUN("mkdir /build_zone") dock$ADD(from = ".",to = "/build_zone") dock$WORKDIR("/build_zone") - #dock$RUN("R -e 'setwd(\"/build_zone\");devtools::build(path = \".\")'") dock$RUN("R -e 'remotes::install_local()'") - - - } dock diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd index 8202c3f3..21b0f8f3 100644 --- a/man/dock_from_desc.Rd +++ b/man/dock_from_desc.Rd @@ -7,8 +7,8 @@ dock_from_desc(path = "DESCRIPTION", FROM = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), AS = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, build_golem_from_source = FALSE, - update_tar_gz = TRUE) + expand = FALSE, update_tar_gz = TRUE, + build_golem_from_source = TRUE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -24,9 +24,9 @@ with \code{R.Version()$major} and \code{R.Version()$minor}.} \item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} -\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} - \item{update_tar_gz}{boolean, if \code{TRUE} and build_golem_from_source is also \code{TRUE} an updated tar.gz Package is created} + +\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} } \description{ Create Dockerfile from DESCRIPTION diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index b3594163..51e51786 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -11,21 +11,21 @@ add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE, - update_tar_gz = TRUE, build_golem_from_source = FALSE) + update_tar_gz = TRUE, build_golem_from_source = TRUE) add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE, update_tar_gz = TRUE, - build_golem_from_source = FALSE) + build_golem_from_source = TRUE) add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE, update_tar_gz = TRUE, - build_golem_from_source = FALSE) + build_golem_from_source = TRUE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} From 01d76d5c99aa701aefb4da70610a8e6f693a8453 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 16 Nov 2019 09:13:24 +0100 Subject: [PATCH 136/211] update shinyexample documentation , fix #288 --- inst/shinyexample/R/app_server.R | 5 ++++- inst/shinyexample/R/app_ui.R | 3 ++- inst/shinyexample/man/app_server.Rd | 18 ++++++++++++++++++ inst/shinyexample/man/app_ui.Rd | 6 +++--- 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 inst/shinyexample/man/app_server.Rd diff --git a/inst/shinyexample/R/app_server.R b/inst/shinyexample/R/app_server.R index 11bb8c98..63776716 100644 --- a/inst/shinyexample/R/app_server.R +++ b/inst/shinyexample/R/app_server.R @@ -1,5 +1,8 @@ #' App Server -#' +#' +#' @param input input +#' @param output ouput +#' @param session session #' @import shiny app_server <- function(input, output,session) { # List the first level callModules here diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index c7c763a9..aada22d1 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,5 +1,6 @@ #' Application UI -#' +#' +#' @param request needed to use `enableBookmarking()` #' @import shiny app_ui <- function(request) { tagList( diff --git a/inst/shinyexample/man/app_server.Rd b/inst/shinyexample/man/app_server.Rd new file mode 100644 index 00000000..5c238ec9 --- /dev/null +++ b/inst/shinyexample/man/app_server.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_server.R +\name{app_server} +\alias{app_server} +\title{App Server} +\usage{ +app_server(input, output, session) +} +\arguments{ +\item{input}{input} + +\item{output}{ouput} + +\item{session}{session} +} +\description{ +App Server +} diff --git a/inst/shinyexample/man/app_ui.Rd b/inst/shinyexample/man/app_ui.Rd index 6e5abce8..549ffee1 100644 --- a/inst/shinyexample/man/app_ui.Rd +++ b/inst/shinyexample/man/app_ui.Rd @@ -2,13 +2,13 @@ % Please edit documentation in R/app_ui.R \name{app_ui} \alias{app_ui} -\title{ui} +\title{Application UI} \usage{ app_ui(request) } \arguments{ -\item{request}{needed for bookmarking} +\item{request}{needed to use `enableBookmarking()`} } \description{ -ui +Application UI } From b19fa45a71845b55f908c4c9d1c5c5741b5591e7 Mon Sep 17 00:00:00 2001 From: colin Date: Sat, 16 Nov 2019 09:37:09 +0100 Subject: [PATCH 137/211] added here to imports --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index bfddb9c1..f7a4c31e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -46,6 +46,7 @@ Imports: dockerfiler, fs, glue, + here, htmltools, jsonlite, magrittr, From 4f118a8eae38d546f4fb3ca08cb0e3608d1d40c2 Mon Sep 17 00:00:00 2001 From: novica Date: Sat, 16 Nov 2019 21:06:43 +0100 Subject: [PATCH 138/211] rename test-add_dockerfile.R to test-add_deploy_helpers.R because it seems dockerfiles is added via deploy helpers --- .../testthat/{test-add_dockerfile.R => test-add_deploy_helpers.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-add_dockerfile.R => test-add_deploy_helpers.R} (100%) diff --git a/tests/testthat/test-add_dockerfile.R b/tests/testthat/test-add_deploy_helpers.R similarity index 100% rename from tests/testthat/test-add_dockerfile.R rename to tests/testthat/test-add_deploy_helpers.R From aef0e133fe23c11bdc89f167ed417760c0807ecd Mon Sep 17 00:00:00 2001 From: novica Date: Sat, 16 Nov 2019 21:30:31 +0100 Subject: [PATCH 139/211] move contents of test-add_rstudioconnect_file --- tests/testthat/test-add_deploy_helpers.R | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 8ddf4a8b..bf849514 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -37,3 +37,35 @@ test_that("add_dockerfile_shinyproxy", { remove_files("Dockerfile") }) }) + +context("function add_rstudioconnect_file") + +test_that("add_rstudioconnect_file", { + with_dir(pkg, { + remove_file("app.R") + output <- testthat::capture_output(add_rstudioconnect_file(pkg = pkg, open = FALSE)) + expect_true(file.exists("app.R")) + test <- + stringr::str_detect(output, "ile created at .*/app.R") + expect_true(test) + remove_file("app.R") + }) + with_dir(pkg, { + remove_file("app.R") + output <- testthat::capture_output(add_shinyappsio_file(pkg = pkg,open = FALSE)) + expect_true(file.exists("app.R")) + test <- + stringr::str_detect(output, "ile created at .*/app.R") + expect_true(test) + remove_file("app.R") + }) + with_dir(pkg, { + remove_file("app.R") + output <- testthat::capture_output(add_shinyserver_file(pkg = pkg,open = FALSE)) + expect_true(file.exists("app.R")) + test <- + stringr::str_detect(output, "ile created at .*/app.R") + expect_true(test) + remove_file("app.R") + }) +}) \ No newline at end of file From ea59d677036762ab8545ea1cab5829b06fbe7ad6 Mon Sep 17 00:00:00 2001 From: novica Date: Sat, 16 Nov 2019 21:31:16 +0100 Subject: [PATCH 140/211] remove obsolete file --- tests/testthat/test-add_rstudioconnect_file.R | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 tests/testthat/test-add_rstudioconnect_file.R diff --git a/tests/testthat/test-add_rstudioconnect_file.R b/tests/testthat/test-add_rstudioconnect_file.R deleted file mode 100644 index 3c69dfaa..00000000 --- a/tests/testthat/test-add_rstudioconnect_file.R +++ /dev/null @@ -1,31 +0,0 @@ -context("function rstudio products") - -test_that("add_rstudioconnect_file", { - with_dir(pkg, { - remove_file("app.R") - output <- testthat::capture_output(add_rstudioconnect_file(pkg = pkg, open = FALSE)) - expect_true(file.exists("app.R")) - test <- - stringr::str_detect(output, "ile created at .*/app.R") - expect_true(test) - remove_file("app.R") - }) - with_dir(pkg, { - remove_file("app.R") - output <- testthat::capture_output(add_shinyappsio_file(pkg = pkg,open = FALSE)) - expect_true(file.exists("app.R")) - test <- - stringr::str_detect(output, "ile created at .*/app.R") - expect_true(test) - remove_file("app.R") - }) - with_dir(pkg, { - remove_file("app.R") - output <- testthat::capture_output(add_shinyserver_file(pkg = pkg,open = FALSE)) - expect_true(file.exists("app.R")) - test <- - stringr::str_detect(output, "ile created at .*/app.R") - expect_true(test) - remove_file("app.R") - }) -}) From 5816cf382e8f929fd484e009a0cee247c9be216e Mon Sep 17 00:00:00 2001 From: novica Date: Sat, 16 Nov 2019 22:07:37 +0100 Subject: [PATCH 141/211] move test-add_function.R to test-add_files.R as the functions are now in the add_files.R file --- tests/testthat/{test-add_function.R => test-add_files.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-add_function.R => test-add_files.R} (100%) diff --git a/tests/testthat/test-add_function.R b/tests/testthat/test-add_files.R similarity index 100% rename from tests/testthat/test-add_function.R rename to tests/testthat/test-add_files.R From 0bc299c7f3a95ecf1ad7e70c7c7e9d559c7f335d Mon Sep 17 00:00:00 2001 From: novica Date: Mon, 18 Nov 2019 22:16:54 +0100 Subject: [PATCH 142/211] move test for r files to new file --- tests/testthat/test-add_files.R | 26 -------------------------- tests/testthat/test-add_r_files.R | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 tests/testthat/test-add_r_files.R diff --git a/tests/testthat/test-add_files.R b/tests/testthat/test-add_files.R index f5e682cf..80de64a9 100644 --- a/tests/testthat/test-add_files.R +++ b/tests/testthat/test-add_files.R @@ -91,29 +91,3 @@ test_that("add_ui_server_files", { remove_file("inst/app/server.R") }) }) - -test_that("add_fct and add_utils", { - with_dir(pkg, { - util_file <- "R/utils_ui.R" - fct_file <- "R/fct_ui.R" - mod <- "R/mod_plop.R" - remove_file(util_file) - remove_file(fct_file) - remove_file(mod) - add_fct("ui", pkg = pkg, open = FALSE) - add_utils("ui", pkg = pkg, open = FALSE) - - expect_true(file.exists(util_file)) - expect_true(file.exists(fct_file)) - rand <- rand_name() - add_module(rand, pkg = pkg, open = FALSE) - add_fct("ui", rand, pkg = pkg, open = FALSE) - add_utils("ui", rand, pkg = pkg, open = FALSE) - expect_true(file.exists(sprintf("R/mod_%s_fct_ui.R", rand))) - expect_true(file.exists(sprintf("R/mod_%s_utils_ui.R", rand))) - - remove_file(sprintf("R/mod_%s.R", rand)) - remove_file(sprintf("R/mod_%s_fct_ui.R", rand)) - remove_file(sprintf("R/mod_%s_utils_ui.R", rand)) - }) -}) diff --git a/tests/testthat/test-add_r_files.R b/tests/testthat/test-add_r_files.R new file mode 100644 index 00000000..c2740af4 --- /dev/null +++ b/tests/testthat/test-add_r_files.R @@ -0,0 +1,27 @@ +context("test-add_r_files function") + +test_that("add_fct and add_utils", { + with_dir(pkg, { + util_file <- "R/utils_ui.R" + fct_file <- "R/fct_ui.R" + mod <- "R/mod_plop.R" + remove_file(util_file) + remove_file(fct_file) + remove_file(mod) + add_fct("ui", pkg = pkg, open = FALSE) + add_utils("ui", pkg = pkg, open = FALSE) + + expect_true(file.exists(util_file)) + expect_true(file.exists(fct_file)) + rand <- rand_name() + add_module(rand, pkg = pkg, open = FALSE) + add_fct("ui", rand, pkg = pkg, open = FALSE) + add_utils("ui", rand, pkg = pkg, open = FALSE) + expect_true(file.exists(sprintf("R/mod_%s_fct_ui.R", rand))) + expect_true(file.exists(sprintf("R/mod_%s_utils_ui.R", rand))) + + remove_file(sprintf("R/mod_%s.R", rand)) + remove_file(sprintf("R/mod_%s_fct_ui.R", rand)) + remove_file(sprintf("R/mod_%s_utils_ui.R", rand)) + }) +}) From 3d323a3d2565b1affa48f2887a82fd88dffed5d9 Mon Sep 17 00:00:00 2001 From: novica Date: Mon, 18 Nov 2019 22:19:14 +0100 Subject: [PATCH 143/211] renamed: test-add_module.R -> test-add_modules.R for consistency --- tests/testthat/{test-add_module.R => test-add_modules.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-add_module.R => test-add_modules.R} (100%) diff --git a/tests/testthat/test-add_module.R b/tests/testthat/test-add_modules.R similarity index 100% rename from tests/testthat/test-add_module.R rename to tests/testthat/test-add_modules.R From a095374295bec4ee5fd1de11b03e4e997eaf72a2 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 21 Nov 2019 00:54:29 +0100 Subject: [PATCH 144/211] fix upgrade='never' in remotes::install_local() --- R/add_deploy_helpers.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 25c865e8..24849955 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -477,12 +477,12 @@ dock_from_desc <- function( from = paste0(read.dcf(path)[1], "_*.tar.gz"), to = "/app.tar.gz" ) - dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\")'") + dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\",upgrade=\"never\")'") } else { dock$RUN("mkdir /build_zone") dock$ADD(from = ".",to = "/build_zone") dock$WORKDIR("/build_zone") - dock$RUN("R -e 'remotes::install_local()'") + dock$RUN("R -e 'remotes::install_local(upgrade=\"never\")'") } dock From 3068adcb8f9df587496a5dcea09887944e40583a Mon Sep 17 00:00:00 2001 From: novica Date: Thu, 21 Nov 2019 01:11:38 +0100 Subject: [PATCH 145/211] I created a file to test the add_resource_path file. I wasn't sure how to test the path being added so I edited the add_resource_path.R to include a message when a path is added, in the same manner when it cannot be added. --- R/add_ressource_path.R | 3 ++- tests/testthat/test-add_resource_path.R | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/testthat/test-add_resource_path.R diff --git a/R/add_ressource_path.R b/R/add_ressource_path.R index 835b0e2b..864b5535 100644 --- a/R/add_ressource_path.R +++ b/R/add_ressource_path.R @@ -15,9 +15,10 @@ add_resource_path <- function(prefix, list_f <- is_empty(list.files(path = directory_path)) - if( list_f & warn_empty ){ + if ( list_f & warn_empty ) { message("Unable to add your directory because it is empty") } else { addResourcePath(prefix, directory_path) +message("Resource path added to ", prefix, "/", directory_path) } } diff --git a/tests/testthat/test-add_resource_path.R b/tests/testthat/test-add_resource_path.R new file mode 100644 index 00000000..400dc777 --- /dev/null +++ b/tests/testthat/test-add_resource_path.R @@ -0,0 +1,11 @@ +context("test-add_resource_path function") + +test_that("add_resource_path", { + + expect_message(add_resource_path("", "", warn_empty = TRUE), + "Unable to add your directory because it is empty") + + expect_message(add_resource_path("golem", "R", warn_empty = FALSE), + paste("Resource path added to ", prefix, "/", directory_path)) + +}) \ No newline at end of file From 65c6cd4cb53306f2885e031e1afd406a3a5291e8 Mon Sep 17 00:00:00 2001 From: novica Date: Thu, 21 Nov 2019 18:06:07 +0100 Subject: [PATCH 146/211] rename 2 files for consistency with R files --- tests/testthat/{test-dev_function.R => test-make_dev.R} | 0 tests/testthat/{test-expect_function.R => test-test_helpers.R} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-dev_function.R => test-make_dev.R} (100%) rename tests/testthat/{test-expect_function.R => test-test_helpers.R} (100%) diff --git a/tests/testthat/test-dev_function.R b/tests/testthat/test-make_dev.R similarity index 100% rename from tests/testthat/test-dev_function.R rename to tests/testthat/test-make_dev.R diff --git a/tests/testthat/test-expect_function.R b/tests/testthat/test-test_helpers.R similarity index 100% rename from tests/testthat/test-expect_function.R rename to tests/testthat/test-test_helpers.R From 2c8accefd4d13d258403774635e8277ab17089ab Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 25 Nov 2019 20:44:29 +0100 Subject: [PATCH 147/211] test refactoring --- R/create_golem.R | 8 +- tests/testthat/helper-config.R | 9 +- tests/testthat/test-add_deploy_helpers.R | 108 +++++++++----------- tests/testthat/test-add_files.R | 120 ++++++++++------------- tests/testthat/test-add_modules.R | 4 +- tests/testthat/test-add_resource_path.R | 13 ++- tests/testthat/test-config.R | 8 +- tests/testthat/test-desc.R | 26 +++-- tests/testthat/test-favicon.R | 19 +++- tests/testthat/test-js.R | 3 - tests/testthat/test-make_dev.R | 2 - tests/testthat/test-test_helpers.R | 3 - tests/testthat/test-use_recomended.R | 9 +- tests/testthat/test-use_utils.R | 4 +- tests/testthat/test-zreload.R | 16 +-- tests/testthat/test-zzzzzzzzzz.R | 2 + 16 files changed, 171 insertions(+), 183 deletions(-) diff --git a/R/create_golem.R b/R/create_golem.R index 97c79d07..59f56013 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -25,7 +25,7 @@ create_golem <- function( without_comments = FALSE, ... ) { - + if (path == '.' & package_name == basename(path)){ package_name <- basename(getwd()) } @@ -92,9 +92,9 @@ create_golem <- function( conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - path <- "here::here()" - attr(path, "tag") <- "!expr" - conf$dev$golem_wd <- path + 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" yaml::write_yaml(conf, yml_path) diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index b89b505f..75f3140a 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -29,6 +29,12 @@ expect_exists <- function(fls) { invisible(act$val) } +burn_after_reading <- function(file, exp){ + unlink(file, force = TRUE) + force(exp) + unlink(file, force = TRUE) +} + # We prevent the random name from having # ui or server inside it safe_let <- function(){ @@ -60,5 +66,4 @@ withr::with_dir(pkg, { set_golem_options() usethis::proj_set(pkg) orig_test <- set_golem_wd(pkg) -}) - +}) \ No newline at end of file diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index bf849514..7a50f078 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -1,71 +1,51 @@ -context("function add_dockerfile") - -test_that("add_dockerfile", { - with_dir(pkg, { - unlink("Dockerfile", force = TRUE) - output <- testthat::capture_output( - add_dockerfile(pkg = pkg, sysreqs = FALSE) - ) - expect_exists("Dockerfile") - test <- stringr::str_detect( - output, - "Dockerfile created at Dockerfile" - ) - expect_true(test) - remove_files("Dockerfile") - }) -}) -# -# -test_that("add_dockerfile_heroku", { +test_that("add_dockerfiles", { with_dir(pkg, { - unlink("Dockerfile", force = TRUE) - output <- testthat::capture_output(add_dockerfile_heroku(pkg = pkg, sysreqs = FALSE)) - expect_exists("Dockerfile") - test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") - expect_true(test) - remove_files("Dockerfile") + + 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) + ) + expect_exists("Dockerfile") + test <- stringr::str_detect( + output, + "Dockerfile created at Dockerfile" + ) + expect_true(test) + } + ) + } + }) }) -test_that("add_dockerfile_shinyproxy", { - with_dir(pkg, { - unlink("Dockerfile", force = TRUE) - output <- testthat::capture_output(add_dockerfile_shinyproxy(pkg = pkg, sysreqs = FALSE)) - expect_exists("Dockerfile") - test <- stringr::str_detect(output, "Dockerfile created at Dockerfile") - remove_files("Dockerfile") - }) -}) - -context("function add_rstudioconnect_file") - -test_that("add_rstudioconnect_file", { - with_dir(pkg, { - remove_file("app.R") - output <- testthat::capture_output(add_rstudioconnect_file(pkg = pkg, open = FALSE)) - expect_true(file.exists("app.R")) - test <- - stringr::str_detect(output, "ile created at .*/app.R") - expect_true(test) - remove_file("app.R") - }) - with_dir(pkg, { - remove_file("app.R") - output <- testthat::capture_output(add_shinyappsio_file(pkg = pkg,open = FALSE)) - expect_true(file.exists("app.R")) - test <- - stringr::str_detect(output, "ile created at .*/app.R") - expect_true(test) - remove_file("app.R") - }) +test_that("add_rstudio_files", { with_dir(pkg, { - remove_file("app.R") - output <- testthat::capture_output(add_shinyserver_file(pkg = pkg,open = FALSE)) - expect_true(file.exists("app.R")) - test <- - stringr::str_detect(output, "ile created at .*/app.R") - expect_true(test) - remove_file("app.R") + + for (fun in list( + add_rstudioconnect_file, + add_shinyappsio_file, + add_shinyserver_file + )){ + burn_after_reading( + "app.R", { + output <- testthat::capture_output( + fun(pkg = pkg, open = FALSE) + ) + expect_exists("app.R") + test <- stringr::str_detect( + output, + "ile created at .*/app.R" + ) + expect_true(test) + } + ) + } + }) }) \ No newline at end of file diff --git a/tests/testthat/test-add_files.R b/tests/testthat/test-add_files.R index 80de64a9..b0fdd3c2 100644 --- a/tests/testthat/test-add_files.R +++ b/tests/testthat/test-add_files.R @@ -1,79 +1,65 @@ context("test-add_file function") -test_that("add_css_file", { +expect_add_file <- function( + fun, + ext, + pkg, + fp +){ + name <- rand_name() + # Be sure to remove all files in case there are + remove_files("inst/app/www", ext) + # Launch the function + fun(name, pkg = pkg, open = FALSE) + # Test that the file exists + expect_exists(file.path("inst/app/www", paste0(name,".", ext))) + # Check that the file exsts + ff <- list.files("inst/app/www/", pattern = name) + expect_equal(tools::file_ext(ff), ext) - with_dir(pkg, { - remove_files("inst/app/www", "\\.css$") - add_css_file("style", pkg = pkg, open = FALSE) - expect_exists("inst/app/www/style.css") - - add_css_file("stylebis", pkg = pkg, open = FALSE, dir = normalizePath(fp)) - expect_true( - file.exists( - file.path(fp, "stylebis.css") - ) - ) - style <-list.files("inst/app/www/", pattern = "style") - expect_equal(tools::file_ext(style), "css") - - # Test that extension is removed - remove_file("inst/app/www/style.css") - add_css_file("style.css", pkg = pkg, open = FALSE) - expect_true(file.exists("inst/app/www/style.css")) - - remove_file("inst/app/www/style.css") - remove_file("inst/app/www/stylebis.css") - }) -}) + # Try another file in another dir + bis <- paste0(name, rand_name()) + fun(bis, pkg = pkg, open = FALSE, dir = normalizePath(fp)) + expect_exists( normalizePath(fp) ) + ff <- list.files( normalizePath(fp), pattern = bis) + expect_equal(tools::file_ext(ff), ext) + + # Check that the extension is removed + name <- rand_name() + ter <- paste0(name, ".", ext) + fun(ter, pkg = pkg, open = FALSE,) + expect_exists(file.path("inst/app/www")) + ff <- list.files("inst/app/www/", pattern = ter) + expect_equal(tools::file_ext(ff), ext) + + remove_files("inst/app/www", ext) +} -test_that("add_js_file", { +test_that("add_files", { + with_dir(pkg, { - remove_file("inst/app/www/script.js") - add_js_file("script", pkg = pkg, open = FALSE) - expect_true(file.exists("inst/app/www/script.js")) - - add_js_file("script", pkg = pkg, open = FALSE, dir = normalizePath(fp)) - expect_true( - file.exists( - file.path(fp, "script.js") - ) + expect_add_file( + add_css_file, + ext = "css", + pkg = pkg, + fp = fp ) - script <- - list.files("inst/app/www/", pattern = "script") - expect_equal(tools::file_ext(script), - "js") - remove_file("inst/app/www/script.js") - - # Test that extension is removed - add_js_file("script.js", pkg = pkg, open = FALSE) - expect_true(file.exists("inst/app/www/script.js")) - remove_file("inst/app/www/script.js") - }) -}) - -test_that("add_js_handler", { - with_dir(pkg, { - remove_file("inst/app/www/handler.js") - add_js_handler("handler", pkg = pkg, open = FALSE) - expect_true(file.exists("inst/app/www/handler.js")) - - add_js_handler("handler",pkg = pkg, open = FALSE, dir = normalizePath(fp)) - expect_true( - file.exists( - file.path(fp, "handler.js") - ) + expect_add_file( + add_js_file, + ext = "js", + pkg = pkg, + fp = fp + ) + expect_add_file( + add_js_handler, + ext = "js", + pkg = pkg, + fp = fp ) - script <- - list.files("inst/app/www/", pattern = "handler") - expect_equal(tools::file_ext(script), - "js") - remove_file("inst/app/www/handler.js") - # Test that extension is removed - add_js_handler("handler.js", pkg = pkg, open = FALSE) - expect_true(file.exists("inst/app/www/handler.js")) - remove_file("inst/app/www/handler.js") }) + + }) test_that("add_ui_server_files", { diff --git a/tests/testthat/test-add_modules.R b/tests/testthat/test-add_modules.R index 0ba170d1..55486400 100644 --- a/tests/testthat/test-add_modules.R +++ b/tests/testthat/test-add_modules.R @@ -11,9 +11,7 @@ test_that("add_module", { script <- list.files("R", pattern = "mod_test") lapply(tools::file_ext(script), function(x) testthat::expect_equal(x, "R")) ## Test message of function - if (file.exists("R/mod_output.R")) { - file.remove("R/mod_output.R") - } + remove_file("R/mod_output.R") 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-add_resource_path.R b/tests/testthat/test-add_resource_path.R index 400dc777..b69ecd60 100644 --- a/tests/testthat/test-add_resource_path.R +++ b/tests/testthat/test-add_resource_path.R @@ -1,11 +1,10 @@ -context("test-add_resource_path function") - test_that("add_resource_path", { + withr::with_dir(pkg, { + expect_message( + add_resource_path("", "", warn_empty = TRUE), + "Unable to add your directory because it is empty" + ) + }) - expect_message(add_resource_path("", "", warn_empty = TRUE), - "Unable to add your directory because it is empty") - - expect_message(add_resource_path("golem", "R", warn_empty = FALSE), - paste("Resource path added to ", prefix, "/", directory_path)) }) \ No newline at end of file diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index 36c34018..d0e91a9b 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -12,12 +12,12 @@ test_that("config works", { value = "inprod", config = "production" ) - pkgload::load_all(path = pkg) - expect_equal(get_golem_config("where"), "indev") - expect_equal(get_golem_config("where", config = "production"), "inprod") + + 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"), { - get_golem_config("where") + config::get("where", file = "inst/golem-config.yml") } ) expect_equal(where_conf, "inprod") diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index a80904fb..5c143aa2 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -2,7 +2,18 @@ context("test-desc") test_that("desc works", { with_dir(pkg,{ - output <- capture_output(fill_desc( + output <- capture_output( + fill_desc( + fakename, + "newtitle", + "Newdescription.", + "firstname", + "lastname", + "name@test.com", + "http://repo_url.com" + ) + ) + add_desc <- c( fakename, "newtitle", "Newdescription.", @@ -10,16 +21,17 @@ test_that("desc works", { "lastname", "name@test.com", "http://repo_url.com" - )) - add_desc <- c(fakename, "newtitle", "Newdescription.", - "firstname", "lastname", "name@test.com", - "http://repo_url.com") + ) desc <- readLines("DESCRIPTION") expect_true( - all(purrr::map_lgl(add_desc,function(x){any(grepl(x,desc))})) + all( + purrr::map_lgl(add_desc,function(x){ + any(grepl(x,desc))} + ) + ) ) - + expect_true( stringr::str_detect(output, "DESCRIPTION file modified") ) diff --git a/tests/testthat/test-favicon.R b/tests/testthat/test-favicon.R index 6103757c..78909189 100644 --- a/tests/testthat/test-favicon.R +++ b/tests/testthat/test-favicon.R @@ -4,14 +4,27 @@ test_that("test use_favicon",{ with_dir(pkg,{ use_favicon() expect_true(file.exists("inst/app/www/favicon.ico")) - purrr::map(c("test.jpeg","test.bmp","test.gif","test.tiff"),~ expect_error(use_favicon(path = .x))) + purrr::map( + c( + "test.jpeg", + "test.bmp", + "test.gif", + "test.tiff" + ), + ~ expect_error( + use_favicon(path = .x) + ) + ) golem::remove_favicon() expect_false(file.exists("inst/app/www/favicon.ico")) - }) }) +}) test_that("test favicon",{ with_dir(pkg,{ - expect_is(favicon("jean","jean"),"shiny.tag") + expect_is( + favicon("jean","jean"), + "shiny.tag" + ) }) }) \ No newline at end of file diff --git a/tests/testthat/test-js.R b/tests/testthat/test-js.R index 3bad2a95..0a6c2d6e 100644 --- a/tests/testthat/test-js.R +++ b/tests/testthat/test-js.R @@ -1,6 +1,3 @@ -context("test js function") - - test_that("active_js", { expect_is(activate_js(),'shiny.tag') }) diff --git a/tests/testthat/test-make_dev.R b/tests/testthat/test-make_dev.R index 6fcae962..cd899069 100644 --- a/tests/testthat/test-make_dev.R +++ b/tests/testthat/test-make_dev.R @@ -1,5 +1,3 @@ -context("tests dev function") - test_that("test app_dev",{ with_options( c( diff --git a/tests/testthat/test-test_helpers.R b/tests/testthat/test-test_helpers.R index 4039bb14..e00aa68b 100644 --- a/tests/testthat/test-test_helpers.R +++ b/tests/testthat/test-test_helpers.R @@ -1,5 +1,3 @@ -context("tests expect fonctions") - test_that("test expect_shinytag",{ with_dir(pkg,{ expect_equal(capture_output(expect_shinytag(favicon("jean"))),"") @@ -16,7 +14,6 @@ test_that("test expect_shinytaglist",{ test_that("test expect_shinytaglist",{ with_dir(pkg,{ - ui <- shiny::h1("Jean") htmltools::save_html(ui,file = "jean.html") expect_equal(capture_output(expect_html_equal(ui = ui,html = "jean.html")),"") diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 2c835c94..7e8055b4 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -1,12 +1,11 @@ -context("tests use_recomended functions") - test_that("test use_recommended_deps",{ with_dir(pkg,{ use_recommended_deps() packages <- c('shiny', 'DT', 'attempt', 'glue', 'golem', 'htmltools') deps <- desc::desc_get_deps(file = "DESCRIPTION") - test <- purrr::map_lgl(packages, ~ .x %in% deps$package) - expect_true(all(test)) + expect_true( + all(purrr::map_lgl(packages, ~ .x %in% deps$package)) + ) }) }) @@ -15,6 +14,6 @@ test_that("test use_recommended_tests",{ with_dir(pkg,{ use_recommended_tests() expect_true(dir.exists("tests")) - expect_true(file.exists("tests/testthat/test-golem-recommended.R")) + expect_exists("tests/testthat/test-golem-recommended.R") }) }) diff --git a/tests/testthat/test-use_utils.R b/tests/testthat/test-use_utils.R index 79e86858..335eac91 100644 --- a/tests/testthat/test-use_utils.R +++ b/tests/testthat/test-use_utils.R @@ -4,7 +4,7 @@ test_that("test use_utils_ui",{ with_dir(pkg,{ remove_file("R/golem_utils_ui.R") use_utils_ui() - expect_true(file.exists("R/golem_utils_ui.R")) + expect_exists("R/golem_utils_ui.R") }) }) @@ -12,7 +12,7 @@ test_that("test use_utils_server",{ with_dir(pkg,{ remove_file("R/golem_utils_server.R") use_utils_server() - expect_true(file.exists("R/golem_utils_server.R")) + expect_exists("R/golem_utils_server.R") }) }) diff --git a/tests/testthat/test-zreload.R b/tests/testthat/test-zreload.R index bb3ad9e7..b98f03da 100644 --- a/tests/testthat/test-zreload.R +++ b/tests/testthat/test-zreload.R @@ -1,8 +1,7 @@ -context("tests reload") - ## fake package fakename <- paste0(sample(letters, 10, TRUE), collapse = "") tpdir <- tempdir() + if(!dir.exists(file.path(tpdir,fakename))){ create_golem(file.path(tpdir, fakename), open = FALSE) } @@ -12,8 +11,10 @@ test_that("test document_and_reload",{ with_dir(pkg_reload,{ file.create("R/sum.R") cat( - "#' @export - sum_golem <- function(a,b){a + b}",file ="R/sum.R") + "#' @export + sum_golem <- function(a,b){a + b}", + file ="R/sum.R" + ) document_and_reload(pkg = ".") expect_equal(sum_golem(1,2),3) }) @@ -21,8 +22,9 @@ test_that("test document_and_reload",{ test_that("test detach_all_attached",{ with_dir(pkg_reload,{ - test <- detach_all_attached() - ok <- all(purrr::map_lgl(test,~ is.null(.x))) - testthat::expect_true(ok) + test <- detach_all_attached() + expect_true( + all(purrr::map_lgl(test, is.null)) + ) }) }) diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 579775d3..79668a85 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -1,2 +1,4 @@ # For setting back old usethis settings usethis::proj_set(orig_test) + +unlink(pkg, TRUE, TRUE) \ No newline at end of file From 314d4885fd0c49b925de37cf28e580cf1e73f235 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 25 Nov 2019 21:16:17 +0100 Subject: [PATCH 148/211] skip on travis --- tests/testthat/test-zzzzzzzzzz.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 79668a85..031e456b 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -1,4 +1,6 @@ # For setting back old usethis settings -usethis::proj_set(orig_test) +if (!identical(Sys.getenv("TRAVIS"), "true")) { + usethis::proj_set(orig_test) +} unlink(pkg, TRUE, TRUE) \ No newline at end of file From e739d6ca72462756c30ba2759a6262b219bf474b Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 25 Nov 2019 21:25:03 +0100 Subject: [PATCH 149/211] add_module now default to not exporting the module function, close #144 internal functions in the golem are now documented and combined with @noRd, close #292 --- R/add_modules.R | 44 ++++++++++++++----------- inst/shinyexample/NAMESPACE | 3 ++ inst/shinyexample/R/app_config.R | 15 +++++++++ inst/shinyexample/R/app_server.R | 11 ++++--- inst/shinyexample/R/app_ui.R | 19 ++++++++--- inst/shinyexample/R/run_app.R | 11 +++++-- inst/shinyexample/inst/golem-config.yml | 3 ++ inst/shinyexample/man/app_server.Rd | 18 ---------- inst/shinyexample/man/app_ui.Rd | 14 -------- inst/shinyexample/man/run_app.Rd | 2 +- man/add_module.Rd | 4 ++- 11 files changed, 77 insertions(+), 67 deletions(-) delete mode 100644 inst/shinyexample/man/app_server.Rd delete mode 100644 inst/shinyexample/man/app_ui.Rd diff --git a/R/add_modules.R b/R/add_modules.R index 8f0a7545..a625f9af 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -10,6 +10,7 @@ #' @param dir_create Creates the directory if it doesn't exist, default is `TRUE`. #' @param fct The name of the fct file. #' @param utils The name of the utils file. +#' @param export Logical. Should the module be exported? Default is `FALSE`. #' @note This function will prefix the `name` argument with `mod_`. #' @export #' @importFrom glue glue @@ -22,7 +23,8 @@ add_module <- function( open = TRUE, dir_create = TRUE, fct = NULL, - utils = NULL + utils = NULL, + export = FALSE ){ name <- file_path_sans_ext(name) @@ -63,23 +65,21 @@ add_module <- function( glue <- function(...){ glue::glue(..., .open = "%", .close = "%") } - write_there("# Module UI") - write_there(" ") - write_there(glue("#' @title mod_%name%_ui and mod_%name%_server")) - write_there("#' @description A shiny Module.") + + write_there(glue("#' %name% UI Function")) + write_there("#'") + write_there("#' @description A shiny Module.") write_there("#'") - write_there("#' @param id shiny id") - write_there("#' @param input internal") - write_there("#' @param output internal") - write_there("#' @param session internal") + write_there("#' @param id,input,output,session Internal parameters for {shiny}.") write_there("#'") - write_there(glue("#' @rdname mod_%name%")) + if (export){ + write_there(glue("#' @rdname mod_%name%")) + write_there("#' @export ") + } else { + write_there("#' @noRd ") + } write_there("#'") - write_there("#' @keywords internal") - write_there("#' @export ") write_there("#' @importFrom shiny NS tagList ") - - write_there(glue("mod_%name%_ui <- function(id){")) write_there(" ns <- NS(id)") write_there(" tagList(") @@ -87,14 +87,18 @@ add_module <- function( write_there(" )") write_there("}") write_there(" ") - write_there("# Module Server") - write_there(" ") - write_there(glue("#' @rdname mod_%name%")) - write_there("#' @export") - write_there("#' @keywords internal") - write_there(" ") + + write_there(glue("#' %name% Server Function")) + write_there("#'") + if (export){ + write_there(glue("#' @rdname mod_%name%")) + write_there("#' @export ") + } else { + write_there("#' @noRd ") + } write_there(glue("mod_%name%_server <- function(input, output, session){")) write_there(" ns <- session$ns") + write_there(" ") write_there("}") write_there(" ") diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index bf248860..7c54be88 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -3,5 +3,8 @@ export(run_app) import(config) import(shiny) +importFrom(golem,activate_js) +importFrom(golem,add_resource_path) +importFrom(golem,favicon) importFrom(golem,with_golem_options) importFrom(shiny,shinyApp) diff --git a/inst/shinyexample/R/app_config.R b/inst/shinyexample/R/app_config.R index 6408d6ed..2b17433f 100644 --- a/inst/shinyexample/R/app_config.R +++ b/inst/shinyexample/R/app_config.R @@ -1,8 +1,23 @@ +#' Access files in the current app +#' +#' @param ... Character vector specifying directory and or file to +#' point to inside the current package. +#' +#' @noRd app_sys <- function(...){ system.file(..., package = "shinyexample") } + +#' Access files in the current app +#' +#' @param value Value to retrieve from the config file. +#' @param config R_CONFIG_ACTIVE value. +#' @param use_parent Logical, scan the parent directory for config file. +#' #' @import config +#' +#' @noRd get_golem_config <- function( value, config = Sys.getenv("R_CONFIG_ACTIVE", "default"), diff --git a/inst/shinyexample/R/app_server.R b/inst/shinyexample/R/app_server.R index 63776716..20d69cb3 100644 --- a/inst/shinyexample/R/app_server.R +++ b/inst/shinyexample/R/app_server.R @@ -1,9 +1,10 @@ -#' App Server +#' The application server-side #' -#' @param input input -#' @param output ouput -#' @param session session +#' @param input,output,session Internal parameters for {shiny}. +#' DO NOT REMOVE. #' @import shiny -app_server <- function(input, output,session) { +#' @noRd +app_server <- function( input, output, session ) { # List the first level callModules here + } diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index aada22d1..4c52fa09 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,7 +1,9 @@ -#' Application UI +#' The application User-Interfac #' -#' @param request needed to use `enableBookmarking()` +#' @param request Internal parameter for {shiny}. +#' DO NOT REMOVE. #' @import shiny +#' @noRd app_ui <- function(request) { tagList( # Leave this function for adding external resources @@ -13,16 +15,23 @@ app_ui <- function(request) { ) } +#' Add external Resources to the Application +#' +#' This function is internally used to add external +#' resources inside the Shiny application. +#' #' @import shiny +#' @importFrom golem add_resource_path activate_js favicon +#' @noRd golem_add_external_resources <- function(){ - golem::add_resource_path( + add_resource_path( 'www', app_sys('app/www') ) tags$head( - golem::activate_js(), - golem::favicon(), + activate_js(), + favicon(), tags$title("shinyexample") # Add here all the external resources # If you have a custom.css in the inst/app/www diff --git a/inst/shinyexample/R/run_app.R b/inst/shinyexample/R/run_app.R index 743c013e..dd6ae784 100644 --- a/inst/shinyexample/R/run_app.R +++ b/inst/shinyexample/R/run_app.R @@ -1,13 +1,18 @@ #' Run the Shiny Application #' -#' @param ... A list of Options to be added to the app +#' @param ... A series of options to be used inside the app. #' #' @export #' @importFrom shiny shinyApp #' @importFrom golem with_golem_options -run_app <- function(...) { +run_app <- function( + ... +) { with_golem_options( - app = shinyApp(ui = app_ui, server = app_server), + app = shinyApp( + ui = app_ui, + server = app_server + ), golem_opts = list(...) ) } diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml index 00ae9a47..6ce2c787 100644 --- a/inst/shinyexample/inst/golem-config.yml +++ b/inst/shinyexample/inst/golem-config.yml @@ -5,4 +5,7 @@ default: production: app_prod: yes + +dev: + golem_wd: !exp here::here() diff --git a/inst/shinyexample/man/app_server.Rd b/inst/shinyexample/man/app_server.Rd deleted file mode 100644 index 5c238ec9..00000000 --- a/inst/shinyexample/man/app_server.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/app_server.R -\name{app_server} -\alias{app_server} -\title{App Server} -\usage{ -app_server(input, output, session) -} -\arguments{ -\item{input}{input} - -\item{output}{ouput} - -\item{session}{session} -} -\description{ -App Server -} diff --git a/inst/shinyexample/man/app_ui.Rd b/inst/shinyexample/man/app_ui.Rd deleted file mode 100644 index 549ffee1..00000000 --- a/inst/shinyexample/man/app_ui.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/app_ui.R -\name{app_ui} -\alias{app_ui} -\title{Application UI} -\usage{ -app_ui(request) -} -\arguments{ -\item{request}{needed to use `enableBookmarking()`} -} -\description{ -Application UI -} diff --git a/inst/shinyexample/man/run_app.Rd b/inst/shinyexample/man/run_app.Rd index 174137cd..db9dd85e 100644 --- a/inst/shinyexample/man/run_app.Rd +++ b/inst/shinyexample/man/run_app.Rd @@ -7,7 +7,7 @@ run_app(...) } \arguments{ -\item{...}{A list of Options to be added to the app} +\item{...}{A series of options to be used inside the app.} } \description{ Run the Shiny Application diff --git a/man/add_module.Rd b/man/add_module.Rd index a6b8342a..a559f0c0 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -5,7 +5,7 @@ \title{Create a module} \usage{ add_module(name, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE, fct = NULL, utils = NULL) + dir_create = TRUE, fct = NULL, utils = NULL, export = FALSE) } \arguments{ \item{name}{The name of the module} @@ -19,6 +19,8 @@ add_module(name, pkg = get_golem_wd(), open = TRUE, \item{fct}{The name of the fct file.} \item{utils}{The name of the utils file.} + +\item{export}{Logical. Should the module be exported? Default is \code{FALSE}.} } \description{ This function creates a module inside the \code{R/} folder, based From 93d1178898c83fd5d641df05c06e05e75690f7dc Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 25 Nov 2019 21:28:51 +0100 Subject: [PATCH 150/211] explicit namespacing in tests --- tests/testthat/test-zreload.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-zreload.R b/tests/testthat/test-zreload.R index b98f03da..e8dced53 100644 --- a/tests/testthat/test-zreload.R +++ b/tests/testthat/test-zreload.R @@ -16,14 +16,14 @@ test_that("test document_and_reload",{ file ="R/sum.R" ) document_and_reload(pkg = ".") - expect_equal(sum_golem(1,2),3) + testthat::expect_equal(sum_golem(1,2),3) }) }) test_that("test detach_all_attached",{ with_dir(pkg_reload,{ test <- detach_all_attached() - expect_true( + testthat::expect_true( all(purrr::map_lgl(test, is.null)) ) }) From 7c69ac20fb1659d75eb03edc56e3a490993d53d8 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 25 Nov 2019 21:40:36 +0100 Subject: [PATCH 151/211] some tests tweaks --- tests/testthat/helper-config.R | 1 + tests/testthat/test-zzzzzzzzzz.R | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 75f3140a..089ee3ef 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -66,4 +66,5 @@ withr::with_dir(pkg, { set_golem_options() usethis::proj_set(pkg) orig_test <- set_golem_wd(pkg) + usethis::use_mit_license("Golem") }) \ No newline at end of file diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 031e456b..4c7dacea 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -1,5 +1,5 @@ # For setting back old usethis settings -if (!identical(Sys.getenv("TRAVIS"), "true")) { +if (dir.exists(orig_test)){ usethis::proj_set(orig_test) } From 7fabf91830c61360ae5c4105b9283e34a7c69ca0 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 25 Nov 2019 21:43:46 +0100 Subject: [PATCH 152/211] pkg::run_app() is no longer explicitely namespaced in run_dev.R, close #267 --- inst/shinyexample/dev/run_dev.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shinyexample/dev/run_dev.R b/inst/shinyexample/dev/run_dev.R index e831bae5..6729c32b 100644 --- a/inst/shinyexample/dev/run_dev.R +++ b/inst/shinyexample/dev/run_dev.R @@ -9,4 +9,4 @@ golem::detach_all_attached() golem::document_and_reload() # Run the application -shinyexample::run_app() +run_app() From c726ac729e26652914aeefea9ca793389cbf1b74 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 26 Nov 2019 21:49:34 +0100 Subject: [PATCH 153/211] close #304 --- NAMESPACE | 1 - R/addin_functions.R | 17 ------------ R/addins.R | 66 +++++++++++++++++++++++++++++++++------------ man/addins.Rd | 35 ++++++++++++++++++++++++ man/go_to.Rd | 16 ----------- man/insert_ns.Rd | 11 -------- 6 files changed, 84 insertions(+), 62 deletions(-) delete mode 100644 R/addin_functions.R create mode 100644 man/addins.Rd delete mode 100644 man/go_to.Rd delete mode 100644 man/insert_ns.Rd diff --git a/NAMESPACE b/NAMESPACE index 11fc2e92..83f94ee1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -34,7 +34,6 @@ export(get_golem_options) export(get_golem_version) export(get_golem_wd) export(get_sysreqs) -export(insert_ns) export(invoke_js) export(make_dev) export(message_dev) diff --git a/R/addin_functions.R b/R/addin_functions.R deleted file mode 100644 index c0da7ac9..00000000 --- a/R/addin_functions.R +++ /dev/null @@ -1,17 +0,0 @@ -#' Shorcut to insert NS -#' -#' @importFrom rstudioapi getSourceEditorContext -#' @importFrom rstudioapi modifyRange -#' -#' @export -insert_ns <- function () { - curr_editor <- rstudioapi::getSourceEditorContext() - - id <- curr_editor$id - sel_rng <- curr_editor$selection[[1]]$range - sel_text <- curr_editor$selection[[1]]$text - - mod_text <- paste0("ns(", sel_text, ")") - - rstudioapi::modifyRange(sel_rng, mod_text, id = id) -} \ No newline at end of file diff --git a/R/addins.R b/R/addins.R index 0eb845e8..91025200 100644 --- a/R/addins.R +++ b/R/addins.R @@ -1,9 +1,33 @@ -#' {golem} addins +#' `{golem}` addins #' -#' Addins to go to common files used in developing a `{golem}` package. +#' `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. +#' +#' @importFrom rstudioapi getSourceEditorContext modifyRange +#' @importFrom attempt stop_if_not +#' @importFrom rstudioapi modifyRange #' -#' @param file Name of the file to be loaded from the `dev` directory. -#' @param wd Working directory of the current golem package. +#' @aliases addins +#' @rdname addins +#' @name addins +NULL + +#' @rdname addins +#' @aliases addins +insert_ns <- function () { + + curr_editor <- rstudioapi::getSourceEditorContext() + + id <- curr_editor$id + sel_rng <- curr_editor$selection[[1]]$range + sel_text <- curr_editor$selection[[1]]$text + + mod_text <- paste0("ns(", sel_text, ")") + + rstudioapi::modifyRange(sel_rng, mod_text, id = id) +} + go_to <- function( file, @@ -15,39 +39,47 @@ go_to <- function( if (! file.exists(file)){ message(file, "not found.") } - if (rstudioapi::hasFun("navigateToFile") ){ - rstudioapi::navigateToFile( - file - ) - } else { - message("Your version of RStudio does not support `navigateToFile`") - } + + stop_if_not( + rstudioapi::hasFun("navigateToFile"), + msg = "Your version of RStudio does not support `navigateToFile`" + ) + + rstudioapi::navigateToFile( file ) } +#' @rdname addins +#' @aliases addins go_to_start <- function(){ go_to("dev/01_start.R") } - +#' @rdname addins +#' @aliases addins go_to_dev <- function(){ go_to("dev/02_dev.R") } - +#' @rdname addins +#' @aliases addins go_to_deploy <- function(){ go_to("dev/03_deploy.R") } - +#' @rdname addins +#' @aliases addins go_to_run_dev <- function(){ go_to("dev/run_dev.R") } - +#' @rdname addins +#' @aliases addins go_to_app_ui <- function(){ go_to("R/app_ui.R") } - +#' @rdname addins +#' @aliases addins go_to_app_server <- function(){ go_to("R/app_server.R") } - +#' @rdname addins +#' @aliases addins go_to_run_app <- function(){ go_to("R/run_app.R") } \ No newline at end of file diff --git a/man/addins.Rd b/man/addins.Rd new file mode 100644 index 00000000..667a0aa6 --- /dev/null +++ b/man/addins.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/addins.R +\name{addins} +\alias{addins} +\alias{insert_ns} +\alias{go_to_start} +\alias{go_to_dev} +\alias{go_to_deploy} +\alias{go_to_run_dev} +\alias{go_to_app_ui} +\alias{go_to_app_server} +\alias{go_to_run_app} +\title{\code{{golem}} addins} +\usage{ +insert_ns() + +go_to_start() + +go_to_dev() + +go_to_deploy() + +go_to_run_dev() + +go_to_app_ui() + +go_to_app_server() + +go_to_run_app() +} +\description{ +\code{insert_ns()} takes a selected character vector and wrap it in \code{ns()} +The series of \code{go_to_*()} addins help you go to +common files used in developing a \code{{golem}} application. +} diff --git a/man/go_to.Rd b/man/go_to.Rd deleted file mode 100644 index 903fddc1..00000000 --- a/man/go_to.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addins.R -\name{go_to} -\alias{go_to} -\title{{golem} addins} -\usage{ -go_to(file, wd = golem::get_golem_wd()) -} -\arguments{ -\item{file}{Name of the file to be loaded from the \code{dev} directory.} - -\item{wd}{Working directory of the current golem package.} -} -\description{ -Addins to go to common files used in developing a \code{{golem}} package. -} diff --git a/man/insert_ns.Rd b/man/insert_ns.Rd deleted file mode 100644 index d7d0f9f6..00000000 --- a/man/insert_ns.Rd +++ /dev/null @@ -1,11 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addin_functions.R -\name{insert_ns} -\alias{insert_ns} -\title{Shorcut to insert NS} -\usage{ -insert_ns() -} -\description{ -Shorcut to insert NS -} From e24cc1d709747d20fdf9b066ff1a4d40fe397a81 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 27 Nov 2019 19:26:38 +0100 Subject: [PATCH 154/211] fix #306 --- R/add_deploy_helpers.R | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 24849955..522623c6 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -395,12 +395,19 @@ dock_from_desc <- function( packages_on_cran <- remotes_deps$package[remotes_deps$is_cran] %>% intersect(packages) + packages_not_on_cran <- packages %>% setdiff(packages_on_cran) - packages_on_cran <- setNames(lapply(packages_on_cran, packageVersion), packages_on_cran) + packages_with_version <- data.frame( + package=remotes_deps$package, + installed=remotes_deps$installed,stringsAsFactors = FALSE + )%>% + filter(package %in% packages_on_cran) + + packages_on_cran <- packages_with_version$installed %>% setNames(packages_with_version$package) dock <- dockerfiler::Dockerfile$new(FROM = FROM) From c031dc93ef49d1f6b17183755c79abaf0e08daa3 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 27 Nov 2019 19:42:21 +0100 Subject: [PATCH 155/211] fix issue with filter --- NAMESPACE | 1 + R/add_deploy_helpers.R | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 11fc2e92..b96acabe 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -65,6 +65,7 @@ importFrom(config,get) importFrom(desc,desc_get_deps) importFrom(desc,description) importFrom(dockerfiler,Dockerfile) +importFrom(dplyr,filter) importFrom(fs,file_copy) importFrom(glue,glue) importFrom(htmltools,includeScript) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 522623c6..54535ae5 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -350,6 +350,7 @@ alert_build <- function(path, output ,build_golem_from_source){ #' @importFrom desc desc_get_deps #' @importFrom magrittr %>% #' @importFrom stats setNames +#' @importFrom dplyr filter #' dock_from_desc <- function( path = "DESCRIPTION", @@ -401,10 +402,10 @@ dock_from_desc <- function( packages_with_version <- data.frame( - package=remotes_deps$package, + pp=remotes_deps$package, installed=remotes_deps$installed,stringsAsFactors = FALSE )%>% - filter(package %in% packages_on_cran) + dplyr::filter(pp %in% packages_on_cran) packages_on_cran <- packages_with_version$installed %>% setNames(packages_with_version$package) From 26d75a2e2eeb857c234bed5f455cb63e380ad059 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 27 Nov 2019 19:45:27 +0100 Subject: [PATCH 156/211] remove filter... --- R/add_deploy_helpers.R | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 54535ae5..d7009085 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -350,7 +350,6 @@ alert_build <- function(path, output ,build_golem_from_source){ #' @importFrom desc desc_get_deps #' @importFrom magrittr %>% #' @importFrom stats setNames -#' @importFrom dplyr filter #' dock_from_desc <- function( path = "DESCRIPTION", @@ -402,11 +401,10 @@ dock_from_desc <- function( packages_with_version <- data.frame( - pp=remotes_deps$package, + package=remotes_deps$package, installed=remotes_deps$installed,stringsAsFactors = FALSE - )%>% - dplyr::filter(pp %in% packages_on_cran) - + ) + packages_with_version <- packages_with_version[packages_with_version$package %in% packages_on_cran,] packages_on_cran <- packages_with_version$installed %>% setNames(packages_with_version$package) From 995044481c73871feb0b009a65e8ce0a51cb360b Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 27 Nov 2019 20:50:42 +0100 Subject: [PATCH 157/211] NAMESPACE --- NAMESPACE | 1 - 1 file changed, 1 deletion(-) diff --git a/NAMESPACE b/NAMESPACE index b96acabe..11fc2e92 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -65,7 +65,6 @@ importFrom(config,get) importFrom(desc,desc_get_deps) importFrom(desc,description) importFrom(dockerfiler,Dockerfile) -importFrom(dplyr,filter) importFrom(fs,file_copy) importFrom(glue,glue) importFrom(htmltools,includeScript) From fe1396821b6c0df232a473dbb012aa45cd1edf93 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 27 Nov 2019 21:21:38 +0100 Subject: [PATCH 158/211] add upgrade = "never" everywhere in isntall_version --- R/add_deploy_helpers.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 24849955..1c2c94ae 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -429,7 +429,7 @@ dock_from_desc <- function( ping <- mapply(function(dock, ver, nm){ res <- dock$RUN( sprintf( - "Rscript -e 'remotes::install_version(\"%s\", version = \"%s\")'", + "Rscript -e 'remotes::install_version(\"%s\",upgrade=\"never\", version = \"%s\")'", nm, ver ) ) From a3ec30f97b15abfe357eb4ad5d2a6f13d09fbd71 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 27 Nov 2019 21:44:44 +0100 Subject: [PATCH 159/211] we now default to $(document).ready() --- R/add_files.R | 53 ++++++++++++++++++++++++++++++------------------ man/add_files.Rd | 4 +++- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/R/add_files.R b/R/add_files.R index e20e0251..c1eb9c32 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -5,6 +5,7 @@ #' #' @inheritParams add_module #' @param dir Path to the dir where the file while be created. +#' @param with_doc_ready Should the default file include `$( document ).ready()`? #' @export #' @rdname add_files #' @importFrom glue glue @@ -17,10 +18,13 @@ add_js_file <- function( pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, - dir_create = TRUE + dir_create = TRUE, + with_doc_ready = TRUE ){ - attempt::stop_if(rlang::is_missing(name), - msg = "Name is required") + attempt::stop_if( + rlang::is_missing(name), + msg = "Name is required" + ) name <- file_path_sans_ext(name) @@ -50,14 +54,23 @@ add_js_file <- function( file.create(where) + if (with_doc_ready){ + write_there <- function(...){ + write(..., file = where, append = TRUE) + } + write_there("$( document ).ready(function() {") + write_there(" ") + write_there("});") + } + cat_green_tick(glue::glue("File created at {where}")) if (file.exists(paste0(pkg, "/DESCRIPTION"))) { - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - )} + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) + )} if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) @@ -76,7 +89,7 @@ add_js_handler <- function( dir_create = TRUE ){ attempt::stop_if(rlang::is_missing(name), - msg = "Name is required") + msg = "Name is required") name <- file_path_sans_ext(name) @@ -121,11 +134,11 @@ add_js_handler <- function( cat_green_tick(glue::glue("File created at {where}")) if (file.exists(paste0(pkg, "/DESCRIPTION"))) { - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - )} + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) + )} if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) @@ -144,7 +157,7 @@ add_css_file <- function( dir_create = TRUE ){ attempt::stop_if(rlang::is_missing(name), - msg = "Name is required") + msg = "Name is required") name <- file_path_sans_ext(name) @@ -178,11 +191,11 @@ add_css_file <- function( cat_green_tick(glue::glue("File created at {where}")) if (file.exists(paste0(pkg, "/DESCRIPTION"))) { - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' - ) - )} + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' + ) + )} if (rstudioapi::isAvailable() & open ){ rstudioapi::navigateToFile(where) diff --git a/man/add_files.Rd b/man/add_files.Rd index a6daf534..e85db825 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -8,7 +8,7 @@ \title{Create Files} \usage{ add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) + open = TRUE, dir_create = TRUE, with_doc_ready = TRUE) add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", open = TRUE, dir_create = TRUE) @@ -29,6 +29,8 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \item{open}{Should the file be opened?} \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} + +\item{with_doc_ready}{Should the default file include \code{$( document ).ready()}?} } \description{ These functions create files inside the \code{inst/app} folder. From c2737f43626d6f33ff38da2bc179182175bd9ec8 Mon Sep 17 00:00:00 2001 From: Aart Goossens Date: Thu, 28 Nov 2019 09:43:15 +0100 Subject: [PATCH 160/211] Typo in golem::add_dockerfile_heroku() --- vignettes/c_deploy.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index f91b45a0..efc2f809 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -71,6 +71,6 @@ golem::add_dockerfile() golem::add_shinyproxy_dockerfile() # If you want to deploy to Heroku -golem::add_heroku_dockerfile() +golem::add_dockerfile_heroku() ``` From 3a878f0632701a6737e7c3b953dc1b1892004ec0 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 28 Nov 2019 09:51:25 +0100 Subject: [PATCH 161/211] Corrected typo in vignette --- vignettes/c_deploy.Rmd | 2 +- vignettes/f_config.Rmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index efc2f809..605da7c1 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -68,7 +68,7 @@ golem::add_shinyserver_file() golem::add_dockerfile() # If you want to deploy to ShinyProxy -golem::add_shinyproxy_dockerfile() +golem::add_dockerfile_shinyproxy() # If you want to deploy to Heroku golem::add_dockerfile_heroku() diff --git a/vignettes/f_config.Rmd b/vignettes/f_config.Rmd index 6a20817d..e5403705 100644 --- a/vignettes/f_config.Rmd +++ b/vignettes/f_config.Rmd @@ -69,7 +69,7 @@ get_golem_version() You can set these with: -```{r} +```{r eval = FALSE} set_golem_name("this") set_golem_wd(".") set_golem_version("0.0.1") From 9b5fb4d5cfc096ffede0b481a4a2bd7f14901cfd Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 28 Nov 2019 22:19:13 +0100 Subject: [PATCH 162/211] Refactored everything with {fs} + optimized some parts of the code --- .Rbuildignore | 2 + .gitignore | 2 + NAMESPACE | 10 ++ R/add_deploy_helpers.R | 258 +++++++++++++++++++++++--------------- R/add_files.R | 78 ++++++------ R/add_modules.R | 12 +- R/add_r_files.R | 11 +- R/add_ressource_path.R | 12 +- R/addins.R | 6 +- R/config.R | 23 ++-- R/create_golem.R | 53 +++++--- R/desc.R | 12 +- R/get_sysreqs.R | 54 ++++---- R/options.R | 9 +- R/use_favicon.R | 32 +++-- R/use_files.R | 15 +-- R/use_recommended.R | 23 ++-- R/use_utils.R | 45 ++++--- R/utils.R | 41 ++++-- inst/manualtests/script.R | 47 +++++++ inst/manualtests/tests.sh | 9 ++ 21 files changed, 473 insertions(+), 281 deletions(-) create mode 100644 inst/manualtests/script.R create mode 100644 inst/manualtests/tests.sh diff --git a/.Rbuildignore b/.Rbuildignore index 586a40cf..558a2003 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -18,3 +18,5 @@ ^CRAN-RELEASE$ ^appveyor\.yml$ ^CONTRIBUTING\.md$ +^doc$ +^Meta$ diff --git a/.gitignore b/.gitignore index 6c28d84a..8123f8cf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ inst/rstudio/.DS_Store .Rproj.user .Rhistory .RData +doc +Meta diff --git a/NAMESPACE b/NAMESPACE index 83f94ee1..629e24d3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -64,7 +64,17 @@ importFrom(config,get) importFrom(desc,desc_get_deps) importFrom(desc,description) importFrom(dockerfiler,Dockerfile) +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,file_move) +importFrom(fs,file_temp) +importFrom(fs,path) +importFrom(fs,path_abs) +importFrom(fs,path_file) importFrom(glue,glue) importFrom(htmltools,includeScript) importFrom(htmltools,save_html) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 6bdf84d5..c1c0d225 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -2,21 +2,21 @@ #' @importFrom cli cat_bullet #' @importFrom usethis use_build_ignore use_package #' @importFrom pkgload pkg_name +#' @importFrom fs path file_create path_file add_rstudio_files <- function( pkg, open, service = c("RStudio Connect", "Shiny Server", "ShinyApps.io") ){ service <- match.arg(service) - where <- file.path(pkg, "app.R") - - if ( !check_file_exist(where) ) return(invisible(FALSE)) + where <- path(pkg, "app.R") + file_create( where ) write_there <- function(..., here = where){ write(..., here, append = TRUE) } - file.create( where ) - use_build_ignore( basename(where) ) + + 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()") @@ -42,7 +42,6 @@ add_rstudio_files <- function( ) ) - if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) } else { @@ -90,7 +89,7 @@ add_rstudioconnect_file <- function( add_shinyappsio_file <- function( pkg = get_golem_wd(), open = TRUE - ){ +){ add_rstudio_files(pkg = pkg, open = open, service = "ShinyApps.io") } @@ -99,7 +98,7 @@ add_shinyappsio_file <- function( add_shinyserver_file <- function( pkg = get_golem_wd(), open = TRUE - ){ +){ add_rstudio_files(pkg = pkg, open = open, service = "Shiny Server") } @@ -131,6 +130,7 @@ add_shinyserver_file <- function( #' @importFrom desc desc_get_deps #' @importFrom dockerfiler Dockerfile #' @importFrom rstudioapi navigateToFile isAvailable +#' @importFrom fs path path_file #' @examples #' \donttest{ #' # Add a standard Dockerfile @@ -167,22 +167,33 @@ add_dockerfile <- function( build_golem_from_source = TRUE ) { + where <- path(pkg, output) - where <- file.path(pkg, output) if ( !check_file_exist(where) ) return(invisible(FALSE)) - usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, - sysreqs = sysreqs, repos = repos,expand = expand, - build_golem_from_source = build_golem_from_source, - update_tar_gz = update_tar_gz) + usethis::use_build_ignore(path_file(where)) + + dock <- dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_golem_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz + ) + dock$EXPOSE(port) + dock$CMD( glue::glue( "R -e \"options('shiny.port'={port},shiny.host='{host}');{read.dcf(path)[1]}::run_app()\"" ) ) + dock$write(output) + if (open) { if (rstudioapi::isAvailable()) { rstudioapi::navigateToFile(output) @@ -190,14 +201,17 @@ add_dockerfile <- function( 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 + ) } #' @export #' @rdname dockerfiles +#' @importFrom fs path path_file add_dockerfile_shinyproxy <- function( path = "DESCRIPTION", output = "Dockerfile", @@ -216,14 +230,22 @@ add_dockerfile_shinyproxy <- function( build_golem_from_source = TRUE ){ - where <- file.path(pkg, output) + where <- path(pkg, output) if ( !check_file_exist(where) ) return(invisible(FALSE)) - usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, - sysreqs = sysreqs, repos = repos, expand = expand, - build_golem_from_source=build_golem_from_source, - update_tar_gz = update_tar_gz) + + usethis::use_build_ignore(output) + + dock <- dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_golem_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz + ) dock$EXPOSE(3838) dock$CMD(glue::glue( @@ -238,9 +260,11 @@ add_dockerfile_shinyproxy <- function( try(file.edit(output)) } } - alert_build(path, output,build_golem_from_source=build_golem_from_source) - - usethis::use_build_ignore(files = output) + alert_build( + path, + output, + build_golem_from_source = build_golem_from_source + ) invisible(output) @@ -248,6 +272,7 @@ add_dockerfile_shinyproxy <- function( #' @export #' @rdname dockerfiles +#' @importFrom fs path path_file add_dockerfile_heroku <- function( path = "DESCRIPTION", output = "Dockerfile", @@ -265,15 +290,22 @@ add_dockerfile_heroku <- function( update_tar_gz = TRUE, build_golem_from_source = TRUE ){ - where <- file.path(pkg, output) + where <- path(pkg, output) + + if ( !check_file_exist(where) ) return(invisible(FALSE)) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - usethis::use_build_ignore(basename(where)) - dock <- dock_from_desc(path = path, FROM = from, AS = as, sysreqs = sysreqs, repos = repos, - expand = expand,build_golem_from_source = build_golem_from_source, - update_tar_gz = update_tar_gz) + usethis::use_build_ignore(output) + + dock <- dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_golem_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz + ) dock$CMD( glue::glue( @@ -282,7 +314,11 @@ add_dockerfile_heroku <- function( ) 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( "\\.", "-", @@ -324,12 +360,12 @@ alert_build <- function(path, output ,build_golem_from_source){ cat_green_tick( glue("Dockerfile created at {output}") ) - if ( ! build_golem_from_source){ - cat_red_bullet( - glue::glue( - "Be sure to keep your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" + if ( ! build_golem_from_source ){ + cat_red_bullet( + glue::glue( + "Be sure to keep your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" + ) ) - ) } } @@ -350,7 +386,7 @@ alert_build <- function(path, output ,build_golem_from_source){ #' @importFrom desc desc_get_deps #' @importFrom magrittr %>% #' @importFrom stats setNames -#' +#' @noRd dock_from_desc <- function( path = "DESCRIPTION", FROM = paste0( @@ -366,28 +402,32 @@ dock_from_desc <- function( build_golem_from_source = TRUE ){ - packages <- desc::desc_get_deps(path)$package packages <- packages[packages != "R"] # remove R - packages <- packages[ !packages %in% c("base", "boot", "class", "cluster", - "codetools", "compiler", "datasets", - "foreign", "graphics", "grDevices", - "grid", "KernSmooth", "lattice", "MASS", - "Matrix", "methods", "mgcv", "nlme", - "nnet", "parallel", "rpart", "spatial", - "splines", "stats", "stats4", "survival", - "tcltk", "tools", "utils")] # remove base and recommended - + packages <- packages[ !packages %in% c( + "base", "boot", "class", "cluster", + "codetools", "compiler", "datasets", + "foreign", "graphics", "grDevices", + "grid", "KernSmooth", "lattice", "MASS", + "Matrix", "methods", "mgcv", "nlme", + "nnet", "parallel", "rpart", "spatial", + "splines", "stats", "stats4", "survival", + "tcltk", "tools", "utils" + )] # remove base and recommended - - if (sysreqs){ # please wait during system requirement calculation - cat_bullet("Please wait during system requirements calculation...",bullet = "info",bullet_col = "green") # TODO animated version ? - system_requirement <- unique(get_sysreqs(packages = packages)) - cat_bullet("done",bullet = "tick",bullet_col = "green") # TODO animated version ? + cat_bullet( + "Please wait while we compute system requirements...", + bullet = "info", + bullet_col = "green" + ) # TODO animated version ? + system_requirement <- unique( + get_sysreqs(packages = packages) + ) + cat_green_tick("Done") # TODO animated version ? - }else{ + } else{ system_requirement <- NULL } @@ -395,61 +435,79 @@ dock_from_desc <- function( packages_on_cran <- remotes_deps$package[remotes_deps$is_cran] %>% intersect(packages) - packages_not_on_cran <- packages %>% setdiff(packages_on_cran) - - packages_with_version <- data.frame( - package=remotes_deps$package, - installed=remotes_deps$installed,stringsAsFactors = FALSE + packages_with_version <- data.frame( + package=remotes_deps$package, + installed=remotes_deps$installed, + stringsAsFactors = FALSE ) - packages_with_version <- packages_with_version[packages_with_version$package %in% packages_on_cran,] + packages_with_version <- packages_with_version[ + packages_with_version$package %in% packages_on_cran, + ] - packages_on_cran <- packages_with_version$installed %>% setNames(packages_with_version$package) + packages_on_cran <- packages_with_version$installed %>% + setNames(packages_with_version$package) dock <- dockerfiler::Dockerfile$new(FROM = FROM) if (length(system_requirement)>0){ - if ( !expand){ - dock$RUN(paste("apt-get update && apt-get install -y ",paste(system_requirement,collapse = " "))) - } else{ - dock$RUN("apt-get update" ) - for ( sr in system_requirement){ - dock$RUN(paste("apt-get install -y ",sr)) + dock$RUN( + paste( + "apt-get update && apt-get install -y ", + paste(system_requirement, collapse = " ") + ) + ) + } else { + dock$RUN("apt-get update") + for ( sr in system_requirement ){ + dock$RUN( paste("apt-get install -y ", sr) ) } } - - } + } dock$RUN( - sprintf("echo \"options(repos = c(CRAN = '%s'), download.file.method = 'libcurl')\" >> /usr/local/lib/R/etc/Rprofile.site",repos)) + sprintf( + "echo \"options(repos = c(CRAN = '%s'), download.file.method = 'libcurl')\" >> /usr/local/lib/R/etc/Rprofile.site", + repos + ) + ) + dock$RUN("R -e 'install.packages(\"remotes\")'") # We need to be sure install_cran is there dock$RUN("R -e 'remotes::install_github(\"r-lib/remotes\", ref = \"97bbf81\")'") - if ( length(packages_on_cran>0)){ - ping <- mapply(function(dock, ver, nm){ - res <- dock$RUN( - sprintf( - "Rscript -e 'remotes::install_version(\"%s\",upgrade=\"never\", version = \"%s\")'", - nm, ver + ping <- mapply(function(dock, ver, nm){ + res <- dock$RUN( + sprintf( + "Rscript -e 'remotes::install_version(\"%s\",upgrade=\"never\", version = \"%s\")'", + nm, + ver + ) ) + }, + ver = packages_on_cran, + nm = names(packages_on_cran), + MoreArgs = list(dock = dock) ) - }, ver = packages_on_cran, nm = names(packages_on_cran), MoreArgs = list(dock = dock)) } - if ( length(packages_not_on_cran>0)){ + if ( length(packages_not_on_cran > 0)){ - nn<- lapply( - remotes_deps$remote[!remotes_deps$is_cran], - function(.){ .[c('repo','username','sha')] - }) %>% do.call(rbind,.) %>% as.data.frame() + nn<- lapply( + remotes_deps$remote[!remotes_deps$is_cran], + function(.){ + .[c('repo','username','sha')] + } + ) %>% + do.call(rbind,.) %>% + as.data.frame() - nn<- glue::glue("{nn$username}/{nn$repo}@{nn$sha}") + nn <- glue::glue("{nn$username}/{nn$repo}@{nn$sha}") pong <- mapply(function(dock, ver, nm){ res <- dock$RUN( @@ -458,32 +516,36 @@ dock_from_desc <- function( ver ) ) - }, ver = nn, MoreArgs = list(dock = dock)) + }, + ver = nn, + MoreArgs = list(dock = dock) + ) } if ( !build_golem_from_source){ if ( update_tar_gz ){ - ancienne_version <- list.files(pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"),full.names = TRUE) + ancienne_version <- list.files( + pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"), + full.names = TRUE + ) if (length(ancienne_version) > 0){ - cat_red_bullet(glue::glue("We remove {paste(ancienne_version,collapse = ", ")} from folder")) - lapply(ancienne_version,file.remove) - lapply(ancienne_version,unlink,force=TRUE) + cat_red_bullet(glue::glue("We remove {paste(ancienne_version,collapse = ", ")} from folder")) + lapply(ancienne_version,file.remove) + lapply(ancienne_version,unlink,force=TRUE) } - - + cat_green_tick(glue::glue(" {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz created.")) devtools::build(path = ".") } # we use an already builded tar.gz file - dock$COPY( - from = paste0(read.dcf(path)[1], "_*.tar.gz"), - to = "/app.tar.gz" - ) - dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\",upgrade=\"never\")'") + from = paste0(read.dcf(path)[1], "_*.tar.gz"), + to = "/app.tar.gz" + ) + dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\",upgrade=\"never\")'") } else { dock$RUN("mkdir /build_zone") dock$ADD(from = ".",to = "/build_zone") diff --git a/R/add_files.R b/R/add_files.R index c1eb9c32..8cd1fec2 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -12,7 +12,7 @@ #' @importFrom cli cat_bullet #' @importFrom utils file.edit #' @importFrom tools file_path_sans_ext - +#' @importFrom fs path_abs path file_create file_exists add_js_file <- function( name, pkg = get_golem_wd(), @@ -28,7 +28,7 @@ add_js_file <- function( name <- file_path_sans_ext(name) - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_dir_if_needed( @@ -43,16 +43,13 @@ add_js_file <- function( return(invisible(FALSE)) } - dir <- normalizePath(dir) + dir <- path_abs(dir) - where <- file.path( + where <- path( dir, glue::glue("{name}.js") ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - file.create(where) + file_create(where) if (with_doc_ready){ write_there <- function(...){ @@ -63,9 +60,11 @@ add_js_file <- function( write_there("});") } - cat_green_tick(glue::glue("File created at {where}")) + cat_green_tick( + glue::glue("File created at {where}") + ) - if (file.exists(paste0(pkg, "/DESCRIPTION"))) { + if (file_exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' @@ -81,6 +80,7 @@ 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(), @@ -88,12 +88,14 @@ add_js_handler <- function( open = TRUE, dir_create = TRUE ){ - attempt::stop_if(rlang::is_missing(name), - msg = "Name is required") + attempt::stop_if( + rlang::is_missing(name), + msg = "Name is required" + ) name <- file_path_sans_ext(name) - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_dir_if_needed( @@ -108,16 +110,13 @@ add_js_handler <- function( return(invisible(FALSE)) } - dir <- normalizePath(dir) + dir <- path_abs(dir) where <- file.path( dir, glue::glue("{name}.js") ) - - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - file.create(where) + + file_create(where) write_there <- function(...){ write(..., file = where, append = TRUE) @@ -133,7 +132,7 @@ add_js_handler <- function( cat_green_tick(glue::glue("File created at {where}")) - if (file.exists(paste0(pkg, "/DESCRIPTION"))) { + if (file_exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' @@ -149,6 +148,7 @@ add_js_handler <- function( #' @export #' @rdname add_files +#' @importFrom fs path_abs path file_create file_exists add_css_file <- function( name, pkg = get_golem_wd(), @@ -156,12 +156,14 @@ add_css_file <- function( open = TRUE, dir_create = TRUE ){ - attempt::stop_if(rlang::is_missing(name), - msg = "Name is required") + attempt::stop_if( + rlang::is_missing(name), + msg = "Name is required" + ) name <- file_path_sans_ext(name) - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_dir_if_needed( @@ -176,21 +178,17 @@ add_css_file <- function( return(invisible(FALSE)) } - dir <- normalizePath(dir) + dir <- path_abs(dir) - where <- file.path( + where <- path( dir, glue::glue("{name}.css") ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - file.create(where) + file_create(where) cat_green_tick(glue::glue("File created at {where}")) - if (file.exists(paste0(pkg, "/DESCRIPTION"))) { + if (file_exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' @@ -208,6 +206,7 @@ add_css_file <- function( #' @export #' @rdname add_files #' @importFrom glue glue +#' @importFrom fs path_abs file_create add_ui_server_files <- function( pkg = get_golem_wd(), dir = "inst/app", @@ -215,7 +214,7 @@ add_ui_server_files <- function( ){ #browser() - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_dir_if_needed( @@ -230,15 +229,13 @@ add_ui_server_files <- function( return(invisible(FALSE)) } - dir <- normalizePath(dir) + dir <- path_abs(dir) # UI - where <- file.path( - dir, "ui.R" - ) - if ( !check_file_exist(where) ) return(invisible(FALSE)) + where <- path( dir, "ui.R") + + file_create(where) - file.create(where) write_there <- function(...) write(..., file = where, append = TRUE) if (is.null(getOption('golem.pkg.name'))){ @@ -257,9 +254,9 @@ add_ui_server_files <- function( where <- file.path( dir, "server.R" ) - if ( !check_file_exist(where) ) return(invisible(FALSE)) - file.create(where) + file_create(where) + write_there <- function(...) write(..., file = where, append = TRUE) write_there( @@ -268,7 +265,6 @@ add_ui_server_files <- function( pkg ) ) - cat_green_tick(glue("server file created at {where}")) } diff --git a/R/add_modules.R b/R/add_modules.R index a625f9af..9a648da7 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -17,6 +17,7 @@ #' @importFrom cli cat_bullet #' @importFrom utils file.edit #' @importFrom tools file_path_sans_ext +#' @importFrom fs path_abs path file_create add_module <- function( name, pkg = get_golem_wd(), @@ -29,7 +30,7 @@ add_module <- function( name <- file_path_sans_ext(name) - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_dir_if_needed( @@ -52,13 +53,12 @@ add_module <- function( add_utils(utils, module = name, open = open) } - where <- file.path( + where <- path( "R", paste0("mod_", name, ".R") ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - file.create(where) + + 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 5c1c4b0b..a4e636ed 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -1,4 +1,5 @@ #' @importFrom tools file_path_sans_ext +#' @importFrom fs path_abs path file_create add_r_files <- function( name, ext = c("fct", "utils"), @@ -10,7 +11,7 @@ add_r_files <- function( name <- file_path_sans_ext(name) - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_dir_if_needed( @@ -26,15 +27,11 @@ add_r_files <- function( if (!is.null(module)){ module <- paste0("mod_", module, "_") } - where <- file.path( + where <- path( "R", paste0(module, ext, "_", name, ".R") ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - - file.create(where) + file_create(where) if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) diff --git a/R/add_ressource_path.R b/R/add_ressource_path.R index 864b5535..8ec68a5b 100644 --- a/R/add_ressource_path.R +++ b/R/add_ressource_path.R @@ -9,16 +9,18 @@ #' #' @export #' -add_resource_path <- function(prefix, - directory_path, - warn_empty = FALSE){ - +add_resource_path <- function( + prefix, + directory_path, + warn_empty = FALSE +){ + list_f <- is_empty(list.files(path = directory_path)) if ( list_f & warn_empty ) { message("Unable to add your directory because it is empty") } else { addResourcePath(prefix, directory_path) -message("Resource path added to ", prefix, "/", directory_path) + message("Resource path added to ", prefix, "/", directory_path) } } diff --git a/R/addins.R b/R/addins.R index 91025200..6daf2d08 100644 --- a/R/addins.R +++ b/R/addins.R @@ -28,15 +28,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 <- file.path( + file <- path( wd, file ) - if (! file.exists(file)){ + if (! file_exists(file)){ message(file, "not found.") } diff --git a/R/config.R b/R/config.R index c9a50ca0..1e6b50d2 100644 --- a/R/config.R +++ b/R/config.R @@ -1,36 +1,37 @@ #' @importFrom attempt attempt is_try_error #' @importFrom pkgload pkg_path +#' @importFrom fs path path_abs guess_where_config <- function( path = "." ){ # Trying the path - ret_path <- file.path( + ret_path <- path( path, "inst/golem-config.yml" ) - if (file.exists(ret_path)) return(normalizePath(ret_path)) + 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(normalizePath(ret_path)) + if (file_exists(ret_path)) return(path_abs(ret_path)) # Trying with pkgpath ret_path <- attempt({ - file.path( + path( pkg_path(), "inst/golem-config.yml" ) }) if ( !is_try_error(ret_path) & - file.exists(ret_path) + file_exists(ret_path) ) { return( - normalizePath(ret_path) + path_abs(ret_path) ) } return(NULL) } #' @importFrom yesno yesno -#' @importFrom fs file_copy +#' @importFrom fs file_copy path #' @importFrom pkgload pkg_name get_current_config <- function( path = ".", @@ -41,12 +42,12 @@ get_current_config <- function( path_conf <- guess_where_config(path) # We default to inst/ if this doesn't exist if (is.null(path_conf)){ - path_conf <- file.path( + path_conf <- path( path, "inst/golem-config.yml" ) } - if (!file.exists(path_conf)){ + if (!file_exists(path_conf)){ ask <- yesno( sprintf( "The %s file doesn't exist, create?", @@ -58,7 +59,7 @@ get_current_config <- function( file_copy( path = golem_sys("shinyexample/inst/golem-config.yml"), - new_path = file.path( + new_path = path( path, "inst/golem-config.yml" ) ) @@ -69,7 +70,7 @@ get_current_config <- function( ) ) replace_word( - file.path( + path( path, "R/app_config.R" ), "shinyexample", diff --git a/R/create_golem.R b/R/create_golem.R index 59f56013..41d1aeec 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -11,11 +11,10 @@ #' @param ... not used #' #' @importFrom yesno yesno -#' @importFrom cli cat_rule -#' @importFrom cli cat_line +#' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace -#' @importFrom rstudioapi isAvailable -#' @importFrom rstudioapi openProject +#' @importFrom rstudioapi isAvailable openProject +#' @importFrom fs path_abs path_file path file_move #' @export create_golem <- function( path, @@ -25,9 +24,9 @@ create_golem <- function( without_comments = FALSE, ... ) { - - if (path == '.' & package_name == basename(path)){ - package_name <- basename(getwd()) + + if (path == '.' & package_name == path_file(path)){ + package_name <- path_file(getwd()) } if (check_name){ @@ -36,7 +35,7 @@ create_golem <- function( cat_green_tick("Valid package name") } - if (dir.exists(path)){ + if (dir_exists(path)){ res <- yesno::yesno( paste("The path", path, "already exists, override?") ) @@ -46,14 +45,27 @@ create_golem <- function( } cat_rule("Creating dir") - dir.create(path, recursive = TRUE, showWarnings = FALSE) + dir_create( + path, + recurse = TRUE + ) cat_green_tick("Created package directory") cat_rule("Copying package skeleton") from <- golem_sys("shinyexample") - ll <- list.files(path = from, full.names = TRUE, all.files = TRUE,no.. = TRUE) + ll <- list.files( + path = from, + full.names = TRUE, + all.files = TRUE, + no.. = TRUE + ) # remove `..` - file.copy(from = ll, to = path, overwrite = TRUE, recursive = TRUE) + file.copy( + ll, + path, + overwrite = TRUE, + recursive = TRUE + ) t1 <- list.files( path, @@ -71,9 +83,9 @@ create_golem <- function( for ( i in t ){ - file.rename( - from = i, - to = gsub("REMOVEME", "", i) + file_move( + path = i, + new_path = gsub("REMOVEME", "", i) ) try({ @@ -83,12 +95,13 @@ create_golem <- function( replace = package_name ) }, - silent=TRUE) + silent = TRUE + ) } cat_green_tick("Copied app skeleton") cat_rule("Setting the default config") - yml_path <- file.path(path, "inst/golem-config.yml") + yml_path <- path(path, "inst/golem-config.yml") conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) @@ -104,8 +117,8 @@ create_golem <- function( if ( without_comments == TRUE ) { files <- list.files( path = c( - file.path(path, "dev"), - file.path(path, "R") + path(path, "dev"), + path(path, "R") ), full.names = TRUE ) @@ -121,7 +134,7 @@ create_golem <- function( "A new golem named ", package_name, " was created at ", - normalizePath(path), + path_abs(path), " .\n", "To continue working on your app, start editing the 01_start.R file." ) @@ -133,7 +146,7 @@ create_golem <- function( return( invisible( - normalizePath(path) + path_abs(path) ) ) } diff --git a/R/desc.R b/R/desc.R index 8658f51b..47d01705 100644 --- a/R/desc.R +++ b/R/desc.R @@ -12,6 +12,7 @@ #' @importFrom desc description #' @importFrom glue glue #' @importFrom cli cat_bullet +#' @importFrom fs path path_abs #' @export fill_desc <- function( pkg_name, @@ -24,10 +25,10 @@ fill_desc <- function( pkg = get_golem_wd() ){ - path <- normalizePath(pkg) + path <- path_abs(pkg) desc <- desc::description$new( - file = file.path(path, "DESCRIPTION") + file = path(path, "DESCRIPTION") ) desc$set( "Authors@R", @@ -75,10 +76,3 @@ fill_desc <- function( ) } - -if_not_null <- function(x, ...){ - if (! is.null(x)){ - force(...) - } -} - diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index f60807ad..dc0b5a6c 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -13,34 +13,46 @@ #' @export get_sysreqs <- function(packages, quiet = TRUE,batch_n=30){ - # all_deps <- paste(miniCRAN::pkgDep(packages,suggests = FALSE,quiet=quiet), collapse = ",") - # all_deps <- paste(unique(c(packages, unlist(miniCRAN::pkgDep(packages, suggests = FALSE)))), collapse = ",") - all_deps <- sort(unique(c(packages, unlist(remotes::package_deps(packages)$package)))) - all_deps - - split(all_deps, ceiling(seq_along(all_deps)/batch_n)) %>% - map(~get_batch_sysreqs(.x,quiet=quiet)) %>% + all_deps <- sort( + unique( + c(packages, unlist( remotes::package_deps(packages)$package ) + ) + ) + ) + + split( + all_deps, + ceiling( + seq_along(all_deps) / batch_n + ) + ) %>% + map( ~ get_batch_sysreqs(.x, quiet = quiet)) %>% unlist() %>% unname() %>% unique() %>% sort() - + } - -get_batch_sysreqs <- function(all_deps,quiet=TRUE){ - - all_deps<-paste(all_deps , collapse = ",") - - - url <- sprintf("https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc",all_deps) - path <- tempfile() - utils::download.file(url, path,mode = "wb",quiet = quiet) +#' @importFrom fs file_delete file_temp +get_batch_sysreqs <- function( + all_deps, + quiet=TRUE +){ + + url <- sprintf( + "https://sysreqs.r-hub.io/pkg/%s/linux-x86_64-debian-gcc", + paste(all_deps, collapse = ",") + ) + path <- file_temp() + utils::download.file( + url, + path, + mode = "wb", + quiet = quiet + ) out <- jsonlite::fromJSON(path) - unlink(path) - - - + file_delete(path) unique(out[!is.na(out)]) } diff --git a/R/options.R b/R/options.R index d8a49d7d..c2696e18 100644 --- a/R/options.R +++ b/R/options.R @@ -163,11 +163,12 @@ set_golem_things <- function( #' @export #' @rdname golem_opts +#' @importFrom fs path_abs set_golem_wd <- function( path = pkgload::pkg_path(), talkative = TRUE ){ - path <- normalizePath(path, winslash = "/") + path <- path_abs(path) # Setting wd if (path == here::here()){ @@ -189,12 +190,13 @@ set_golem_wd <- function( #' @export #' @rdname golem_opts +#' @importFrom fs path_abs set_golem_name <- function( name = pkgload::pkg_name(), path = pkgload::pkg_path(), talkative = TRUE ){ - path <- normalizePath(path, winslash = "/") + path <- path_abs(path) set_golem_things( "golem_name", name, @@ -208,12 +210,13 @@ set_golem_name <- function( #' @export #' @rdname golem_opts +#' @importFrom fs path_abs set_golem_version <- function( version = pkgload::pkg_version(), path = pkgload::pkg_path(), talkative = TRUE ){ - path <- normalizePath(path, winslash = "/") + path <- path_abs(path) set_golem_things( "golem_version", as.character(version), diff --git a/R/use_favicon.R b/R/use_favicon.R index a4954b56..f1444dcf 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -6,6 +6,7 @@ #' @export #' #' @importFrom attempt stop_if_not +#' @importFrom fs path_abs path file_copy #' #' @examples #' \donttest{ @@ -14,28 +15,34 @@ #' use_favicon(path='path/to/your/favicon.ico') #' } #' } -use_favicon <- function(path, pkg = get_golem_wd()){ +use_favicon <- function( + path, + pkg = get_golem_wd() +){ if (missing(path)){ path <- golem_sys("shinyexample/inst/app/www", "favicon.ico") } ext <- tools::file_ext(path) - stop_if_not(ext, ~ .x %in% c("png",'ico'), - "favicon must have .ico or .png extension") + stop_if_not( + ext, + ~ .x %in% c("png",'ico'), + "favicon must have .ico or .png extension" + ) - path <- normalizePath(path) + path <- path_abs(path) - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - to <- file.path(normalizePath(pkg), "inst/app/www", glue::glue("favicon.{ext}")) + to <- path(path_abs(pkg), "inst/app/www", glue::glue("favicon.{ext}")) if (! (path == to)) { - file.copy( - overwrite = TRUE, + file_copy( path, - to + to, + overwrite = TRUE ) cat_green_tick(glue::glue("favicon.{ext} created at {to}")) } @@ -47,18 +54,19 @@ use_favicon <- function(path, pkg = get_golem_wd()){ } #' @rdname favicon -#' @export +#' @export +#' @importFrom fs file_delete file_exists remove_favicon <- function( path = "inst/app/www/favicon.ico" ){ - if (file.exists(path)){ + if (file_exists(path)){ cat_green_tick( sprintf( "Removing favicon at %s", path ) ) - unlink(path) + file_delete(path) } else { cat_red_bullet( sprintf( diff --git a/R/use_files.R b/R/use_files.R index b0aebfa8..fc33dda6 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -9,7 +9,7 @@ #' @rdname use_files #' @importFrom glue glue #' @importFrom cli cat_bullet - +#' @importFrom fs path_abs path use_external_js_file <- function( url, name, @@ -19,7 +19,7 @@ use_external_js_file <- function( dir_create = TRUE ){ - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) new_file <- glue::glue("{name}.js") @@ -35,9 +35,9 @@ use_external_js_file <- function( return(invisible(FALSE)) } - dir <- normalizePath(dir) + dir <- path_abs(dir) - where <- file.path( + where <- path( dir, new_file ) if ( !check_file_exist(where) ) { @@ -69,6 +69,7 @@ use_external_js_file <- function( #' @export #' @rdname use_files +#' @importFrom fs path_abs use_external_css_file <- function( url, name, @@ -78,7 +79,7 @@ use_external_css_file <- function( dir_create = TRUE ){ - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) new_file <- glue::glue("{name}.css") @@ -94,9 +95,9 @@ use_external_css_file <- function( return(invisible(FALSE)) } - dir <- normalizePath(dir) + dir <- path_abs(dir) - where <- file.path( + where <- path( dir, new_file ) if ( !check_file_exist(where) ) { diff --git a/R/use_recommended.R b/R/use_recommended.R index 523de329..cb51eff8 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -9,6 +9,7 @@ #' @param recommended A vector of recommended packages. #' #' @importFrom usethis use_testthat use_package +#' @importFrom fs path_abs #' @rdname use_recommended #' #' @export @@ -16,7 +17,7 @@ use_recommended_deps <- function( pkg = get_golem_wd(), recommended = c("shiny","DT","attempt","glue","htmltools","golem") ){ - old <- setwd(normalizePath(pkg)) + old <- setwd(path_abs(pkg)) on.exit(setwd(old)) for ( i in sort(recommended)){ try(use_package(i)) @@ -30,20 +31,24 @@ use_recommended_deps <- function( #' @importFrom usethis use_testthat use_package #' @importFrom utils capture.output #' @importFrom attempt without_warning -use_recommended_tests <- function -(pkg = get_golem_wd() - ){ - old <- setwd(normalizePath(pkg)) +#' @importFrom fs path_abs path +use_recommended_tests <- function ( + pkg = get_golem_wd() +){ + old <- setwd(path_abs(pkg)) + on.exit(setwd(old)) + if (!dir.exists( - file.path(normalizePath(pkg), "tests") + path(path_abs(pkg), "tests") )){ without_warning(use_testthat)() } - x <- capture.output(use_package("processx")) - file.copy( + capture.output(use_package("processx")) + + file_copy( golem_sys("utils", "test-golem-recommended.R"), - file.path(normalizePath(pkg), "tests", "testthat") + path(old, "tests", "testthat") ) cat_green_tick("Tests added") } diff --git a/R/use_utils.R b/R/use_utils.R index 9a8ad651..6bc76fb4 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -13,33 +13,46 @@ #' @importFrom cli cat_bullet #' @importFrom glue glue #' @importFrom utils capture.output -use_utils_ui <- function(pkg = get_golem_wd()){ - use_utils(file_name = "golem_utils_ui.R", pkg=pkg) +use_utils_ui <- function( + pkg = get_golem_wd() +){ + use_utils( + file_name = "golem_utils_ui.R", + pkg = pkg + ) capture.output( usethis::use_package("htmltools") ) cat_green_tick("Utils UI added") - # automatiquement dans le fichier deplace } #' @export #' @rdname utils_files -use_utils_server <- function(pkg = get_golem_wd()){ - use_utils(file_name = "golem_utils_server.R", pkg=pkg) - cat_bullet("Utils server added", bullet = "tick", bullet_col = "green") +use_utils_server <- function( + pkg = get_golem_wd() +){ + use_utils( + file_name = "golem_utils_server.R", + pkg = pkg + ) + cat_green_tick("Utils server added") } -use_utils <- function(file_name, pkg = get_golem_wd()){ - old <- setwd(normalizePath(pkg)) - on.exit(setwd(old)) - where <- file.path(normalizePath(pkg), "R", file_name) +#' @importFrom fs file_copy path_abs +use_utils <- function( + file_name, + pkg = get_golem_wd() +){ + old <- setwd( + path_abs(pkg) + ) + on.exit( setwd(old) ) + where <- path(path_abs(pkg), "R", file_name) if ( !check_file_exist(where) ) { return(invisible(FALSE)) } - file.copy( - from = golem_sys("utils", file_name), - to = where + file_copy( + path = golem_sys("utils", file_name), + new_path = where ) - cat_bullet(glue::glue("File created at {where}"), bullet = "tick", bullet_col = "green") + cat_green_tick(glue::glue("File created at {where}")) } - -# "utils_server.R" diff --git a/R/utils.R b/R/utils.R index 1a07a64f..de1f748d 100644 --- a/R/utils.R +++ b/R/utils.R @@ -16,16 +16,18 @@ darkgrey <- function(x) { x <- crayon::make_style("darkgrey")(x) } -dir_not_exist <- Negate(dir.exists) -file_not_exist <- Negate(file.exists) +#' @importFrom fs dir_exists file_exists +dir_not_exist <- Negate(dir_exists) +file_not_exist <- Negate(file_exists) -is_package <- function(path){ - x <- attempt::attempt({ - pkgload::pkg_path() - }) - !attempt::is_try_error(x) -} +# is_package <- function(path){ +# x <- attempt::attempt({ +# pkgload::pkg_path() +# }) +# !attempt::is_try_error(x) +# } +#' @importFrom fs dir_create file_create create_if_needed <- function( path, type = c("file", "directory"), @@ -57,16 +59,17 @@ create_if_needed <- function( # Create the file if (type == "file"){ - fs::file_create(path) + file_create(path) write(content, path, append = TRUE) } else if (type == "directory"){ - fs::dir_create(path, recurse = TRUE) + dir_create(path, recurse = TRUE) } # TRUE means that file exists (either # created or already there) return(TRUE) } +#' @importFrom fs dir_create create_dir_if_needed <- function( path, auto_create @@ -85,7 +88,7 @@ create_dir_if_needed <- function( } # Will create if autocreate or if yes to interactive if (go_create) { - dir.create(path, recursive = TRUE) + dir_create(path, recursive = TRUE) cat_green_tick( sprintf( "Created folder %s to receive the file", @@ -98,16 +101,19 @@ create_dir_if_needed <- function( return(go_create) } +#' @importFrom fs file_exists check_file_exist <- function(file){ res <- TRUE - if (file.exists(file)){ + if (file_exists(file)){ res <- yesno::yesno("This file already exists, override?") } return(res) } + +#' @importFrom fs dir_exists check_dir_exist <- function(dir){ res <- TRUE - if (!dir.exists(dir)){ + if (!dir_exists(dir)){ res <- yesno::yesno(sprintf("The %s does not exists, create?", dir)) } return(res) @@ -133,6 +139,8 @@ remove_comments <- function(file) { writeLines(text = lines_without_comment, con = file) } +#' @importFrom cli cat_bullet + cat_green_tick <- function(...){ cat_bullet( ..., @@ -141,6 +149,7 @@ cat_green_tick <- function(...){ ) } +#' @importFrom cli cat_bullet cat_red_bullet <- function(...){ cat_bullet( ..., @@ -148,3 +157,9 @@ cat_red_bullet <- function(...){ bullet_col = "red" ) } + +if_not_null <- function(x, ...){ + if (! is.null(x)){ + force(...) + } +} diff --git a/inst/manualtests/script.R b/inst/manualtests/script.R new file mode 100644 index 00000000..bd752810 --- /dev/null +++ b/inst/manualtests/script.R @@ -0,0 +1,47 @@ +setwd("/tmp") +golem::create_golem( "gogolele",open = FALSE) +setwd("/tmp/gogolele/") +golem::set_golem_options() +golem::fill_desc( + pkg_name = "gogolele", + pkg_title = "PKG_TITLE", + pkg_description = "PKG_DESC.", + author_first_name = "AUTHOR_FIRST", + author_last_name = "AUTHOR_LAST", + author_email = "AUTHOR@MAIL.COM", + repo_url = NULL +) +golem::set_golem_options() +usethis::use_mit_license( name = "Golem User" ) +usethis::use_readme_rmd( open = FALSE ) +usethis::use_code_of_conduct() +usethis::use_lifecycle_badge( "Experimental" ) +usethis::use_news_md( open = FALSE ) +usethis::use_data_raw( name = "my_dataset", open = FALSE ) +golem::use_recommended_tests() +golem::use_recommended_deps() +golem::use_utils_ui() +golem::use_utils_server() +devtools::test() + +golem::add_module( name = "my_first_module" ) +golem::add_module( name = "my_other_module" ) + +ui <- readLines("R/app_ui.R") +ui[12] <- 'mod_my_first_module_ui("mod_my_first_module_ui_1"),\nmod_my_other_module_ui("my_other_module_ui_1")' +write(ui, "R/app_ui.R") + +srv <- readLines("R/app_server.R") +srv[3] <- 'callModule(mod_my_first_module_server, "my_first_module_ui_1")\ncallModule(mod_my_other_module_server, "my_other_module_ui_1")' +write(srv, "R/app_server.R") + +usethis::use_package( "thinkr" ) + +golem::add_js_file( "script", open = FALSE) +golem::add_js_handler( "handlers", open = FALSE) +golem::add_css_file( "custom", open = FALSE) + +devtools::test() +remotes::install_local() + + diff --git a/inst/manualtests/tests.sh b/inst/manualtests/tests.sh new file mode 100644 index 00000000..2d7748e1 --- /dev/null +++ b/inst/manualtests/tests.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +R -e 'devtools::build("../../", path = ".")' + +docker build -t golemmantest . + +docker run golemmantest + +rm golem_*tar.gz From fa50c268536c564ca669235265babc0630b69bf8 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 29 Nov 2019 08:31:49 +0100 Subject: [PATCH 163/211] renamed french word --- R/add_deploy_helpers.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index c1c0d225..2db5f18b 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -525,15 +525,15 @@ dock_from_desc <- function( if ( !build_golem_from_source){ if ( update_tar_gz ){ - ancienne_version <- list.files( + old_version <- list.files( pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"), full.names = TRUE ) - if (length(ancienne_version) > 0){ - cat_red_bullet(glue::glue("We remove {paste(ancienne_version,collapse = ", ")} from folder")) - lapply(ancienne_version,file.remove) - lapply(ancienne_version,unlink,force=TRUE) + if ( length(old_version) > 0){ + lapply(old_version, file.remove) + lapply(old_version, unlink, force = TRUE) + cat_red_bullet(glue::glue("{paste(old_version,collapse = ', ')} were removed from folder")) } cat_green_tick(glue::glue(" {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz created.")) From 322e06d57e9ed2b7b608f108609b4ba17e185eb4 Mon Sep 17 00:00:00 2001 From: cervangirard Date: Fri, 20 Dec 2019 15:04:26 +0100 Subject: [PATCH 164/211] Add video resource --- README.Rmd | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.Rmd b/README.Rmd index 30a776e4..fb1a935e 100644 --- a/README.Rmd +++ b/README.Rmd @@ -62,6 +62,7 @@ _Building Big Shiny Apps_ - [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) - [Hands-on demonstration of {golem}](https://shinydevseries.com/post/golem-demo/) - 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) ### Cheatsheet diff --git a/README.md b/README.md index 4fdd5a25..3d1c54ec 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ This package is part of a series of tools for Shiny, which includes: {golem}](https://shinydevseries.com/post/golem-demo/) - useR\! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://youtu.be/tCAan6smrjs) + - 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) ### Cheatsheet From 1a5535e6b732458a64f99e1199d724ad78930767 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 26 Dec 2019 21:52:25 +0100 Subject: [PATCH 165/211] close #285 --- R/add_deploy_helpers.R | 16 +++---- R/add_files.R | 32 ++++++-------- R/add_modules.R | 11 +++-- R/add_r_files.R | 6 +-- R/use_files.R | 28 ++++++------ R/use_utils.R | 41 ++++++++++-------- R/utils.R | 85 ++++++++++++++++++------------------- inst/utils/golem_utils_ui.R | 63 +++++++++++++++------------ man/dock_from_desc.Rd | 33 -------------- 9 files changed, 141 insertions(+), 174 deletions(-) delete mode 100644 man/dock_from_desc.Rd diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 2db5f18b..5a57f1aa 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -32,7 +32,7 @@ add_rstudio_files <- function( ) #use_build_ignore(where) x <- capture.output(use_package("pkgload")) - cat_green_tick(glue("File created at {where}")) + cat_created(where) cat_line("To deploy, run:") cat_bullet(darkgrey("rsconnect::deployApp()\n")) cat_red_bullet( @@ -146,7 +146,6 @@ add_shinyserver_file <- function( #' add_dockerfile_heroku() #' } #'} - add_dockerfile <- function( path = "DESCRIPTION", output = "Dockerfile", @@ -169,7 +168,7 @@ add_dockerfile <- function( where <- path(pkg, output) - if ( !check_file_exist(where) ) return(invisible(FALSE)) + #if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(path_file(where)) @@ -232,7 +231,7 @@ add_dockerfile_shinyproxy <- function( where <- path(pkg, output) - if ( !check_file_exist(where) ) return(invisible(FALSE)) + #if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(output) @@ -292,7 +291,7 @@ add_dockerfile_heroku <- function( ){ where <- path(pkg, output) - if ( !check_file_exist(where) ) return(invisible(FALSE)) + #if ( !check_file_exist(where) ) return(invisible(FALSE)) usethis::use_build_ignore(output) @@ -356,10 +355,9 @@ add_dockerfile_heroku <- function( } #' @importFrom glue glue -alert_build <- function(path, output ,build_golem_from_source){ - cat_green_tick( - glue("Dockerfile created at {output}") - ) +alert_build <- function(path, output, build_golem_from_source){ + + cat_created(output, "Dockerfile") if ( ! build_golem_from_source ){ cat_red_bullet( glue::glue( diff --git a/R/add_files.R b/R/add_files.R index 8cd1fec2..e4408614 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -31,9 +31,8 @@ add_js_file <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - dir_created <- create_dir_if_needed( - dir, - dir_create + dir_created <- create_if_needed( + dir, type = "directory" ) if (!dir_created){ @@ -60,9 +59,7 @@ add_js_file <- function( write_there("});") } - cat_green_tick( - glue::glue("File created at {where}") - ) + cat_created(where) if (file_exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( @@ -98,9 +95,8 @@ add_js_handler <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - dir_created <- create_dir_if_needed( - dir, - dir_create + dir_created <- create_if_needed( + dir, type = "directory" ) if (!dir_created){ @@ -130,7 +126,7 @@ add_js_handler <- function( write_there(" })") write_there("});") - cat_green_tick(glue::glue("File created at {where}")) + cat_created(where) if (file_exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( @@ -166,9 +162,8 @@ add_css_file <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - dir_created <- create_dir_if_needed( - dir, - dir_create + dir_created <- create_if_needed( + dir, type = "directory" ) if (!dir_created){ @@ -186,7 +181,7 @@ add_css_file <- function( file_create(where) - cat_green_tick(glue::glue("File created at {where}")) + cat_created(where) if (file_exists(paste0(pkg, "/DESCRIPTION"))) { cat_red_bullet( @@ -217,9 +212,8 @@ add_ui_server_files <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - dir_created <- create_dir_if_needed( - dir, - dir_create + dir_created <- create_if_needed( + dir, type = "directory" ) if (!dir_created){ @@ -248,7 +242,7 @@ add_ui_server_files <- function( sprintf( "%s:::app_ui()", pkg ) ) - cat_green_tick(glue("ui file created at {where}")) + cat_created(where, "ui file") # server where <- file.path( @@ -265,6 +259,6 @@ add_ui_server_files <- function( pkg ) ) - cat_green_tick(glue("server file created at {where}")) + cat_created(where, "server file") } diff --git a/R/add_modules.R b/R/add_modules.R index 9a648da7..4ac76af6 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -33,9 +33,8 @@ add_module <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - dir_created <- create_dir_if_needed( - "R", - dir_create + dir_created <- create_if_needed( + path(pkg, "R"), type = "directory" ) if (!dir_created){ @@ -56,7 +55,7 @@ add_module <- function( where <- path( "R", paste0("mod_", name, ".R") ) - + file_create(where) write_there <- function(...){ @@ -65,7 +64,7 @@ add_module <- function( glue <- function(...){ glue::glue(..., .open = "%", .close = "%") } - + write_there(glue("#' %name% UI Function")) write_there("#'") write_there("#' @description A shiny Module.") @@ -108,7 +107,7 @@ add_module <- function( write_there("## To be copied in the server") write_there(glue('# callModule(mod_%name%_server, "%name%_ui_1")')) write_there(" ") - cat_green_tick(glue("File created at %where%")) + cat_created(where) if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) } else { diff --git a/R/add_r_files.R b/R/add_r_files.R index a4e636ed..4989f08c 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -14,10 +14,10 @@ add_r_files <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - dir_created <- create_dir_if_needed( - "R", - dir_create + dir_created <- create_if_needed( + "R", type = "directory" ) + if (!dir_created){ cat_red_bullet( "File not added (needs a valid directory)" diff --git a/R/use_files.R b/R/use_files.R index fc33dda6..75fce7e4 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -23,9 +23,8 @@ use_external_js_file <- function( on.exit(setwd(old)) new_file <- glue::glue("{name}.js") - dir_created <- create_dir_if_needed( - dir, - dir_create + dir_created <- create_if_needed( + dir, type = "directory" ) if (!dir_created){ @@ -40,9 +39,9 @@ use_external_js_file <- function( where <- path( dir, new_file ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } + # if ( !check_file_exist(where) ) { + # return(invisible(FALSE)) + # } if ( tools::file_ext(url) != "js") { cat_red_bullet( @@ -53,7 +52,7 @@ use_external_js_file <- function( utils::download.file(url, where) - cat_green_tick(glue::glue("File created at {where}")) + cat_created(where) cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' @@ -82,10 +81,9 @@ use_external_css_file <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) new_file <- glue::glue("{name}.css") - - dir_created <- create_dir_if_needed( - dir, - dir_create + + dir_created <- create_if_needed( + dir, type = "directory" ) if (!dir_created){ @@ -100,9 +98,9 @@ use_external_css_file <- function( where <- path( dir, new_file ) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } + # if ( !check_file_exist(where) ) { + # return(invisible(FALSE)) + # } if ( tools::file_ext(url) != "css") { cat_red_bullet( @@ -113,7 +111,7 @@ use_external_css_file <- function( utils::download.file(url, where) - cat_green_tick(glue::glue("File created at {where}")) + cat_created(where) cat_red_bullet( glue::glue( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' diff --git a/R/use_utils.R b/R/use_utils.R index 6bc76fb4..82d5ea71 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -16,28 +16,31 @@ use_utils_ui <- function( pkg = get_golem_wd() ){ - use_utils( + added <- use_utils( file_name = "golem_utils_ui.R", pkg = pkg ) - capture.output( - usethis::use_package("htmltools") - ) - cat_green_tick("Utils UI added") + usethis::use_package("htmltools") + + if (added){ + cat_green_tick("Utils UI added") + } } #' @export #' @rdname utils_files use_utils_server <- function( pkg = get_golem_wd() ){ - use_utils( + added <- use_utils( file_name = "golem_utils_server.R", pkg = pkg ) - cat_green_tick("Utils server added") + if (added){ + cat_green_tick("Utils server added") + } } -#' @importFrom fs file_copy path_abs +#' @importFrom fs file_copy path_abs path_file use_utils <- function( file_name, pkg = get_golem_wd() @@ -47,12 +50,16 @@ use_utils <- function( ) on.exit( setwd(old) ) where <- path(path_abs(pkg), "R", file_name) - if ( !check_file_exist(where) ) { - return(invisible(FALSE)) - } - file_copy( - path = golem_sys("utils", file_name), - new_path = where - ) - cat_green_tick(glue::glue("File created at {where}")) -} + if (file_exists(where)){ + cat_exists(where) + return(FALSE) + } else { + file_copy( + path = golem_sys("utils", file_name), + new_path = where + ) + cat_created(where) + return(TRUE) + } + +} \ No newline at end of file diff --git a/R/utils.R b/R/utils.R index de1f748d..64af94bb 100644 --- a/R/utils.R +++ b/R/utils.R @@ -51,56 +51,24 @@ create_if_needed <- function( ) ) # Return early if the user doesn't allow - if (!ask) return(FALSE) - - } else { - return(TRUE) - } + if (!ask) { + return(FALSE) + } else { + # Create the file + if (type == "file"){ + file_create(path) + write(content, path, append = TRUE) + } else if (type == "directory"){ + dir_create(path, recurse = TRUE) + } + } + } - # Create the file - if (type == "file"){ - file_create(path) - write(content, path, append = TRUE) - } else if (type == "directory"){ - dir_create(path, recurse = TRUE) - } # TRUE means that file exists (either # created or already there) return(TRUE) } -#' @importFrom fs dir_create -create_dir_if_needed <- function( - path, - auto_create -){ - # TRUE if path doesn't exist - dir_not_there <- dir_not_exist(path) - go_create <- TRUE - # If not exists, maybe create it - if (dir_not_there){ - # Auto create if needed - if (auto_create){ - go_create <- TRUE - } else { - # Ask for creation - go_create <- yesno::yesno(sprintf("The %s does not exists, create?", path)) - } - # Will create if autocreate or if yes to interactive - if (go_create) { - dir_create(path, recursive = TRUE) - cat_green_tick( - sprintf( - "Created folder %s to receive the file", - path - ) - ) - } - } - - return(go_create) -} - #' @importFrom fs file_exists check_file_exist <- function(file){ res <- TRUE @@ -110,6 +78,7 @@ check_file_exist <- function(file){ return(res) } +# TODO Remove from codebase #' @importFrom fs dir_exists check_dir_exist <- function(dir){ res <- TRUE @@ -158,6 +127,34 @@ cat_red_bullet <- function(...){ ) } +#' @importFrom cli cat_bullet +cat_info <- function(...){ + cat_bullet( + ..., + bullet = "arrow_right", + bullet_col = "grey" + ) +} + +cat_exists <- function(where){ + cat_red_bullet( + sprintf( + "%s already exists, skipping the copy.", + path_file(where) + ) + ) + cat_info( + sprintf( + "If you want replace it, remove the %s file first.", + path_file(where) + ) + ) +} + +cat_created <- function(where, file = "File"){ + cat_green_tick(glue::glue("{file} created at {where}")) +} + if_not_null <- function(x, ...){ if (! is.null(x)){ force(...) diff --git a/inst/utils/golem_utils_ui.R b/inst/utils/golem_utils_ui.R index 70bda507..973b9ccb 100644 --- a/inst/utils/golem_utils_ui.R +++ b/inst/utils/golem_utils_ui.R @@ -210,31 +210,38 @@ col_1 <- function(...){ column(1, ...) } -#' Include Content From a File -#' -#' Load rendered RMarkdown from a file and turn into HTML. -#' -#' @rdname includeRMarkdown -#' @export -#' -#' @importFrom rmarkdown render -#' @importFrom markdown markdownToHTML -#' @importFrom htmltools HTML -includeRMarkdown <- function(path){ - - md <- tempfile(fileext = '.md') - - on.exit(unlink(md),add = TRUE) - - rmarkdown::render( - path, - output_format = 'md_document', - output_dir = tempdir(), - output_file = md,quiet = TRUE) - - html <- markdown::markdownToHTML(md, fragment.only = TRUE) - - Encoding(html) <- "UTF-8" - - return(HTML(html)) -} +# UNCOMMENT AND USE +# +# usethis::use_package("markdown") +# usethis::use_package("rmarkdown") +# +# To use this part of the UI +# +#' #' Include Content From a File +#' #' +#' #' Load rendered RMarkdown from a file and turn into HTML. +#' #' +#' #' @rdname includeRMarkdown +#' #' @export +#' #' +#' #' @importFrom rmarkdown render +#' #' @importFrom markdown markdownToHTML +#' #' @importFrom htmltools HTML +#' includeRMarkdown <- function(path){ +#' +#' md <- tempfile(fileext = '.md') +#' +#' on.exit(unlink(md),add = TRUE) +#' +#' rmarkdown::render( +#' path, +#' output_format = 'md_document', +#' output_dir = tempdir(), +#' output_file = md,quiet = TRUE) +#' +#' html <- markdown::markdownToHTML(md, fragment.only = TRUE) +#' +#' Encoding(html) <- "UTF-8" +#' +#' return(HTML(html)) +#' } diff --git a/man/dock_from_desc.Rd b/man/dock_from_desc.Rd deleted file mode 100644 index 21b0f8f3..00000000 --- a/man/dock_from_desc.Rd +++ /dev/null @@ -1,33 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_deploy_helpers.R -\name{dock_from_desc} -\alias{dock_from_desc} -\title{Create Dockerfile from DESCRIPTION} -\usage{ -dock_from_desc(path = "DESCRIPTION", FROM = paste0("rocker/r-ver:", - R.Version()$major, ".", R.Version()$minor), AS = NULL, - sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, update_tar_gz = TRUE, - build_golem_from_source = TRUE) -} -\arguments{ -\item{path}{path to the DESCRIPTION file to use as an input.} - -\item{FROM}{The FROM of the Dockerfile. Default is FROM rocker/r-ver: -with \code{R.Version()$major} and \code{R.Version()$minor}.} - -\item{AS}{The AS of the Dockerfile. Default it NULL.} - -\item{sysreqs}{boolean to check the system requirements} - -\item{repos}{character vector, the base URL of the repositories} - -\item{expand}{boolean, if \code{TRUE} each system requirement will be known his own RUN line} - -\item{update_tar_gz}{boolean, if \code{TRUE} and build_golem_from_source is also \code{TRUE} an updated tar.gz Package is created} - -\item{build_golem_from_source}{boolean, if \code{TRUE} no tar.gz Package is created and the Dockerfile directly mount the source folder to build it} -} -\description{ -Create Dockerfile from DESCRIPTION -} From 82a3b97e793a411a8d0b5815f4b2b4bf27993796 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 26 Dec 2019 21:58:42 +0100 Subject: [PATCH 166/211] close #316 --- R/add_modules.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/add_modules.R b/R/add_modules.R index 4ac76af6..1638af17 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -25,7 +25,8 @@ add_module <- function( dir_create = TRUE, fct = NULL, utils = NULL, - export = FALSE + export = FALSE, + placeholder = " " ){ name <- file_path_sans_ext(name) @@ -82,7 +83,7 @@ add_module <- function( write_there(glue("mod_%name%_ui <- function(id){")) write_there(" ns <- NS(id)") write_there(" tagList(") - write_there(" ") + write_there(placeholder) write_there(" )") write_there("}") write_there(" ") From f52fcef12037d8e3505318829a3df5775d9aef03 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 26 Dec 2019 22:10:32 +0100 Subject: [PATCH 167/211] added doc and server placeholders --- R/add_modules.R | 6 ++++-- man/add_module.Rd | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/R/add_modules.R b/R/add_modules.R index 1638af17..bb841e61 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -11,6 +11,7 @@ #' @param fct The name of the fct file. #' @param utils The name of the utils file. #' @param export Logical. Should the module be exported? Default is `FALSE`. +#' @param ph_ui,ph_server Texts to insert inside the modules UI and server. For advanced use. #' @note This function will prefix the `name` argument with `mod_`. #' @export #' @importFrom glue glue @@ -26,7 +27,8 @@ add_module <- function( fct = NULL, utils = NULL, export = FALSE, - placeholder = " " + ph_ui = " ", + ph_server = " " ){ name <- file_path_sans_ext(name) @@ -98,7 +100,7 @@ add_module <- function( } write_there(glue("mod_%name%_server <- function(input, output, session){")) write_there(" ns <- session$ns") - write_there(" ") + write_there(placeholderserver) write_there("}") write_there(" ") diff --git a/man/add_module.Rd b/man/add_module.Rd index a559f0c0..5b40a6b6 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -5,7 +5,8 @@ \title{Create a module} \usage{ add_module(name, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE, fct = NULL, utils = NULL, export = FALSE) + dir_create = TRUE, fct = NULL, utils = NULL, export = FALSE, + ph_ui = " ", ph_server = " ") } \arguments{ \item{name}{The name of the module} @@ -21,6 +22,8 @@ add_module(name, pkg = get_golem_wd(), open = TRUE, \item{utils}{The name of the utils file.} \item{export}{Logical. Should the module be exported? Default is \code{FALSE}.} + +\item{ph_ui, ph_server}{Texts to insert inside the modules UI and server. For advanced use.} } \description{ This function creates a module inside the \code{R/} folder, based From d9280a06e5ddde7060f18931541b93d36490eaf3 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 26 Dec 2019 22:46:35 +0100 Subject: [PATCH 168/211] correct variable names --- R/add_modules.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/add_modules.R b/R/add_modules.R index bb841e61..c90d2abf 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -85,7 +85,7 @@ add_module <- function( write_there(glue("mod_%name%_ui <- function(id){")) write_there(" ns <- NS(id)") write_there(" tagList(") - write_there(placeholder) + write_there(ph_ui) write_there(" )") write_there("}") write_there(" ") @@ -100,7 +100,7 @@ add_module <- function( } write_there(glue("mod_%name%_server <- function(input, output, session){")) write_there(" ns <- session$ns") - write_there(placeholderserver) + write_there(ph_server) write_there("}") write_there(" ") From c536d337480181dd70b202d51efc9fe657d56dee Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 24 Jan 2020 15:13:02 +0100 Subject: [PATCH 169/211] small renaming --- inst/shinyexample/R/app_config.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shinyexample/R/app_config.R b/inst/shinyexample/R/app_config.R index 2b17433f..22534834 100644 --- a/inst/shinyexample/R/app_config.R +++ b/inst/shinyexample/R/app_config.R @@ -9,7 +9,7 @@ app_sys <- function(...){ } -#' Access files in the current app +#' Read App Config #' #' @param value Value to retrieve from the config file. #' @param config R_CONFIG_ACTIVE value. From 62b60e0bf6f58d9faf3b7950a6439a9966bcfd53 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sat, 25 Jan 2020 22:54:48 +0100 Subject: [PATCH 170/211] fix #337 --- R/add_deploy_helpers.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 5a57f1aa..49c85a49 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -448,7 +448,7 @@ dock_from_desc <- function( packages_on_cran <- packages_with_version$installed %>% setNames(packages_with_version$package) - dock <- dockerfiler::Dockerfile$new(FROM = FROM) + dock <- dockerfiler::Dockerfile$new(FROM = FROM, AS = AS) if (length(system_requirement)>0){ if ( !expand){ From 702c664c0b2c1949bc99dede4157fc95d729cb93 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 27 Jan 2020 17:49:42 +0100 Subject: [PATCH 171/211] fix #340 mode rconnect to suggest --- DESCRIPTION | 4 +-- man/add_files.Rd | 39 ++++++++++++++++++-------- man/add_module.Rd | 16 ++++++++--- man/addins.Rd | 2 +- man/amend_golem_config.Rd | 9 ++++-- man/create_golem.Rd | 10 +++++-- man/dockerfiles.Rd | 59 +++++++++++++++++++++++++++------------ man/file_creation.Rd | 18 +++++++++--- man/fill_desc.Rd | 13 +++++++-- man/golem.Rd | 8 +++--- man/golem_js.Rd | 4 +-- man/golem_opts.Rd | 38 +++++++++++++++++-------- man/use_files.Rd | 20 ++++++++++--- man/use_recommended.Rd | 6 ++-- 14 files changed, 175 insertions(+), 71 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f7a4c31e..7812b363 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -55,7 +55,6 @@ Imports: remotes, rlang, roxygen2, - rsconnect, rstudioapi, shiny, stats, @@ -72,6 +71,7 @@ Suggests: pkgdown, rcmdcheck, rmarkdown, + rsconnect, spelling, stringr, withr @@ -81,4 +81,4 @@ Encoding: UTF-8 Language: en-US LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 6.1.1 +RoxygenNote: 7.0.2 diff --git a/man/add_files.Rd b/man/add_files.Rd index e85db825..4e985988 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -7,17 +7,32 @@ \alias{add_ui_server_files} \title{Create Files} \usage{ -add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE, with_doc_ready = TRUE) - -add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", - open = TRUE, dir_create = TRUE) - -add_css_file(name, 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) +add_js_file( + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE, + with_doc_ready = TRUE +) + +add_js_handler( + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +) + +add_css_file( + name, + 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{ \item{name}{The name of the module} @@ -30,7 +45,7 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} -\item{with_doc_ready}{Should the default file include \code{$( document ).ready()}?} +\item{with_doc_ready}{Should the default file include \verb{$( document ).ready()}?} } \description{ These functions create files inside the \code{inst/app} folder. diff --git a/man/add_module.Rd b/man/add_module.Rd index 5b40a6b6..94f19703 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -4,9 +4,17 @@ \alias{add_module} \title{Create a module} \usage{ -add_module(name, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE, fct = NULL, utils = NULL, export = FALSE, - ph_ui = " ", ph_server = " ") +add_module( + name, + pkg = get_golem_wd(), + open = TRUE, + dir_create = TRUE, + fct = NULL, + utils = NULL, + export = FALSE, + ph_ui = " ", + ph_server = " " +) } \arguments{ \item{name}{The name of the module} @@ -26,7 +34,7 @@ add_module(name, pkg = get_golem_wd(), open = TRUE, \item{ph_ui, ph_server}{Texts to insert inside the modules UI and server. For advanced use.} } \description{ -This function creates a module inside the \code{R/} folder, based +This function creates a module inside the \verb{R/} folder, based on a specific module structure. This function can be used outside of a {golem} project. } diff --git a/man/addins.Rd b/man/addins.Rd index 667a0aa6..6e626dfa 100644 --- a/man/addins.Rd +++ b/man/addins.Rd @@ -30,6 +30,6 @@ go_to_run_app() } \description{ \code{insert_ns()} takes a selected character vector and wrap it in \code{ns()} -The series of \code{go_to_*()} addins help you go to +The series of \verb{go_to_*()} addins help you go to common files used in developing a \code{{golem}} application. } diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd index 5ec8185c..92393bc0 100644 --- a/man/amend_golem_config.Rd +++ b/man/amend_golem_config.Rd @@ -4,8 +4,13 @@ \alias{amend_golem_config} \title{Amend golem config file} \usage{ -amend_golem_config(key, value, config = "default", - pkg = get_golem_wd(), talkative = TRUE) +amend_golem_config( + key, + value, + config = "default", + pkg = get_golem_wd(), + talkative = TRUE +) } \arguments{ \item{key}{key of the value to add in \code{config}} diff --git a/man/create_golem.Rd b/man/create_golem.Rd index 3840de08..04b0db5d 100644 --- a/man/create_golem.Rd +++ b/man/create_golem.Rd @@ -4,8 +4,14 @@ \alias{create_golem} \title{Create a package for Shiny App using golem} \usage{ -create_golem(path, check_name = TRUE, open = TRUE, - package_name = basename(path), without_comments = FALSE, ...) +create_golem( + path, + check_name = TRUE, + open = TRUE, + package_name = basename(path), + without_comments = FALSE, + ... +) } \arguments{ \item{path}{Name of the folder to create the package in. This will also be diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 51e51786..1c58c424 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -6,26 +6,49 @@ \alias{add_dockerfile_heroku} \title{Create a Dockerfile for Shiny App} \usage{ -add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/r-ver:", - R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, - host = "0.0.0.0", sysreqs = TRUE, - repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE, - update_tar_gz = TRUE, build_golem_from_source = TRUE) +add_dockerfile( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = "https://cran.rstudio.com/", + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE +) -add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/r-ver:", - R.Version()$major, ".", R.Version()$minor), as = NULL, - sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, open = TRUE, update_tar_gz = TRUE, - build_golem_from_source = TRUE) +add_dockerfile_shinyproxy( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + as = NULL, + sysreqs = TRUE, + repos = "https://cran.rstudio.com/", + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE +) -add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", - pkg = get_golem_wd(), from = paste0("rocker/r-ver:", - R.Version()$major, ".", R.Version()$minor), as = NULL, - sysreqs = TRUE, repos = "https://cran.rstudio.com/", - expand = FALSE, open = TRUE, update_tar_gz = TRUE, - build_golem_from_source = TRUE) +add_dockerfile_heroku( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + as = NULL, + sysreqs = TRUE, + repos = "https://cran.rstudio.com/", + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE +) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} diff --git a/man/file_creation.Rd b/man/file_creation.Rd index dea0be83..eebaa65e 100644 --- a/man/file_creation.Rd +++ b/man/file_creation.Rd @@ -5,11 +5,21 @@ \alias{add_utils} \title{Add fct_ and utils_ files} \usage{ -add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) +add_fct( + name, + module = NULL, + pkg = get_golem_wd(), + open = TRUE, + dir_create = TRUE +) -add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, - dir_create = TRUE) +add_utils( + name, + module = NULL, + pkg = get_golem_wd(), + open = TRUE, + dir_create = TRUE +) } \arguments{ \item{name}{The name of the file} diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index 8a6e3407..be4802f9 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -4,9 +4,16 @@ \alias{fill_desc} \title{Fill your description} \usage{ -fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, - author_last_name, author_email, repo_url = NULL, - pkg = get_golem_wd()) +fill_desc( + pkg_name, + pkg_title, + pkg_description, + author_first_name, + author_last_name, + author_email, + repo_url = NULL, + pkg = get_golem_wd() +) } \arguments{ \item{pkg_name}{The name of the package} diff --git a/man/golem.Rd b/man/golem.Rd index f41867c2..2e3b56a5 100644 --- a/man/golem.Rd +++ b/man/golem.Rd @@ -17,13 +17,13 @@ Useful links: } \author{ -\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) +\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (\href{https://orcid.org/0000-0003-0671-9270}{ORCID}) Authors: \itemize{ - \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) - \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) - \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) + \item Colin Fay \email{contact@colinfay.me} (\href{https://orcid.org/0000-0001-7343-1846}{ORCID}) + \item Sébastien Rochette \email{sebastien@thinkr.fr} (\href{https://orcid.org/0000-0002-1565-9313}{ORCID}) + \item Cervan Girard \email{cervan@thinkr.fr} (\href{https://orcid.org/0000-0002-4816-4624}{ORCID}) } Other contributors: diff --git a/man/golem_js.Rd b/man/golem_js.Rd index f1c87382..189ac7f7 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -23,8 +23,8 @@ invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) \item{hideid}{Hide an element with the id provided.} \item{showclass}{Same as showid, but with class.} \item{hideclass}{Same as hideid, but with class.} -\item{showhref}{Same as showid, but with \code{a[href*=}} -\item{hidehref}{Same as hideid, but with \code{a[href*=}} +\item{showhref}{Same as showid, but with \verb{a[href*=}} +\item{hidehref}{Same as hideid, but with \verb{a[href*=}} \item{clickon}{Click on an element. The full jQuery selector has to be used.} \item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} \item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index 4b3e4428..e43ddbc2 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -10,25 +10,41 @@ \alias{get_golem_version} \title{\code{{golem}} options} \usage{ -set_golem_options(golem_name = pkgload::pkg_name(), +set_golem_options( + golem_name = pkgload::pkg_name(), golem_version = pkgload::pkg_version(), - golem_wd = pkgload::pkg_path(), app_prod = FALSE, talkative = TRUE) + golem_wd = pkgload::pkg_path(), + app_prod = FALSE, + talkative = TRUE +) set_golem_wd(path = pkgload::pkg_path(), talkative = TRUE) -set_golem_name(name = pkgload::pkg_name(), path = pkgload::pkg_path(), - talkative = TRUE) +set_golem_name( + name = pkgload::pkg_name(), + path = pkgload::pkg_path(), + talkative = TRUE +) -set_golem_version(version = pkgload::pkg_version(), - path = pkgload::pkg_path(), talkative = TRUE) +set_golem_version( + version = pkgload::pkg_version(), + path = pkgload::pkg_path(), + talkative = TRUE +) get_golem_wd(use_parent = TRUE, path = pkgload::pkg_path()) -get_golem_name(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, path = pkgload::pkg_path()) - -get_golem_version(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, path = pkgload::pkg_path()) +get_golem_name( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = pkgload::pkg_path() +) + +get_golem_version( + config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, + path = pkgload::pkg_path() +) } \arguments{ \item{golem_name}{Name of the current golem.} diff --git a/man/use_files.Rd b/man/use_files.Rd index 4444d963..1436af71 100644 --- a/man/use_files.Rd +++ b/man/use_files.Rd @@ -5,11 +5,23 @@ \alias{use_external_css_file} \title{Use Files} \usage{ -use_external_js_file(url, name, pkg = get_golem_wd(), - dir = "inst/app/www", open = TRUE, dir_create = TRUE) +use_external_js_file( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +) -use_external_css_file(url, name, pkg = get_golem_wd(), - dir = "inst/app/www", open = TRUE, dir_create = TRUE) +use_external_css_file( + url, + name, + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +) } \arguments{ \item{url}{String representation of URL for the file to be downloaded} diff --git a/man/use_recommended.Rd b/man/use_recommended.Rd index b28f3489..4ee669cf 100644 --- a/man/use_recommended.Rd +++ b/man/use_recommended.Rd @@ -5,8 +5,10 @@ \alias{use_recommended_tests} \title{Add recommended elements} \usage{ -use_recommended_deps(pkg = get_golem_wd(), recommended = c("shiny", - "DT", "attempt", "glue", "htmltools", "golem")) +use_recommended_deps( + pkg = get_golem_wd(), + recommended = c("shiny", "DT", "attempt", "glue", "htmltools", "golem") +) use_recommended_tests(pkg = get_golem_wd()) } From e63f9ed077559c346f5795b43edfe8e458ed0b4e Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 27 Jan 2020 17:52:03 +0100 Subject: [PATCH 172/211] fix #339 --- tests/testthat/test-config.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index d0e91a9b..f28fa059 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -2,7 +2,7 @@ test_that("config works", { with_dir(pkg, { expect_equal(get_golem_name(), fakename) expect_equal(get_golem_version(), "0.0.0.9000") - expect_equal(get_golem_wd(), pkg) + expect_equal(normalizePath(get_golem_wd(), mustWork = FALSE), normalizePath(pkg, mustWork = FALSE)) amend_golem_config( key = "where", value = "indev" From 329eef5cef003ac8a7f51fb15ce0ef2313a59919 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 27 Jan 2020 22:26:17 +0100 Subject: [PATCH 173/211] #339 again --- tests/testthat/test-config.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index f28fa059..340ebbec 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -29,7 +29,7 @@ test_that("config works", { set_golem_version("0.0.0.9000") set_golem_wd(normalizePath("inst")) - expect_equal(get_golem_wd(), normalizePath("inst")) + expect_equal(normalizePath(get_golem_wd()), normalizePath("inst")) set_golem_wd(pkg) }) }) From db8fc858b1c059547a17d014936d2392e6c90bf8 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 30 Jan 2020 20:01:25 +0100 Subject: [PATCH 174/211] fix #341, use : pkgload::load_all(export_all = FALSE,helpers = FALSE,attach_testthat = FALSE) --- R/add_deploy_helpers.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 49c85a49..dad80281 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -22,7 +22,7 @@ add_rstudio_files <- function( write_there("# To deploy, run: rsconnect::deployApp()") write_there("# Or use the blue button on top of this file") write_there("") - write_there("pkgload::load_all()") + write_there("pkgload::load_all(export_all = FALSE,helpers = FALSE,attach_testthat = FALSE)") write_there("options( \"golem.app.prod\" = TRUE)") write_there( sprintf( From 2da0766179703772d5f14f7ff06b44e5425d8ba3 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 30 Jan 2020 20:31:09 +0100 Subject: [PATCH 175/211] issue #347 remove %>% --- DESCRIPTION | 1 - NAMESPACE | 1 - R/add_deploy_helpers.R | 27 ++++++++++++--------------- R/get_sysreqs.R | 17 ++++++++--------- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7812b363..415cda60 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -49,7 +49,6 @@ Imports: here, htmltools, jsonlite, - magrittr, pkgload, purrr, remotes, diff --git a/NAMESPACE b/NAMESPACE index 629e24d3..ff5ee963 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -80,7 +80,6 @@ importFrom(htmltools,includeScript) importFrom(htmltools,save_html) importFrom(htmltools,tags) importFrom(jsonlite,fromJSON) -importFrom(magrittr,"%>%") importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(pkgload,pkg_path) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index dad80281..d648c762 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -382,7 +382,6 @@ alert_build <- function(path, output, build_golem_from_source){ #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps -#' @importFrom magrittr %>% #' @importFrom stats setNames #' @noRd dock_from_desc <- function( @@ -430,11 +429,11 @@ dock_from_desc <- function( } remotes_deps <- remotes::package_deps(packages) - packages_on_cran <- remotes_deps$package[remotes_deps$is_cran] %>% - intersect(packages) + packages_on_cran <- + intersect(remotes_deps$package[remotes_deps$is_cran],packages) - packages_not_on_cran <- packages %>% - setdiff(packages_on_cran) + packages_not_on_cran <- + setdiff(packages,packages_on_cran) packages_with_version <- data.frame( package=remotes_deps$package, @@ -445,8 +444,8 @@ dock_from_desc <- function( packages_with_version$package %in% packages_on_cran, ] - packages_on_cran <- packages_with_version$installed %>% - setNames(packages_with_version$package) + packages_on_cran <- + setNames(packages_with_version$installed, packages_with_version$package) dock <- dockerfiler::Dockerfile$new(FROM = FROM, AS = AS) @@ -496,14 +495,12 @@ dock_from_desc <- function( if ( length(packages_not_on_cran > 0)){ - nn<- lapply( - remotes_deps$remote[!remotes_deps$is_cran], - function(.){ - .[c('repo','username','sha')] - } - ) %>% - do.call(rbind,.) %>% - as.data.frame() + nn <- + as.data.frame(do.call(rbind, + lapply(remotes_deps$remote[!remotes_deps$is_cran], + function(.) { + .[c('repo', 'username', 'sha')] + }))) nn <- glue::glue("{nn$username}/{nn$repo}@{nn$sha}") diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index dc0b5a6c..4011dbad 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -9,7 +9,6 @@ #' @importFrom jsonlite fromJSON #' @importFrom remotes package_deps #' @importFrom purrr map -#' @importFrom magrittr %>% #' @export get_sysreqs <- function(packages, quiet = TRUE,batch_n=30){ @@ -20,18 +19,18 @@ get_sysreqs <- function(packages, quiet = TRUE,batch_n=30){ ) ) - split( +sp <- split( all_deps, ceiling( seq_along(all_deps) / batch_n ) - ) %>% - map( ~ get_batch_sysreqs(.x, quiet = quiet)) %>% - unlist() %>% - unname() %>% - unique() %>% - sort() - + ) + + +sort(unique(unname(unlist( + map(sp, ~ get_batch_sysreqs(.x, quiet = quiet)) +)))) + } #' @importFrom fs file_delete file_temp From 306082adf7a16dbcf2f067dca96f6f19973c2b63 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 30 Jan 2020 20:49:59 +0100 Subject: [PATCH 176/211] remove purr #347 --- DESCRIPTION | 1 - R/get_sysreqs.R | 6 +++++- tests/testthat/test-desc.R | 7 +++++-- tests/testthat/test-favicon.R | 6 +++--- tests/testthat/test-use_recomended.R | 16 ++++++++++++++-- tests/testthat/test-zreload.R | 5 ++++- 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 415cda60..49399d15 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -50,7 +50,6 @@ Imports: htmltools, jsonlite, pkgload, - purrr, remotes, rlang, roxygen2, diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 4011dbad..5a79fb69 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -28,7 +28,11 @@ sp <- split( sort(unique(unname(unlist( - map(sp, ~ get_batch_sysreqs(.x, quiet = quiet)) + + # map(sp, ~ get_batch_sysreqs(.x, quiet = quiet)) + lapply(sp, function(.x){ get_batch_sysreqs(.x, quiet = quiet) }) + + )))) } diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 5c143aa2..369e3426 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -26,10 +26,13 @@ test_that("desc works", { expect_true( all( - purrr::map_lgl(add_desc,function(x){ + # purrr::map_lgl(add_desc,function(x){ + # any(grepl(x,desc))} + # ) + as.logical(lapply(add_desc,function(x){ any(grepl(x,desc))} ) - ) + )) ) expect_true( diff --git a/tests/testthat/test-favicon.R b/tests/testthat/test-favicon.R index 78909189..d79937a0 100644 --- a/tests/testthat/test-favicon.R +++ b/tests/testthat/test-favicon.R @@ -4,16 +4,16 @@ test_that("test use_favicon",{ with_dir(pkg,{ use_favicon() expect_true(file.exists("inst/app/www/favicon.ico")) - purrr::map( + lapply( c( "test.jpeg", "test.bmp", "test.gif", "test.tiff" ), - ~ expect_error( + function(.x) {expect_error( use_favicon(path = .x) - ) + )} ) golem::remove_favicon() expect_false(file.exists("inst/app/www/favicon.ico")) diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 7e8055b4..68938a2e 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -4,9 +4,21 @@ test_that("test use_recommended_deps",{ packages <- c('shiny', 'DT', 'attempt', 'glue', 'golem', 'htmltools') deps <- desc::desc_get_deps(file = "DESCRIPTION") expect_true( - all(purrr::map_lgl(packages, ~ .x %in% deps$package)) - ) + # all( + # purrr::map_lgl(packages, ~ .x %in% deps$package) + # ) + all( + as.logical( + lapply(packages, + function(.x){.x %in% deps$package} + ) + + ) + )) }) + + + }) diff --git a/tests/testthat/test-zreload.R b/tests/testthat/test-zreload.R index e8dced53..8a8b945c 100644 --- a/tests/testthat/test-zreload.R +++ b/tests/testthat/test-zreload.R @@ -24,7 +24,10 @@ test_that("test detach_all_attached",{ with_dir(pkg_reload,{ test <- detach_all_attached() testthat::expect_true( - all(purrr::map_lgl(test, is.null)) + # all(purrr::map_lgl(test, is.null)) + all(as.logical(lapply(test, is.null)) + + ) ) }) }) From 74bf39db1cd0c0d4bd7807751986385dbd282d55 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 30 Jan 2020 21:02:50 +0100 Subject: [PATCH 177/211] remove yesno #347 --- R/config.R | 1 - R/create_golem.R | 3 +-- R/utils.R | 6 +++--- R/yesno.R | 5 +++++ 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 R/yesno.R diff --git a/R/config.R b/R/config.R index 1e6b50d2..d5ef2d67 100644 --- a/R/config.R +++ b/R/config.R @@ -30,7 +30,6 @@ guess_where_config <- function( return(NULL) } -#' @importFrom yesno yesno #' @importFrom fs file_copy path #' @importFrom pkgload pkg_name get_current_config <- function( diff --git a/R/create_golem.R b/R/create_golem.R index 41d1aeec..0f7ce727 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -10,7 +10,6 @@ #' @param without_comments Poolean start project without golem comments #' @param ... not used #' -#' @importFrom yesno yesno #' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace #' @importFrom rstudioapi isAvailable openProject @@ -36,7 +35,7 @@ create_golem <- function( } if (dir_exists(path)){ - res <- yesno::yesno( + res <- yesno( paste("The path", path, "already exists, override?") ) if (!res){ diff --git a/R/utils.R b/R/utils.R index 64af94bb..e0414197 100644 --- a/R/utils.R +++ b/R/utils.R @@ -43,7 +43,7 @@ create_if_needed <- function( # If it doesn't exist, ask if we are allowed # to create it if (dont_exist){ - ask <- yesno::yesno( + ask <- yesno( sprintf( "The %s %s doesn't exist, create?", basename(path), @@ -73,7 +73,7 @@ create_if_needed <- function( check_file_exist <- function(file){ res <- TRUE if (file_exists(file)){ - res <- yesno::yesno("This file already exists, override?") + res <- yesno("This file already exists, override?") } return(res) } @@ -83,7 +83,7 @@ check_file_exist <- function(file){ check_dir_exist <- function(dir){ res <- TRUE if (!dir_exists(dir)){ - res <- yesno::yesno(sprintf("The %s does not exists, create?", dir)) + res <- yesno(sprintf("The %s does not exists, create?", dir)) } return(res) } diff --git a/R/yesno.R b/R/yesno.R new file mode 100644 index 00000000..f67cc97b --- /dev/null +++ b/R/yesno.R @@ -0,0 +1,5 @@ +yesno <- function (...) +{ + cat(paste0(..., collapse = "")) + menu(c("Yes", "No")) == 1 +} \ No newline at end of file From 558c1fe5cf5bcb0a9285b47202d2074f2a205333 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Thu, 30 Jan 2020 22:31:40 +0100 Subject: [PATCH 178/211] update NAMESPACE --- DESCRIPTION | 3 +-- NAMESPACE | 3 +-- R/get_sysreqs.R | 1 - R/yesno.R | 1 + tests/testthat/test-desc.R | 3 --- tests/testthat/test-use_recomended.R | 3 --- tests/testthat/test-zreload.R | 2 -- 7 files changed, 3 insertions(+), 13 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 49399d15..0bd4b044 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -60,8 +60,7 @@ Imports: tools, usethis, utils, - yaml, - yesno + yaml Suggests: covr, devtools, diff --git a/NAMESPACE b/NAMESPACE index ff5ee963..b05c31a6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -83,7 +83,6 @@ importFrom(jsonlite,fromJSON) importFrom(pkgload,load_all) importFrom(pkgload,pkg_name) importFrom(pkgload,pkg_path) -importFrom(purrr,map) importFrom(remotes,dev_package_deps) importFrom(remotes,package_deps) importFrom(rlang,enquo) @@ -110,8 +109,8 @@ importFrom(utils,download.file) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) importFrom(utils,installed.packages) +importFrom(utils,menu) importFrom(utils,packageVersion) importFrom(utils,sessionInfo) importFrom(yaml,read_yaml) importFrom(yaml,write_yaml) -importFrom(yesno,yesno) diff --git a/R/get_sysreqs.R b/R/get_sysreqs.R index 5a79fb69..a96ce319 100644 --- a/R/get_sysreqs.R +++ b/R/get_sysreqs.R @@ -8,7 +8,6 @@ #' @importFrom utils download.file #' @importFrom jsonlite fromJSON #' @importFrom remotes package_deps -#' @importFrom purrr map #' @export get_sysreqs <- function(packages, quiet = TRUE,batch_n=30){ diff --git a/R/yesno.R b/R/yesno.R index f67cc97b..8ddbeeeb 100644 --- a/R/yesno.R +++ b/R/yesno.R @@ -1,3 +1,4 @@ +#' @importFrom utils menu yesno <- function (...) { cat(paste0(..., collapse = "")) diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 369e3426..c9b7d222 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -26,9 +26,6 @@ test_that("desc works", { expect_true( all( - # purrr::map_lgl(add_desc,function(x){ - # any(grepl(x,desc))} - # ) as.logical(lapply(add_desc,function(x){ any(grepl(x,desc))} ) diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 68938a2e..acc55f29 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -4,9 +4,6 @@ test_that("test use_recommended_deps",{ packages <- c('shiny', 'DT', 'attempt', 'glue', 'golem', 'htmltools') deps <- desc::desc_get_deps(file = "DESCRIPTION") expect_true( - # all( - # purrr::map_lgl(packages, ~ .x %in% deps$package) - # ) all( as.logical( lapply(packages, diff --git a/tests/testthat/test-zreload.R b/tests/testthat/test-zreload.R index 8a8b945c..5d18b8f5 100644 --- a/tests/testthat/test-zreload.R +++ b/tests/testthat/test-zreload.R @@ -24,9 +24,7 @@ test_that("test detach_all_attached",{ with_dir(pkg_reload,{ test <- detach_all_attached() testthat::expect_true( - # all(purrr::map_lgl(test, is.null)) all(as.logical(lapply(test, is.null)) - ) ) }) From 03f566312d23d5969aa1e5d8e7588925d62d0eeb Mon Sep 17 00:00:00 2001 From: Lorenzo Meninato Date: Wed, 29 Jan 2020 15:47:47 -0800 Subject: [PATCH 179/211] Fixed bad file endings --- R/add_r_files.R | 3 +- R/addins.R | 3 +- R/browser_button.R | 3 +- R/config.R | 3 +- R/create_golem.R | 3 +- R/js.R | 3 +- R/use_files.R | 3 +- R/use_utils.R | 3 +- R/with_opt.R | 3 +- R/zzz.R | 3 +- inst/shinyexample/R/app_config.R | 3 +- inst/shinyexample/R/app_ui.R | 3 +- inst/shinyexample/dev/02_dev.R | 3 +- inst/utils/golem_utils_server.R | 3 +- tests/testthat/helper-config.R | 3 +- tests/testthat/test-add_deploy_helpers.R | 3 +- tests/testthat/test-add_resource_path.R | 3 +- tests/testthat/test-favicon.R | 3 +- tests/testthat/test-file_endings.R | 37 ++++++++++++++++++++++++ tests/testthat/test-zzzzzzzzzz.R | 3 +- 20 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 tests/testthat/test-file_endings.R diff --git a/R/add_r_files.R b/R/add_r_files.R index 4989f08c..0883d98d 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -91,4 +91,5 @@ add_utils<- function( open = open, dir_create = dir_create ) -} \ No newline at end of file +} + diff --git a/R/addins.R b/R/addins.R index 6daf2d08..6796359c 100644 --- a/R/addins.R +++ b/R/addins.R @@ -82,4 +82,5 @@ go_to_app_server <- function(){ #' @aliases addins go_to_run_app <- function(){ go_to("R/run_app.R") -} \ No newline at end of file +} + diff --git a/R/browser_button.R b/R/browser_button.R index 897ec451..b6fc5306 100644 --- a/R/browser_button.R +++ b/R/browser_button.R @@ -19,4 +19,5 @@ browser_button <- function(){ 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();") -} \ No newline at end of file +} + diff --git a/R/config.R b/R/config.R index d5ef2d67..d4673473 100644 --- a/R/config.R +++ b/R/config.R @@ -116,4 +116,5 @@ amend_golem_config <- function( conf_path ) invisible(TRUE) -} \ No newline at end of file +} + diff --git a/R/create_golem.R b/R/create_golem.R index 0f7ce727..9ac4f5ab 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -158,4 +158,5 @@ create_golem_gui <- function(path,...){ open = FALSE, without_comments = dots$without_comments ) -} \ No newline at end of file +} + diff --git a/R/js.R b/R/js.R index bb22f63e..53619c1e 100644 --- a/R/js.R +++ b/R/js.R @@ -56,4 +56,5 @@ invoke_js <- function( }, fun=fun) invisible(res) -} \ No newline at end of file +} + diff --git a/R/use_files.R b/R/use_files.R index 75fce7e4..7ea403d5 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -123,4 +123,5 @@ use_external_css_file <- function( } else { cat_red_bullet(glue::glue("Go to {where}")) } -} \ No newline at end of file +} + diff --git a/R/use_utils.R b/R/use_utils.R index 82d5ea71..90cd0b1d 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -62,4 +62,5 @@ use_utils <- function( return(TRUE) } -} \ No newline at end of file +} + diff --git a/R/with_opt.R b/R/with_opt.R index 5519c968..11c2f728 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -30,4 +30,5 @@ get_golem_options <- function(which = NULL){ } else { getShinyOption("golem_options")[[which]] } -} \ No newline at end of file +} + diff --git a/R/zzz.R b/R/zzz.R index 15e1e28d..64f57e9d 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1 +1,2 @@ -globalVariables(".") \ No newline at end of file +globalVariables(".") + diff --git a/inst/shinyexample/R/app_config.R b/inst/shinyexample/R/app_config.R index 22534834..7db52c1b 100644 --- a/inst/shinyexample/R/app_config.R +++ b/inst/shinyexample/R/app_config.R @@ -30,4 +30,5 @@ get_golem_config <- function( file = app_sys("golem-config.yml"), use_parent = use_parent ) -} \ No newline at end of file +} + diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 4c52fa09..3394b6cf 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -39,4 +39,5 @@ golem_add_external_resources <- function(){ #tags$link(rel="stylesheet", type="text/css", href="www/custom.css") ) -} \ No newline at end of file +} + diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index f43f648b..20729af7 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -42,4 +42,5 @@ usethis::use_appveyor() # You're now set! # go to dev/03_deploy.R -rstudioapi::navigateToFile("dev/03_deploy.R") \ No newline at end of file +rstudioapi::navigateToFile("dev/03_deploy.R") + diff --git a/inst/utils/golem_utils_server.R b/inst/utils/golem_utils_server.R index 8a960184..ab0ac6d6 100644 --- a/inst/utils/golem_utils_server.R +++ b/inst/utils/golem_utils_server.R @@ -29,4 +29,5 @@ drop_nulls <- function(x){ # typing reactiveValues is too long rv <- shiny::reactiveValues -rvtl <- shiny::reactiveValuesToList \ No newline at end of file +rvtl <- shiny::reactiveValuesToList + diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index 089ee3ef..05cbad5a 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -67,4 +67,5 @@ withr::with_dir(pkg, { usethis::proj_set(pkg) orig_test <- set_golem_wd(pkg) usethis::use_mit_license("Golem") -}) \ No newline at end of file +}) + diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 7a50f078..8d82ec5d 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -48,4 +48,5 @@ test_that("add_rstudio_files", { } }) -}) \ No newline at end of file +}) + diff --git a/tests/testthat/test-add_resource_path.R b/tests/testthat/test-add_resource_path.R index b69ecd60..fd7bc806 100644 --- a/tests/testthat/test-add_resource_path.R +++ b/tests/testthat/test-add_resource_path.R @@ -7,4 +7,5 @@ test_that("add_resource_path", { }) -}) \ No newline at end of file +}) + diff --git a/tests/testthat/test-favicon.R b/tests/testthat/test-favicon.R index d79937a0..92d587ef 100644 --- a/tests/testthat/test-favicon.R +++ b/tests/testthat/test-favicon.R @@ -27,4 +27,5 @@ test_that("test favicon",{ "shiny.tag" ) }) -}) \ No newline at end of file +}) + diff --git a/tests/testthat/test-file_endings.R b/tests/testthat/test-file_endings.R new file mode 100644 index 00000000..8092d99e --- /dev/null +++ b/tests/testthat/test-file_endings.R @@ -0,0 +1,37 @@ +bad_last_line <- function(file){ + bad_file <- tryCatch({ + readLines(con = file) + }, warning = function(w){ + return(TRUE) + }) + if (typeof(bad_file) == "logical"){ + return(TRUE) + } + FALSE +} + +files_with_bad_lines <- function(){ + r_files <- list.files(pattern = ".*R$", recursive = TRUE) + last_lines <- r_files %>% + purrr::map_lgl(bad_last_line) + + invalid_r_file <- function(r_file){ + if (last_lines[which(r_files == r_file)]){ + return(TRUE) + } + FALSE + } + + Filter(invalid_r_file, r_files) +} + +add_final_empty_line <- function(files){ + purrr::walk(files, function(file){ + write("\n", file = file, append = TRUE) + }) +} + +test_that("proper_file_endings", { + bad_files <- files_with_bad_lines() + expect_equal(length(bad_files), 0) +}) diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 4c7dacea..44021dad 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -3,4 +3,5 @@ if (dir.exists(orig_test)){ usethis::proj_set(orig_test) } -unlink(pkg, TRUE, TRUE) \ No newline at end of file +unlink(pkg, TRUE, TRUE) + From bacdb5b117df0019ef492b3ad721d2a19fbe587e Mon Sep 17 00:00:00 2001 From: Jed Stephens Date: Sun, 2 Feb 2020 12:02:52 +0000 Subject: [PATCH 180/211] Fixed spelling error in 01_start.R Corrects the spelling of the word "description" --- inst/shinyexample/dev/01_start.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 1f64758f..34cad82f 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -4,7 +4,7 @@ # # 1 - On init # -## 1.1 - Fill the descripion & set options +## 1.1 - Fill the description & set options ## ## Add information about the package that will contain your app From f78fcda5eb80e233f10a8373317a7c9476b07912 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 7 Feb 2020 22:01:33 +0100 Subject: [PATCH 181/211] close #129 --- NAMESPACE | 1 + R/add_files.R | 63 +++++++++++++++++++++++++++--------- R/bundle_resources.R | 51 +++++++++++++++++++++++++++++ inst/shinyexample/R/app_ui.R | 19 ++++++----- 4 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 R/bundle_resources.R diff --git a/NAMESPACE b/NAMESPACE index b05c31a6..072683f1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,6 +20,7 @@ export(app_dev) export(app_prod) export(browser_button) export(browser_dev) +export(bundle_resources) export(cat_dev) export(create_golem) export(detach_all_attached) diff --git a/R/add_files.R b/R/add_files.R index e4408614..0df5ee0d 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -61,12 +61,23 @@ add_js_file <- function( cat_created(where) - if (file_exists(paste0(pkg, "/DESCRIPTION"))) { - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + if ( + file_exists( + paste0(pkg, "/DESCRIPTION") + ) + ) { + if (dir != "inst/app/www"){ + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) ) - )} + } else { + cat_green_tick( + 'File automatically linked in `golem_add_external_resources()`.' + ) + } + } if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) @@ -128,12 +139,23 @@ add_js_handler <- function( cat_created(where) - if (file_exists(paste0(pkg, "/DESCRIPTION"))) { - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + if ( + file_exists( + paste0(pkg, "/DESCRIPTION") + ) + ) { + if (dir != "inst/app/www"){ + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' + ) + ) + } else { + cat_green_tick( + 'File automatically linked in `golem_add_external_resources()`.' ) - )} + } + } if (rstudioapi::isAvailable() & open){ rstudioapi::navigateToFile(where) @@ -183,12 +205,23 @@ add_css_file <- function( cat_created(where) - if (file_exists(paste0(pkg, "/DESCRIPTION"))) { - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' + if ( + file_exists( + paste0(pkg, "/DESCRIPTION") + ) + ) { + if (dir != "inst/app/www"){ + cat_red_bullet( + glue::glue( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' + ) ) - )} + } else { + cat_green_tick( + 'File automatically linked in `golem_add_external_resources()`.' + ) + } + } if (rstudioapi::isAvailable() & open ){ rstudioapi::navigateToFile(where) diff --git a/R/bundle_resources.R b/R/bundle_resources.R new file mode 100644 index 00000000..cc630c6e --- /dev/null +++ b/R/bundle_resources.R @@ -0,0 +1,51 @@ +#' Automatically serve golem external resources +#' +#' This function is a wrapper around `htmltools::htmlDependency` that +#' automatically bundles the CSS and JavaScript files in `inst/app/www` +#' and which are created by `golem::add_css_file()` , `golem::add_js_file()` +#' and `golem::add_js_handler()`. +#' +#' @param path The path to the folder where the external files are located. +#' @param app_title The title of the app, to be used as an application title. +#' @inheritParams htmltools::htmlDependency +#' +#' @export +bundle_resources <- function( + path, + app_title, + name = "golem_resources", + version = "0.0.1", + meta = NULL, + head = NULL, + attachment = NULL, + package = NULL, + all_files = TRUE +){ + + htmltools::htmlDependency( + name, + version, + src = path, + script = list.files( + path, + pattern = "\\.js$" + ), + stylesheet = list.files( + path, + pattern = "\\.css$" + ), + meta = meta, + head = c( + as.character( + tags$title(app_title) + ), + as.character( + golem::activate_js() + ), + head + ), + attachment = attachment, + package = package, + all_files = all_files + ) +} \ No newline at end of file diff --git a/inst/shinyexample/R/app_ui.R b/inst/shinyexample/R/app_ui.R index 3394b6cf..a96b6bdd 100644 --- a/inst/shinyexample/R/app_ui.R +++ b/inst/shinyexample/R/app_ui.R @@ -1,6 +1,6 @@ -#' The application User-Interfac +#' The application User-Interface #' -#' @param request Internal parameter for {shiny}. +#' @param request Internal parameter for `{shiny}`. #' DO NOT REMOVE. #' @import shiny #' @noRd @@ -21,7 +21,7 @@ app_ui <- function(request) { #' resources inside the Shiny application. #' #' @import shiny -#' @importFrom golem add_resource_path activate_js favicon +#' @importFrom golem add_resource_path activate_js favicon bundle_resources #' @noRd golem_add_external_resources <- function(){ @@ -30,14 +30,13 @@ golem_add_external_resources <- function(){ ) tags$head( - activate_js(), favicon(), - tags$title("shinyexample") - # Add here all the external resources - # If you have a custom.css in the inst/app/www - # Or for example, you can add shinyalert::useShinyalert() here - #tags$link(rel="stylesheet", type="text/css", href="www/custom.css") - + bundle_resources( + path = app_sys('app/www'), + app_title = 'shinyexample' + ) + # Add here other external resources + # for example, you can add shinyalert::useShinyalert() ) } From 56ddff4e1b67fb81244f1fb4edbddb8bee90b325 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 7 Feb 2020 22:04:16 +0100 Subject: [PATCH 182/211] close #352 --- R/js.R | 13 ++++++---- inst/shinyexample/R/app_config.R | 2 +- inst/utils/golem-js.js | 4 ++-- man/bundle_resources.Rd | 41 ++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 man/bundle_resources.Rd diff --git a/R/js.R b/R/js.R index 53619c1e..1b82c51d 100644 --- a/R/js.R +++ b/R/js.R @@ -22,9 +22,13 @@ #' \item{clickon}{Click on an element. The full jQuery selector has to be used.} #' \item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} #' \item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} -#' \item{alert}{Open an alert box with the message provided.} -#' \item{prompt}{Open a prompt box with the message provided.} -#' \item{confirm}{Open a confirm box with the message provided.} +#' \item{alert}{Open an alert box with the message(s) provided.} +#' \item{prompt}{Open a prompt box with the message(s) provided. This function takes +#' a list with message and id `list(message = "", id = "")`. The output of the prompt +#' will be sent to `input$id`.} +#' \item{confirm}{Open a confirm box with the message provided. This function takes +#' a list with message and id `list(message = "", id = "")`. The output of the prompt +#' will be sent to `input$id`.} #' } #' #' @export @@ -54,7 +58,8 @@ invoke_js <- function( function(message, fun){ session$sendCustomMessage(fun, message) }, - fun=fun) + fun = fun + ) invisible(res) } diff --git a/inst/shinyexample/R/app_config.R b/inst/shinyexample/R/app_config.R index 7db52c1b..300dd3bc 100644 --- a/inst/shinyexample/R/app_config.R +++ b/inst/shinyexample/R/app_config.R @@ -15,7 +15,7 @@ app_sys <- function(...){ #' @param config R_CONFIG_ACTIVE value. #' @param use_parent Logical, scan the parent directory for config file. #' -#' @import config +#' @importFrom config get #' #' @noRd get_golem_config <- function( diff --git a/inst/utils/golem-js.js b/inst/utils/golem-js.js index 7f890ee8..74cd4aca 100644 --- a/inst/utils/golem-js.js +++ b/inst/utils/golem-js.js @@ -43,8 +43,8 @@ $( document ).ready(function() { $(what).removeAttr('disabled'); }); - Shiny.addCustomMessageHandler('alert', function(args) { - alert(args.message); + Shiny.addCustomMessageHandler('alert', function(message) { + alert(message); }); Shiny.addCustomMessageHandler('prompt', function(args) { diff --git a/man/bundle_resources.Rd b/man/bundle_resources.Rd new file mode 100644 index 00000000..1e75a17b --- /dev/null +++ b/man/bundle_resources.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/bundle_resources.R +\name{bundle_resources} +\alias{bundle_resources} +\title{Automatically serve golem external resources} +\usage{ +bundle_resources(path, app_title, name = "golem_resources", + version = "0.0.1", meta = NULL, head = NULL, attachment = NULL, + package = NULL, all_files = TRUE) +} +\arguments{ +\item{path}{The path to the folder where the external files are located.} + +\item{app_title}{The title of the app, to be used as an application title.} + +\item{name}{Library name} + +\item{version}{Library version} + +\item{meta}{Named list of meta tags to insert into document head} + +\item{head}{Arbitrary lines of HTML to insert into the document head} + +\item{attachment}{Attachment(s) to include within the document head. See +Details.} + +\item{package}{An R package name to indicate where to find the \code{src} +directory when \code{src} is a relative path (see +\code{\link{resolveDependencies}}).} + +\item{all_files}{Whether all files under the \code{src} directory are +dependency files. If \code{FALSE}, only the files specified in +\code{script}, \code{stylesheet}, and \code{attachment} are treated as +dependency files.} +} +\description{ +This function is a wrapper around \code{htmltools::htmlDependency} that +automatically bundles the CSS and JavaScript files in \code{inst/app/www} +and which are created by \code{golem::add_css_file()} , \code{golem::add_js_file()} +and \code{golem::add_js_handler()}. +} From 663b06a06eedb210dbe948a20d15382725e74c37 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 7 Feb 2020 22:06:05 +0100 Subject: [PATCH 183/211] implements manual testing with docker --- inst/manualtests/Dockerfile | 15 +++++ inst/manualtests/script.R | 110 +++++++++++++++++++++++++----------- inst/manualtests/tests.sh | 5 +- 3 files changed, 94 insertions(+), 36 deletions(-) create mode 100644 inst/manualtests/Dockerfile diff --git a/inst/manualtests/Dockerfile b/inst/manualtests/Dockerfile new file mode 100644 index 00000000..deb02caa --- /dev/null +++ b/inst/manualtests/Dockerfile @@ -0,0 +1,15 @@ +FROM rocker/tidyverse:3.6.0 + +RUN mkdir /golem + +WORKDIR /golem + +COPY golem_*.tar.gz golem.tar.gz + +COPY script.R script.R + +RUN R -e "source('script.R')" + +EXPOSE 5555 + +CMD R -e "options('shiny.port'=5555,shiny.host='0.0.0.0');gogolele::run_app()" \ No newline at end of file diff --git a/inst/manualtests/script.R b/inst/manualtests/script.R index bd752810..6553727e 100644 --- a/inst/manualtests/script.R +++ b/inst/manualtests/script.R @@ -1,47 +1,89 @@ +# This scripts tests that {golem} can actually build an app + +install.packages("remotes") + +remotes::install_local("/golem/golem.tar.gz") + setwd("/tmp") -golem::create_golem( "gogolele",open = FALSE) + +library(golem) +packageVersion("golem") + +library(testthat) + +# Create the golem +golem::create_golem( "gogolele", open = FALSE) setwd("/tmp/gogolele/") + golem::set_golem_options() -golem::fill_desc( - pkg_name = "gogolele", - pkg_title = "PKG_TITLE", - pkg_description = "PKG_DESC.", - author_first_name = "AUTHOR_FIRST", - author_last_name = "AUTHOR_LAST", - author_email = "AUTHOR@MAIL.COM", - repo_url = NULL -) -golem::set_golem_options() -usethis::use_mit_license( name = "Golem User" ) -usethis::use_readme_rmd( open = FALSE ) -usethis::use_code_of_conduct() -usethis::use_lifecycle_badge( "Experimental" ) -usethis::use_news_md( open = FALSE ) -usethis::use_data_raw( name = "my_dataset", open = FALSE ) + + golem::use_recommended_tests() -golem::use_recommended_deps() golem::use_utils_ui() golem::use_utils_server() devtools::test() -golem::add_module( name = "my_first_module" ) -golem::add_module( name = "my_other_module" ) +golem::add_module( + name = "my_first_module", + ph_ui = "#ui_plop", + ph_server = "#server_plop", + open = FALSE +) + +mod1 <- readLines("R/mod_my_first_module.R") +mod1 <- append( + mod1, + c( + "actionButton(ns('go'), 'go'),", + "plotOutput(ns('plot'))" + ), + grep( + "ui_plop", + mod1 + ) +) +mod1 <- append( + mod1, + c( + "observeEvent(input$go, {", + " golem::invoke_js('alert', 'hey')", + "})", + "output$plot <- renderPlot({", + " plot(iris)", + "})" + ), + grep( + "server_plop", + mod1 + ) +) +write(mod1, "R/mod_my_first_module.R") ui <- readLines("R/app_ui.R") -ui[12] <- 'mod_my_first_module_ui("mod_my_first_module_ui_1"),\nmod_my_other_module_ui("my_other_module_ui_1")' +ui <- append( + ui, + c( + ',mod_my_first_module_ui("my_first_module_ui_1")' + ), + grep( + "h1", + ui + ) +) write(ui, "R/app_ui.R") -srv <- readLines("R/app_server.R") -srv[3] <- 'callModule(mod_my_first_module_server, "my_first_module_ui_1")\ncallModule(mod_my_other_module_server, "my_other_module_ui_1")' -write(srv, "R/app_server.R") - -usethis::use_package( "thinkr" ) - -golem::add_js_file( "script", open = FALSE) -golem::add_js_handler( "handlers", open = FALSE) -golem::add_css_file( "custom", open = FALSE) - -devtools::test() -remotes::install_local() - +server <- readLines("R/app_server.R") +server <- append( + server, + c( + 'callModule(mod_my_first_module_server, "my_first_module_ui_1")' + ), + grep( + "first level callModules", + server + ) +) +write(server, "R/app_server.R") +devtools::document() +remotes::install_local() \ No newline at end of file diff --git a/inst/manualtests/tests.sh b/inst/manualtests/tests.sh index 2d7748e1..e9a2a97c 100644 --- a/inst/manualtests/tests.sh +++ b/inst/manualtests/tests.sh @@ -2,8 +2,9 @@ R -e 'devtools::build("../../", path = ".")' -docker build -t golemmantest . +docker build -t golemmantest . --no-cache -docker run golemmantest +docker run -p 5555:5555 -d golemmantest && \ + sleep 2 && open http://localhost:5555 rm golem_*tar.gz From ef1898404fefe7fe121d781cf07c2bf7d370e7e5 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 7 Feb 2020 23:55:43 +0100 Subject: [PATCH 184/211] close #332 --- DESCRIPTION | 5 +- NAMESPACE | 7 +- R/add_deploy_helpers.R | 92 +++++++++++------ R/add_files.R | 116 ++++++--------------- R/add_modules.R | 31 ++---- R/add_r_files.R | 11 +- R/add_ressource_path.R | 17 ++-- R/bundle_resources.R | 2 +- R/create_golem.R | 2 +- R/desc.R | 12 ++- R/js.R | 3 +- R/use_favicon.R | 28 +++++- R/use_files.R | 53 ++++------ R/use_utils.R | 1 - R/utils.R | 128 ++++++++++++++++++++++-- man/add_files.Rd | 39 +++----- man/add_module.Rd | 16 +-- man/add_resource_path.Rd | 12 ++- man/addins.Rd | 2 +- man/amend_golem_config.Rd | 9 +- man/create_golem.Rd | 12 +-- man/dockerfiles.Rd | 59 ++++------- man/file_creation.Rd | 18 +--- man/fill_desc.Rd | 13 +-- man/golem.Rd | 8 +- man/golem_js.Rd | 14 ++- man/golem_opts.Rd | 38 ++----- man/use_files.Rd | 20 +--- man/use_recommended.Rd | 6 +- tests/testthat/test-add_files.R | 12 +-- tests/testthat/test-add_resource_path.R | 7 +- 31 files changed, 380 insertions(+), 413 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0bd4b044..4ca64e3e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -45,7 +45,6 @@ Imports: desc, dockerfiler, fs, - glue, here, htmltools, jsonlite, @@ -55,17 +54,17 @@ Imports: roxygen2, rstudioapi, shiny, - stats, testthat, - tools, usethis, utils, yaml Suggests: covr, devtools, + glue, knitr, pkgdown, + purrr, rcmdcheck, rmarkdown, rsconnect, diff --git a/NAMESPACE b/NAMESPACE index 072683f1..1836f90b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -76,8 +76,7 @@ importFrom(fs,file_temp) importFrom(fs,path) importFrom(fs,path_abs) importFrom(fs,path_file) -importFrom(glue,glue) -importFrom(htmltools,includeScript) +importFrom(htmltools,htmlDependency) importFrom(htmltools,save_html) importFrom(htmltools,tags) importFrom(jsonlite,fromJSON) @@ -87,7 +86,6 @@ importFrom(pkgload,pkg_path) importFrom(remotes,dev_package_deps) importFrom(remotes,package_deps) importFrom(rlang,enquo) -importFrom(rlang,is_empty) importFrom(roxygen2,roxygenise) importFrom(rstudioapi,getSourceEditorContext) importFrom(rstudioapi,isAvailable) @@ -96,11 +94,10 @@ importFrom(rstudioapi,navigateToFile) importFrom(rstudioapi,openProject) importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) -importFrom(stats,setNames) +importFrom(shiny,includeScript) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) -importFrom(tools,file_path_sans_ext) importFrom(usethis,proj_set) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index d648c762..3e5b52a4 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -42,13 +42,7 @@ add_rstudio_files <- function( ) ) - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet( - sprintf("Go to %s", where) - ) - } + open_or_go_to(where, open) } #' Add an app.R at the root of your package to deploy on RStudio Connect @@ -186,8 +180,11 @@ add_dockerfile <- function( dock$EXPOSE(port) dock$CMD( - glue::glue( - "R -e \"options('shiny.port'={port},shiny.host='{host}');{read.dcf(path)[1]}::run_app()\"" + sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + read.dcf(path)[1] ) ) @@ -247,8 +244,9 @@ add_dockerfile_shinyproxy <- function( ) dock$EXPOSE(3838) - dock$CMD(glue::glue( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');{read.dcf(path)[1]}::run_app()\"]" + 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) @@ -307,8 +305,9 @@ add_dockerfile_heroku <- function( ) dock$CMD( - glue::glue( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');{read.dcf(path)[1]}::run_app()\"" + sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + read.dcf(path)[1] ) ) dock$write(output) @@ -321,26 +320,30 @@ add_dockerfile_heroku <- function( apps_h <- gsub( "\\.", "-", - glue("{read.dcf(path)[1]}-{read.dcf(path)[1,][['Version']]}") + 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( - glue("heroku create {apps_h}") + sprintf("heroku create %s", apps_h) ) cat_line( - glue("heroku container:push web --app {apps_h}") + sprintf("heroku container:push web --app %s", apps_h) ) cat_line( - glue("heroku container:release web --app {apps_h}") + sprintf("heroku container:release web --app %s", apps_h) ) cat_line( - glue("heroku open --app {apps_h}") + sprintf("heroku open --app %s", apps_h) ) cat_red_bullet("Be sure to have the heroku CLI installed.") cat_red_bullet( - glue("You can replace {apps_h} with another app name.") + sprintf("You can replace %s with another app name.", apps_h) ) if (open) { if (rstudioapi::isAvailable()) { @@ -354,14 +357,19 @@ add_dockerfile_heroku <- function( } -#' @importFrom glue glue -alert_build <- function(path, output, build_golem_from_source){ - +alert_build <- function( + path, + output, + build_golem_from_source +){ cat_created(output, "Dockerfile") if ( ! build_golem_from_source ){ cat_red_bullet( - glue::glue( - "Be sure to keep your {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz file (generated using `devtools::build()` ) in the same folder as the {basename(output)} file generated" + sprintf( + "Be sure to keep your %s_%s.tar.gz file (generated using `devtools::build()` ) in the same folder as the %s file generated", + read.dcf(path)[1], + read.dcf(path)[1,][['Version']], + basename(output) ) ) } @@ -382,7 +390,6 @@ alert_build <- function(path, output, build_golem_from_source){ #' @importFrom utils installed.packages packageVersion #' @importFrom remotes dev_package_deps #' @importFrom desc desc_get_deps -#' @importFrom stats setNames #' @noRd dock_from_desc <- function( path = "DESCRIPTION", @@ -442,10 +449,12 @@ dock_from_desc <- function( ) packages_with_version <- packages_with_version[ packages_with_version$package %in% packages_on_cran, - ] + ] - packages_on_cran <- - setNames(packages_with_version$installed, packages_with_version$package) + packages_on_cran <- set_name( + packages_with_version$installed, + packages_with_version$package + ) dock <- dockerfiler::Dockerfile$new(FROM = FROM, AS = AS) @@ -502,7 +511,13 @@ dock_from_desc <- function( .[c('repo', 'username', 'sha')] }))) - nn <- glue::glue("{nn$username}/{nn$repo}@{nn$sha}") + nn <- sprintf( + "%s/%s@%s", + nn$username, + nn$repo, + nn$sha + ) + pong <- mapply(function(dock, ver, nm){ res <- dock$RUN( @@ -521,17 +536,28 @@ dock_from_desc <- function( if ( update_tar_gz ){ old_version <- list.files( - pattern = glue::glue("{read.dcf(path)[1]}_.+.tar.gz"), + pattern = sprintf("%s_.+.tar.gz", read.dcf(path)[1]), full.names = TRUE ) if ( length(old_version) > 0){ lapply(old_version, file.remove) lapply(old_version, unlink, force = TRUE) - cat_red_bullet(glue::glue("{paste(old_version,collapse = ', ')} were removed from folder")) + cat_red_bullet( + sprintf( + "%s were removed from folder", + paste(old_version, collapse = ', ') + ) + ) } - - cat_green_tick(glue::glue(" {read.dcf(path)[1]}_{read.dcf(path)[1,][['Version']]}.tar.gz created.")) + + cat_green_tick( + sprintf( + " %s_%s.tar.gz created.", + read.dcf(path)[1], + read.dcf(path)[1,][['Version']] + ) + ) devtools::build(path = ".") } # we use an already builded tar.gz file diff --git a/R/add_files.R b/R/add_files.R index 0df5ee0d..dc104428 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -8,10 +8,8 @@ #' @param with_doc_ready Should the default file include `$( document ).ready()`? #' @export #' @rdname add_files -#' @importFrom glue glue #' @importFrom cli cat_bullet #' @importFrom utils file.edit -#' @importFrom tools file_path_sans_ext #' @importFrom fs path_abs path file_create file_exists add_js_file <- function( name, @@ -45,7 +43,7 @@ add_js_file <- function( dir <- path_abs(dir) where <- path( - dir, glue::glue("{name}.js") + dir, sprintf("%s.js", name) ) file_create(where) @@ -59,31 +57,15 @@ add_js_file <- function( write_there("});") } - cat_created(where) - - if ( - file_exists( - paste0(pkg, "/DESCRIPTION") - ) - ) { - if (dir != "inst/app/www"){ - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - ) - } else { - cat_green_tick( - 'File automatically linked in `golem_add_external_resources()`.' - ) - } - } + file_created_dance( + where, + after_creation_message_js, + pkg, + dir, + name, + open + ) - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } } #' @export @@ -120,48 +102,30 @@ add_js_handler <- function( dir <- path_abs(dir) where <- file.path( - dir, glue::glue("{name}.js") + dir, sprintf("%s.js", name) ) - + file_create(where) write_there <- function(...){ write(..., file = where, append = TRUE) } - glue <- function(...){ - glue::glue(..., .open = "%", .close = "%") - } + write_there("$( document ).ready(function() {") write_there(" Shiny.addCustomMessageHandler('fun', function(arg) {") write_there(" ") write_there(" })") write_there("});") - cat_created(where) - - if ( - file_exists( - paste0(pkg, "/DESCRIPTION") - ) - ) { - if (dir != "inst/app/www"){ - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) - ) - } else { - cat_green_tick( - 'File automatically linked in `golem_add_external_resources()`.' - ) - } - } + file_created_dance( + where, + after_creation_message_js, + pkg, + dir, + name, + open + ) - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } } #' @export @@ -198,42 +162,26 @@ add_css_file <- function( dir <- path_abs(dir) where <- path( - dir, glue::glue("{name}.css") + dir, sprintf( + "%s.css", + name + ) ) file_create(where) - - cat_created(where) - - if ( - file_exists( - paste0(pkg, "/DESCRIPTION") - ) - ) { - if (dir != "inst/app/www"){ - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/{name}.css")`' - ) - ) - } else { - cat_green_tick( - 'File automatically linked in `golem_add_external_resources()`.' - ) - } - } - - if (rstudioapi::isAvailable() & open ){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } + file_created_dance( + where, + after_creation_message_css, + pkg, + dir, + name, + open + ) } #' @export #' @rdname add_files -#' @importFrom glue glue #' @importFrom fs path_abs file_create add_ui_server_files <- function( pkg = get_golem_wd(), diff --git a/R/add_modules.R b/R/add_modules.R index c90d2abf..56ecf9cf 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -14,10 +14,8 @@ #' @param ph_ui,ph_server Texts to insert inside the modules UI and server. For advanced use. #' @note This function will prefix the `name` argument with `mod_`. #' @export -#' @importFrom glue glue #' @importFrom cli cat_bullet #' @importFrom utils file.edit -#' @importFrom tools file_path_sans_ext #' @importFrom fs path_abs path file_create add_module <- function( name, @@ -64,25 +62,22 @@ add_module <- function( write_there <- function(...){ write(..., file = where, append = TRUE) } - glue <- function(...){ - glue::glue(..., .open = "%", .close = "%") - } - write_there(glue("#' %name% UI Function")) + write_there(sprintf("#' %s UI Function", name)) write_there("#'") write_there("#' @description A shiny Module.") write_there("#'") write_there("#' @param id,input,output,session Internal parameters for {shiny}.") write_there("#'") if (export){ - write_there(glue("#' @rdname mod_%name%")) + write_there(sprintf("#' @rdname mod_%s", name)) write_there("#' @export ") } else { write_there("#' @noRd ") } write_there("#'") write_there("#' @importFrom shiny NS tagList ") - write_there(glue("mod_%name%_ui <- function(id){")) + write_there(sprintf("mod_%s_ui <- function(id){", name)) write_there(" ns <- NS(id)") write_there(" tagList(") write_there(ph_ui) @@ -90,34 +85,26 @@ add_module <- function( write_there("}") write_there(" ") - write_there(glue("#' %name% Server Function")) + write_there(sprintf("#' %s Server Function", name)) write_there("#'") if (export){ - write_there(glue("#' @rdname mod_%name%")) + write_there(sprintf("#' @rdname mod_%s", name)) write_there("#' @export ") } else { write_there("#' @noRd ") } - write_there(glue("mod_%name%_server <- function(input, output, session){")) + write_there(sprintf("mod_%s_server <- function(input, output, session){", name)) write_there(" ns <- session$ns") write_there(ph_server) write_there("}") write_there(" ") write_there("## To be copied in the UI") - write_there(glue('# mod_%name%_ui("%name%_ui_1")')) + write_there(sprintf('# mod_%s_ui("%s_ui_1")', name, name)) write_there(" ") write_there("## To be copied in the server") - write_there(glue('# callModule(mod_%name%_server, "%name%_ui_1")')) + write_there(sprintf('# callModule(mod_%s_server, "%s_ui_1")', name, name)) write_there(" ") cat_created(where) - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_bullet( - glue("Go to %where%"), - bullet = "square_small_filled", - bullet_col = "red" - ) - } + open_or_go_to(where, open) } diff --git a/R/add_r_files.R b/R/add_r_files.R index 0883d98d..280dba83 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -1,4 +1,3 @@ -#' @importFrom tools file_path_sans_ext #' @importFrom fs path_abs path file_create add_r_files <- function( name, @@ -32,16 +31,8 @@ add_r_files <- function( ) file_create(where) + open_or_go_to(where, open) - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_bullet( - glue("Go to {where}"), - bullet = "square_small_filled", - bullet_col = "red" - ) - } } #' Add fct_ and utils_ files diff --git a/R/add_ressource_path.R b/R/add_ressource_path.R index 8ec68a5b..30278a9e 100644 --- a/R/add_ressource_path.R +++ b/R/add_ressource_path.R @@ -1,26 +1,25 @@ #' Add resource path #' -#' @param prefix The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory. -#' @param directory_path The directory that contains the static resources to be served. -#' @param warn_empty Boolean. By default FALSE, if TRUE diplay message if directory empty +#' @inheritParams shiny::addResourcePath +#' @param warn_empty Boolean. By default FALSE, if TRUE display message if directory is empty. #' -#' @importFrom rlang is_empty #' @importFrom shiny addResourcePath #' #' @export #' add_resource_path <- function( prefix, - directory_path, + directoryPath, warn_empty = FALSE ){ - list_f <- is_empty(list.files(path = directory_path)) + list_f <- length( + list.files(path = directory_path) + ) == 0 if ( list_f & warn_empty ) { - message("Unable to add your directory because it is empty") + warning("No resources to add from resource path (directory empty).") } else { - addResourcePath(prefix, directory_path) - message("Resource path added to ", prefix, "/", directory_path) + addResourcePath(prefix, directoryPath) } } diff --git a/R/bundle_resources.R b/R/bundle_resources.R index cc630c6e..f6cd6ee4 100644 --- a/R/bundle_resources.R +++ b/R/bundle_resources.R @@ -8,7 +8,7 @@ #' @param path The path to the folder where the external files are located. #' @param app_title The title of the app, to be used as an application title. #' @inheritParams htmltools::htmlDependency -#' +#' @importFrom htmltools htmlDependency #' @export bundle_resources <- function( path, diff --git a/R/create_golem.R b/R/create_golem.R index 9ac4f5ab..822e99b6 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -7,7 +7,7 @@ #' @param open Boolean open the created project #' @param package_name Package name to use.By default it's `basename(path)` but if path == '.' and `package_name` #' not explicitly given, then `basename(getwd())` will be used. -#' @param without_comments Poolean start project without golem comments +#' @param without_comments Boolean start project without golem comments #' @param ... not used #' #' @importFrom cli cat_rule cat_line diff --git a/R/desc.R b/R/desc.R index 47d01705..29429813 100644 --- a/R/desc.R +++ b/R/desc.R @@ -10,7 +10,6 @@ #' @param pkg Path to look for the DESCRIPTION #' #' @importFrom desc description -#' @importFrom glue glue #' @importFrom cli cat_bullet #' @importFrom fs path path_abs #' @export @@ -32,7 +31,12 @@ fill_desc <- function( ) desc$set( "Authors@R", - glue("person('{author_first_name}', '{author_last_name}', email = '{author_email}', role = c('cre', 'aut'))") + sprintf( + "person('%s', '%s', email = '%s', role = c('cre', 'aut'))", + author_first_name, + author_last_name, + author_email + ) ) desc$del( keys = "Maintainer" @@ -60,7 +64,9 @@ fill_desc <- function( repo_url, desc$set( "BugReports", - glue("{repo_url}/issues" + sprintf( + "%s/issues", + repo_url ) ) ) diff --git a/R/js.R b/R/js.R index 1b82c51d..c6183a21 100644 --- a/R/js.R +++ b/R/js.R @@ -33,8 +33,7 @@ #' #' @export #' @rdname golem_js -#' @importFrom htmltools includeScript - +#' @importFrom shiny includeScript activate_js <- function(){ includeScript( system.file("utils/golem-js.js", package = "golem") diff --git a/R/use_favicon.R b/R/use_favicon.R index f1444dcf..25179626 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -24,7 +24,7 @@ use_favicon <- function( path <- golem_sys("shinyexample/inst/app/www", "favicon.ico") } - ext <- tools::file_ext(path) + ext <- file_ext(path) stop_if_not( ext, ~ .x %in% c("png",'ico'), @@ -36,7 +36,14 @@ use_favicon <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - to <- path(path_abs(pkg), "inst/app/www", glue::glue("favicon.{ext}")) + to <- path( + path_abs(pkg), + "inst/app/www", + sprintf( + "favicon.%s", + ext + ) + ) if (! (path == to)) { file_copy( @@ -44,11 +51,24 @@ use_favicon <- function( to, overwrite = TRUE ) - cat_green_tick(glue::glue("favicon.{ext} created at {to}")) + cat_green_tick( + sprintf( + "favicon.%s created at %s", + ext, + to + ) + ) } cat_rule("Change / Add in the app_ui function") - cat_line(darkgrey(glue('golem::favicon("www/favicon.{ext}")'))) + cat_line( + darkgrey( + sprintf( + 'golem::favicon("www/favicon.%s")', + ext + ) + ) + ) cat_line() } diff --git a/R/use_files.R b/R/use_files.R index 7ea403d5..30dc2eb9 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -7,7 +7,6 @@ #' @param dir Path to the dir where the file while be created. #' @export #' @rdname use_files -#' @importFrom glue glue #' @importFrom cli cat_bullet #' @importFrom fs path_abs path use_external_js_file <- function( @@ -21,7 +20,7 @@ use_external_js_file <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - new_file <- glue::glue("{name}.js") + new_file <- sprintf("%s.js", name) dir_created <- create_if_needed( dir, type = "directory" @@ -39,11 +38,8 @@ use_external_js_file <- function( where <- path( dir, new_file ) - # if ( !check_file_exist(where) ) { - # return(invisible(FALSE)) - # } - if ( tools::file_ext(url) != "js") { + if ( file_ext(url) != "js") { cat_red_bullet( "File not added (URL must end with .js extension)" ) @@ -52,18 +48,15 @@ use_external_js_file <- function( utils::download.file(url, where) - cat_created(where) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.js")`' - ) + file_created_dance( + where, + after_creation_message_js, + pkg, + dir, + name, + open ) - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } } #' @export @@ -80,7 +73,7 @@ use_external_css_file <- function( old <- setwd(path_abs(pkg)) on.exit(setwd(old)) - new_file <- glue::glue("{name}.css") + new_file <- sprintf("%s.css", name) dir_created <- create_if_needed( dir, type = "directory" @@ -98,11 +91,8 @@ use_external_css_file <- function( where <- path( dir, new_file ) - # if ( !check_file_exist(where) ) { - # return(invisible(FALSE)) - # } - - if ( tools::file_ext(url) != "css") { + + if ( file_ext(url) != "css") { cat_red_bullet( "File not added (URL must end with .css extension)" ) @@ -111,17 +101,14 @@ use_external_css_file <- function( utils::download.file(url, where) - cat_created(where) - cat_red_bullet( - glue::glue( - 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/{name}.css")`' - ) + file_created_dance( + where, + after_creation_message_css, + pkg, + dir, + name, + open ) - - if (rstudioapi::isAvailable() & open){ - rstudioapi::navigateToFile(where) - } else { - cat_red_bullet(glue::glue("Go to {where}")) - } + } diff --git a/R/use_utils.R b/R/use_utils.R index 90cd0b1d..87fbe748 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -11,7 +11,6 @@ #' @rdname utils_files #' #' @importFrom cli cat_bullet -#' @importFrom glue glue #' @importFrom utils capture.output use_utils_ui <- function( pkg = get_golem_wd() diff --git a/R/utils.R b/R/utils.R index e0414197..f46fa456 100644 --- a/R/utils.R +++ b/R/utils.R @@ -20,13 +20,6 @@ darkgrey <- function(x) { dir_not_exist <- Negate(dir_exists) file_not_exist <- Negate(file_exists) -# is_package <- function(path){ -# x <- attempt::attempt({ -# pkgload::pkg_path() -# }) -# !attempt::is_try_error(x) -# } - #' @importFrom fs dir_create file_create create_if_needed <- function( path, @@ -109,7 +102,6 @@ remove_comments <- function(file) { } #' @importFrom cli cat_bullet - cat_green_tick <- function(...){ cat_bullet( ..., @@ -151,12 +143,128 @@ cat_exists <- function(where){ ) } -cat_created <- function(where, file = "File"){ - cat_green_tick(glue::glue("{file} created at {where}")) +cat_created <- function( + where, + file = "File" +){ + cat_green_tick( + sprintf( + "%s created at %s", + file, + where + ) + ) +} + +# File made dance + +cat_automatically_linked <- function(){ + cat_green_tick( + 'File automatically linked in `golem_add_external_resources()`.' + ) } +open_or_go_to <- function( + where, + open +){ + if ( + rstudioapi::isAvailable() && + open && + rstudioapi::hasFun("navigateToFile") + ){ + rstudioapi::navigateToFile(where) + } else { + cat_red_bullet( + sprintf( + "Go to %s", + where + ) + ) + } +} + +desc_exist <- function(pkg){ + file_exists( + paste0(pkg, "/DESCRIPTION") + ) +} + +after_creation_message_js <- function( + pkg, + dir, + name +){ + if ( + desc_exist(pkg) + ) { + if (dir != "inst/app/www"){ + cat_red_bullet( + sprintf( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/%s.js")`', + name + ) + ) + } else { + cat_automatically_linked() + } + } +} +after_creation_message_css <- function( + pkg, + dir, + name +){ + if ( + desc_exist(pkg) + ) { + if (dir != "inst/app/www"){ + cat_red_bullet( + sprintf( + 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/.css")`', + name + ) + ) + } else { + cat_automatically_linked() + } + } +} + +file_created_dance <- function( + where, + fun, + pkg, + dir, + name, + open +){ + cat_created(where) + + fun(pkg, dir, name) + + open_or_go_to(where, open) +} + +# Minor toolings + if_not_null <- function(x, ...){ if (! is.null(x)){ force(...) } } + +set_name <- function(x, y){ + names(x) <- y + x +} + +# FROM tools::file_path_sans_ext() & tools::file_ext +file_path_sans_ext <- function(x){ + sub("([^.]+)\\.[[:alnum:]]+$", "\\1", x) +} + +file_ext <- function (x) { + pos <- regexpr("\\.([[:alnum:]]+)$", x) + ifelse(pos > -1L, substring(x, pos + 1L), "") +} diff --git a/man/add_files.Rd b/man/add_files.Rd index 4e985988..e85db825 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -7,32 +7,17 @@ \alias{add_ui_server_files} \title{Create Files} \usage{ -add_js_file( - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE, - with_doc_ready = TRUE -) - -add_js_handler( - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -) - -add_css_file( - name, - 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) +add_js_file(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE, with_doc_ready = TRUE) + +add_js_handler(name, pkg = get_golem_wd(), dir = "inst/app/www", + open = TRUE, dir_create = TRUE) + +add_css_file(name, 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{ \item{name}{The name of the module} @@ -45,7 +30,7 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", dir_create = TRUE) \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} -\item{with_doc_ready}{Should the default file include \verb{$( document ).ready()}?} +\item{with_doc_ready}{Should the default file include \code{$( document ).ready()}?} } \description{ These functions create files inside the \code{inst/app} folder. diff --git a/man/add_module.Rd b/man/add_module.Rd index 94f19703..5b40a6b6 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -4,17 +4,9 @@ \alias{add_module} \title{Create a module} \usage{ -add_module( - name, - pkg = get_golem_wd(), - open = TRUE, - dir_create = TRUE, - fct = NULL, - utils = NULL, - export = FALSE, - ph_ui = " ", - ph_server = " " -) +add_module(name, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE, fct = NULL, utils = NULL, export = FALSE, + ph_ui = " ", ph_server = " ") } \arguments{ \item{name}{The name of the module} @@ -34,7 +26,7 @@ add_module( \item{ph_ui, ph_server}{Texts to insert inside the modules UI and server. For advanced use.} } \description{ -This function creates a module inside the \verb{R/} folder, based +This function creates a module inside the \code{R/} folder, based on a specific module structure. This function can be used outside of a {golem} project. } diff --git a/man/add_resource_path.Rd b/man/add_resource_path.Rd index 061a62a3..b047bda5 100644 --- a/man/add_resource_path.Rd +++ b/man/add_resource_path.Rd @@ -4,14 +4,18 @@ \alias{add_resource_path} \title{Add resource path} \usage{ -add_resource_path(prefix, directory_path, warn_empty = FALSE) +add_resource_path(prefix, directoryPath, warn_empty = FALSE) } \arguments{ -\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' means that any request paths that begin with '/foo' will be mapped to the given directory.} +\item{prefix}{The URL prefix (without slashes). Valid characters are a-z, +A-Z, 0-9, hyphen, period, and underscore. For example, a value of 'foo' +means that any request paths that begin with '/foo' will be mapped to the +given directory.} -\item{directory_path}{The directory that contains the static resources to be served.} +\item{directoryPath}{The directory that contains the static resources to be +served.} -\item{warn_empty}{Boolean. By default FALSE, if TRUE diplay message if directory empty} +\item{warn_empty}{Boolean. By default FALSE, if TRUE display message if directory is empty.} } \description{ Add resource path diff --git a/man/addins.Rd b/man/addins.Rd index 6e626dfa..667a0aa6 100644 --- a/man/addins.Rd +++ b/man/addins.Rd @@ -30,6 +30,6 @@ go_to_run_app() } \description{ \code{insert_ns()} takes a selected character vector and wrap it in \code{ns()} -The series of \verb{go_to_*()} addins help you go to +The series of \code{go_to_*()} addins help you go to common files used in developing a \code{{golem}} application. } diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd index 92393bc0..5ec8185c 100644 --- a/man/amend_golem_config.Rd +++ b/man/amend_golem_config.Rd @@ -4,13 +4,8 @@ \alias{amend_golem_config} \title{Amend golem config file} \usage{ -amend_golem_config( - key, - value, - config = "default", - pkg = get_golem_wd(), - talkative = TRUE -) +amend_golem_config(key, value, config = "default", + pkg = get_golem_wd(), talkative = TRUE) } \arguments{ \item{key}{key of the value to add in \code{config}} diff --git a/man/create_golem.Rd b/man/create_golem.Rd index 04b0db5d..150f5bdb 100644 --- a/man/create_golem.Rd +++ b/man/create_golem.Rd @@ -4,14 +4,8 @@ \alias{create_golem} \title{Create a package for Shiny App using golem} \usage{ -create_golem( - path, - check_name = TRUE, - open = TRUE, - package_name = basename(path), - without_comments = FALSE, - ... -) +create_golem(path, check_name = TRUE, open = TRUE, + package_name = basename(path), without_comments = FALSE, ...) } \arguments{ \item{path}{Name of the folder to create the package in. This will also be @@ -25,7 +19,7 @@ the package name from being checked.} \item{package_name}{Package name to use.By default it's \code{basename(path)} but if path == '.' and \code{package_name} not explicitly given, then \code{basename(getwd())} will be used.} -\item{without_comments}{Poolean start project without golem comments} +\item{without_comments}{Boolean start project without golem comments} \item{...}{not used} } diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 1c58c424..51e51786 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -6,49 +6,26 @@ \alias{add_dockerfile_heroku} \title{Create a Dockerfile for Shiny App} \usage{ -add_dockerfile( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), - as = NULL, - port = 80, - host = "0.0.0.0", - sysreqs = TRUE, - repos = "https://cran.rstudio.com/", - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE -) +add_dockerfile(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, + host = "0.0.0.0", sysreqs = TRUE, + repos = "https://cran.rstudio.com/", expand = FALSE, open = TRUE, + update_tar_gz = TRUE, build_golem_from_source = TRUE) -add_dockerfile_shinyproxy( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), - as = NULL, - sysreqs = TRUE, - repos = "https://cran.rstudio.com/", - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE -) +add_dockerfile_shinyproxy(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), as = NULL, + sysreqs = TRUE, repos = "https://cran.rstudio.com/", + expand = FALSE, open = TRUE, update_tar_gz = TRUE, + build_golem_from_source = TRUE) -add_dockerfile_heroku( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), - as = NULL, - sysreqs = TRUE, - repos = "https://cran.rstudio.com/", - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE -) +add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", + pkg = get_golem_wd(), from = paste0("rocker/r-ver:", + R.Version()$major, ".", R.Version()$minor), as = NULL, + sysreqs = TRUE, repos = "https://cran.rstudio.com/", + expand = FALSE, open = TRUE, update_tar_gz = TRUE, + build_golem_from_source = TRUE) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} diff --git a/man/file_creation.Rd b/man/file_creation.Rd index eebaa65e..dea0be83 100644 --- a/man/file_creation.Rd +++ b/man/file_creation.Rd @@ -5,21 +5,11 @@ \alias{add_utils} \title{Add fct_ and utils_ files} \usage{ -add_fct( - name, - module = NULL, - pkg = get_golem_wd(), - open = TRUE, - dir_create = TRUE -) +add_fct(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) -add_utils( - name, - module = NULL, - pkg = get_golem_wd(), - open = TRUE, - dir_create = TRUE -) +add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, + dir_create = TRUE) } \arguments{ \item{name}{The name of the file} diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index be4802f9..8a6e3407 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -4,16 +4,9 @@ \alias{fill_desc} \title{Fill your description} \usage{ -fill_desc( - pkg_name, - pkg_title, - pkg_description, - author_first_name, - author_last_name, - author_email, - repo_url = NULL, - pkg = get_golem_wd() -) +fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, + author_last_name, author_email, repo_url = NULL, + pkg = get_golem_wd()) } \arguments{ \item{pkg_name}{The name of the package} diff --git a/man/golem.Rd b/man/golem.Rd index 2e3b56a5..f41867c2 100644 --- a/man/golem.Rd +++ b/man/golem.Rd @@ -17,13 +17,13 @@ Useful links: } \author{ -\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (\href{https://orcid.org/0000-0003-0671-9270}{ORCID}) +\strong{Maintainer}: Vincent Guyader \email{vincent@thinkr.fr} (0000-0003-0671-9270) Authors: \itemize{ - \item Colin Fay \email{contact@colinfay.me} (\href{https://orcid.org/0000-0001-7343-1846}{ORCID}) - \item Sébastien Rochette \email{sebastien@thinkr.fr} (\href{https://orcid.org/0000-0002-1565-9313}{ORCID}) - \item Cervan Girard \email{cervan@thinkr.fr} (\href{https://orcid.org/0000-0002-4816-4624}{ORCID}) + \item Colin Fay \email{contact@colinfay.me} (0000-0001-7343-1846) + \item Sébastien Rochette \email{sebastien@thinkr.fr} (0000-0002-1565-9313) + \item Cervan Girard \email{cervan@thinkr.fr} (0000-0002-4816-4624) } Other contributors: diff --git a/man/golem_js.Rd b/man/golem_js.Rd index 189ac7f7..231e2af1 100644 --- a/man/golem_js.Rd +++ b/man/golem_js.Rd @@ -23,14 +23,18 @@ invoke_js(fun, ..., session = shiny::getDefaultReactiveDomain()) \item{hideid}{Hide an element with the id provided.} \item{showclass}{Same as showid, but with class.} \item{hideclass}{Same as hideid, but with class.} -\item{showhref}{Same as showid, but with \verb{a[href*=}} -\item{hidehref}{Same as hideid, but with \verb{a[href*=}} +\item{showhref}{Same as showid, but with \code{a[href*=}} +\item{hidehref}{Same as hideid, but with \code{a[href*=}} \item{clickon}{Click on an element. The full jQuery selector has to be used.} \item{disable}{Add "disabled" to an element. The full jQuery selector has to be used.} \item{reable}{Remove "disabled" from an element. The full jQuery selector has to be used.} -\item{alert}{Open an alert box with the message provided.} -\item{prompt}{Open a prompt box with the message provided.} -\item{confirm}{Open a confirm box with the message provided.} +\item{alert}{Open an alert box with the message(s) provided.} +\item{prompt}{Open a prompt box with the message(s) provided. This function takes +a list with message and id \code{list(message = "", id = "")}. The output of the prompt +will be sent to \code{input$id}.} +\item{confirm}{Open a confirm box with the message provided. This function takes +a list with message and id \code{list(message = "", id = "")}. The output of the prompt +will be sent to \code{input$id}.} }} } \description{ diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index e43ddbc2..4b3e4428 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -10,41 +10,25 @@ \alias{get_golem_version} \title{\code{{golem}} options} \usage{ -set_golem_options( - golem_name = pkgload::pkg_name(), +set_golem_options(golem_name = pkgload::pkg_name(), golem_version = pkgload::pkg_version(), - golem_wd = pkgload::pkg_path(), - app_prod = FALSE, - talkative = TRUE -) + golem_wd = pkgload::pkg_path(), app_prod = FALSE, talkative = TRUE) set_golem_wd(path = pkgload::pkg_path(), talkative = TRUE) -set_golem_name( - name = pkgload::pkg_name(), - path = pkgload::pkg_path(), - talkative = TRUE -) +set_golem_name(name = pkgload::pkg_name(), path = pkgload::pkg_path(), + talkative = TRUE) -set_golem_version( - version = pkgload::pkg_version(), - path = pkgload::pkg_path(), - talkative = TRUE -) +set_golem_version(version = pkgload::pkg_version(), + path = pkgload::pkg_path(), talkative = TRUE) get_golem_wd(use_parent = TRUE, path = pkgload::pkg_path()) -get_golem_name( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = pkgload::pkg_path() -) - -get_golem_version( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = pkgload::pkg_path() -) +get_golem_name(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, path = pkgload::pkg_path()) + +get_golem_version(config = Sys.getenv("R_CONFIG_ACTIVE", "default"), + use_parent = TRUE, path = pkgload::pkg_path()) } \arguments{ \item{golem_name}{Name of the current golem.} diff --git a/man/use_files.Rd b/man/use_files.Rd index 1436af71..4444d963 100644 --- a/man/use_files.Rd +++ b/man/use_files.Rd @@ -5,23 +5,11 @@ \alias{use_external_css_file} \title{Use Files} \usage{ -use_external_js_file( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -) +use_external_js_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) -use_external_css_file( - url, - name, - pkg = get_golem_wd(), - dir = "inst/app/www", - open = TRUE, - dir_create = TRUE -) +use_external_css_file(url, name, pkg = get_golem_wd(), + dir = "inst/app/www", open = TRUE, dir_create = TRUE) } \arguments{ \item{url}{String representation of URL for the file to be downloaded} diff --git a/man/use_recommended.Rd b/man/use_recommended.Rd index 4ee669cf..b28f3489 100644 --- a/man/use_recommended.Rd +++ b/man/use_recommended.Rd @@ -5,10 +5,8 @@ \alias{use_recommended_tests} \title{Add recommended elements} \usage{ -use_recommended_deps( - pkg = get_golem_wd(), - recommended = c("shiny", "DT", "attempt", "glue", "htmltools", "golem") -) +use_recommended_deps(pkg = get_golem_wd(), recommended = c("shiny", + "DT", "attempt", "glue", "htmltools", "golem")) use_recommended_tests(pkg = get_golem_wd()) } diff --git a/tests/testthat/test-add_files.R b/tests/testthat/test-add_files.R index b0fdd3c2..c4522f2a 100644 --- a/tests/testthat/test-add_files.R +++ b/tests/testthat/test-add_files.R @@ -3,14 +3,14 @@ context("test-add_file function") expect_add_file <- function( fun, ext, - pkg, + pak, fp ){ name <- rand_name() # Be sure to remove all files in case there are remove_files("inst/app/www", ext) # Launch the function - fun(name, pkg = pkg, open = FALSE) + fun(name, pkg = pak, open = FALSE) # Test that the file exists expect_exists(file.path("inst/app/www", paste0(name,".", ext))) # Check that the file exsts @@ -19,7 +19,7 @@ expect_add_file <- function( # Try another file in another dir bis <- paste0(name, rand_name()) - fun(bis, pkg = pkg, open = FALSE, dir = normalizePath(fp)) + fun(bis, pkg = pak, open = FALSE, dir = normalizePath(fp)) expect_exists( normalizePath(fp) ) ff <- list.files( normalizePath(fp), pattern = bis) expect_equal(tools::file_ext(ff), ext) @@ -41,19 +41,19 @@ test_that("add_files", { expect_add_file( add_css_file, ext = "css", - pkg = pkg, + pak = pkg, fp = fp ) expect_add_file( add_js_file, ext = "js", - pkg = pkg, + pak = pkg, fp = fp ) expect_add_file( add_js_handler, ext = "js", - pkg = pkg, + pak = pkg, fp = fp ) diff --git a/tests/testthat/test-add_resource_path.R b/tests/testthat/test-add_resource_path.R index fd7bc806..f3f8e62b 100644 --- a/tests/testthat/test-add_resource_path.R +++ b/tests/testthat/test-add_resource_path.R @@ -1,11 +1,8 @@ test_that("add_resource_path", { withr::with_dir(pkg, { - expect_message( - add_resource_path("", "", warn_empty = TRUE), - "Unable to add your directory because it is empty" + expect_warning( + add_resource_path("", "", warn_empty = TRUE) ) }) - - }) From 9468dda821b44b38f63c863573b0dc8f008e98af Mon Sep 17 00:00:00 2001 From: colin Date: Sat, 8 Feb 2020 00:18:53 +0100 Subject: [PATCH 185/211] Testing that the app has no error on creation. Close #290 --- DESCRIPTION | 2 +- R/add_ressource_path.R | 2 +- inst/manualtests/script.R | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4ca64e3e..0e4d3b50 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9600 +Version: 0.1.0.9700 Authors@R: c(person(given = "Vincent", family = "Guyader", diff --git a/R/add_ressource_path.R b/R/add_ressource_path.R index 30278a9e..e8b18b38 100644 --- a/R/add_ressource_path.R +++ b/R/add_ressource_path.R @@ -14,7 +14,7 @@ add_resource_path <- function( ){ list_f <- length( - list.files(path = directory_path) + list.files(path = directoryPath) ) == 0 if ( list_f & warn_empty ) { diff --git a/inst/manualtests/script.R b/inst/manualtests/script.R index 6553727e..b2cf6609 100644 --- a/inst/manualtests/script.R +++ b/inst/manualtests/script.R @@ -15,13 +15,16 @@ library(testthat) golem::create_golem( "gogolele", open = FALSE) setwd("/tmp/gogolele/") +usethis::use_mit_license("Golem") +devtools::check() + golem::set_golem_options() golem::use_recommended_tests() +golem::use_recommended_deps() golem::use_utils_ui() golem::use_utils_server() -devtools::test() golem::add_module( name = "my_first_module", @@ -49,7 +52,7 @@ mod1 <- append( " golem::invoke_js('alert', 'hey')", "})", "output$plot <- renderPlot({", - " plot(iris)", + " graphics::plot(datasets::iris)", "})" ), grep( @@ -86,4 +89,5 @@ server <- append( write(server, "R/app_server.R") devtools::document() +devtools::check() remotes::install_local() \ No newline at end of file From 52222ac77b39f5afdb5c9a301e01e46d4561b15d Mon Sep 17 00:00:00 2001 From: colin Date: Sat, 8 Feb 2020 00:25:40 +0100 Subject: [PATCH 186/211] close #238 --- R/add_deploy_helpers.R | 2 +- R/add_modules.R | 2 +- R/add_r_files.R | 2 +- R/desc.R | 2 +- R/use_favicon.R | 2 +- man/add_files.Rd | 2 +- man/add_module.Rd | 2 +- man/amend_golem_config.Rd | 2 +- man/dockerfiles.Rd | 2 +- man/document_and_reload.Rd | 2 +- man/favicon.Rd | 2 +- man/file_creation.Rd | 2 +- man/fill_desc.Rd | 2 +- man/rstudio_deploy.Rd | 2 +- man/use_files.Rd | 2 +- man/use_recommended.Rd | 2 +- man/utils_files.Rd | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 3e5b52a4..03699ff0 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -51,7 +51,7 @@ add_rstudio_files <- function( #' In previous versions, this function was called add_rconnect_file. #' #' @inheritParams add_module -#' @param pkg Where to put the app.R. +#' @param pkg Where to put the app.R. Default is `get_golem_wd()`. #' @param open Open the file #' @aliases add_rconnect_file add_rstudioconnect_file #' @export diff --git a/R/add_modules.R b/R/add_modules.R index 56ecf9cf..92ad9c5d 100644 --- a/R/add_modules.R +++ b/R/add_modules.R @@ -5,7 +5,7 @@ #' of a {golem} project. #' #' @param name The name of the module -#' @param pkg Path to the root of the package. Default is `"."`. +#' @param pkg Path to the root of the package. Default is `get_golem_wd()`. #' @param open Should the file be opened? #' @param dir_create Creates the directory if it doesn't exist, default is `TRUE`. #' @param fct The name of the fct file. diff --git a/R/add_r_files.R b/R/add_r_files.R index 280dba83..ef63eecc 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -42,7 +42,7 @@ add_r_files <- function( #' #' @param name The name of the file #' @param module If not NULL, the file will be module specific in the naming (you don't need to add the leading `mod_`) -#' @param pkg The working directory +#' @param pkg The working directory. Default is `get_golem_wd()`. #' @param open Should the file be opened once created? #' @param dir_create Should the folder be created if it doesn't exist? #' diff --git a/R/desc.R b/R/desc.R index 29429813..b0d51271 100644 --- a/R/desc.R +++ b/R/desc.R @@ -7,7 +7,7 @@ #' @param author_last_name Last Name of the author #' @param author_email Email of the author #' @param repo_url URL (if needed) -#' @param pkg Path to look for the DESCRIPTION +#' @param pkg Path to look for the DESCRIPTION. Default is `get_golem_wd()`. #' #' @importFrom desc description #' @importFrom cli cat_bullet diff --git a/R/use_favicon.R b/R/use_favicon.R index 25179626..9c602cc3 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -1,7 +1,7 @@ #' Add a favicon to your shinyapp #' #' @param path Path to your favicon file (.ico or .png) -#' @param pkg Path to the root of the package. Default is `"."` +#' @param pkg Path to the root of the package. Default is `get_golem_wd()` #' @rdname favicon #' @export #' diff --git a/man/add_files.Rd b/man/add_files.Rd index e85db825..36767de6 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -22,7 +22,7 @@ add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", \arguments{ \item{name}{The name of the module} -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{dir}{Path to the dir where the file while be created.} diff --git a/man/add_module.Rd b/man/add_module.Rd index 5b40a6b6..6bc53774 100644 --- a/man/add_module.Rd +++ b/man/add_module.Rd @@ -11,7 +11,7 @@ add_module(name, pkg = get_golem_wd(), open = TRUE, \arguments{ \item{name}{The name of the module} -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{open}{Should the file be opened?} diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd index 5ec8185c..3d5ed44a 100644 --- a/man/amend_golem_config.Rd +++ b/man/amend_golem_config.Rd @@ -16,7 +16,7 @@ amend_golem_config(key, value, config = "default", the value of the \code{R_CONFIG_NAME} environment variable ("default" if the variable does not exist).} -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{talkative}{Should the messages be printed to the console?} } diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index 51e51786..68956896 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -32,7 +32,7 @@ add_dockerfile_heroku(path = "DESCRIPTION", output = "Dockerfile", \item{output}{name of the Dockerfile output.} -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\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: with \code{R.Version()$major} and \code{R.Version()$minor}.} diff --git a/man/document_and_reload.Rd b/man/document_and_reload.Rd index d6d11d24..38bfe9b1 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -7,7 +7,7 @@ document_and_reload(pkg = get_golem_wd()) } \arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} } \description{ This function calls \code{rstudioapi::documentSaveAll()}, diff --git a/man/favicon.Rd b/man/favicon.Rd index 8150b790..bec29755 100644 --- a/man/favicon.Rd +++ b/man/favicon.Rd @@ -15,7 +15,7 @@ favicon(ico = "www/favicon.ico", rel = "shortcut icon") \arguments{ \item{path}{Path to your favicon file (.ico or .png)} -\item{pkg}{Path to the root of the package. Default is \code{"."}} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}} \item{ico}{path to favicon file} diff --git a/man/file_creation.Rd b/man/file_creation.Rd index dea0be83..20e7d916 100644 --- a/man/file_creation.Rd +++ b/man/file_creation.Rd @@ -16,7 +16,7 @@ add_utils(name, module = NULL, pkg = get_golem_wd(), open = TRUE, \item{module}{If not NULL, the file will be module specific in the naming (you don't need to add the leading \code{mod_})} -\item{pkg}{The working directory} +\item{pkg}{The working directory. Default is \code{get_golem_wd()}.} \item{open}{Should the file be opened once created?} diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index 8a6e3407..1d03ff8f 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -23,7 +23,7 @@ fill_desc(pkg_name, pkg_title, pkg_description, author_first_name, \item{repo_url}{URL (if needed)} -\item{pkg}{Path to look for the DESCRIPTION} +\item{pkg}{Path to look for the DESCRIPTION. Default is \code{get_golem_wd()}.} } \description{ Fill your description diff --git a/man/rstudio_deploy.Rd b/man/rstudio_deploy.Rd index e848f0d4..af614d2b 100644 --- a/man/rstudio_deploy.Rd +++ b/man/rstudio_deploy.Rd @@ -14,7 +14,7 @@ add_shinyappsio_file(pkg = get_golem_wd(), open = TRUE) add_shinyserver_file(pkg = get_golem_wd(), open = TRUE) } \arguments{ -\item{pkg}{Where to put the app.R.} +\item{pkg}{Where to put the app.R. Default is \code{get_golem_wd()}.} \item{open}{Open the file} } diff --git a/man/use_files.Rd b/man/use_files.Rd index 4444d963..ed16a173 100644 --- a/man/use_files.Rd +++ b/man/use_files.Rd @@ -16,7 +16,7 @@ use_external_css_file(url, name, pkg = get_golem_wd(), \item{name}{The name of the module} -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{dir}{Path to the dir where the file while be created.} diff --git a/man/use_recommended.Rd b/man/use_recommended.Rd index b28f3489..65c5b335 100644 --- a/man/use_recommended.Rd +++ b/man/use_recommended.Rd @@ -11,7 +11,7 @@ use_recommended_deps(pkg = get_golem_wd(), recommended = c("shiny", use_recommended_tests(pkg = get_golem_wd()) } \arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{recommended}{A vector of recommended packages.} } diff --git a/man/utils_files.Rd b/man/utils_files.Rd index 69bfabaa..4a161cbd 100644 --- a/man/utils_files.Rd +++ b/man/utils_files.Rd @@ -10,7 +10,7 @@ use_utils_ui(pkg = get_golem_wd()) use_utils_server(pkg = get_golem_wd()) } \arguments{ -\item{pkg}{Path to the root of the package. Default is \code{"."}.} +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} } \description{ \describe{ From d2481aae46448ffbbc92e9e1c6747b79fdcf72e8 Mon Sep 17 00:00:00 2001 From: colin Date: Sat, 8 Feb 2020 00:34:08 +0100 Subject: [PATCH 187/211] close #350 --- inst/utils/test-golem-recommended.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/utils/test-golem-recommended.R b/inst/utils/test-golem-recommended.R index fdf3e105..dbf3ee91 100644 --- a/inst/utils/test-golem-recommended.R +++ b/inst/utils/test-golem-recommended.R @@ -22,7 +22,7 @@ test_that( "R", c( "-e", - "setwd('../../'); pkgload::load_all();run_app()" + "pkgload::load_all(here::here());run_app()" ) ) Sys.sleep(5) From 1f74eaf51c610d30c5bfaf8906d59e4450199c2f Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 9 Feb 2020 21:46:58 +0100 Subject: [PATCH 188/211] update default NAMESPACE to be CRAN ready at golem init --- inst/shinyexample/NAMESPACE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shinyexample/NAMESPACE b/inst/shinyexample/NAMESPACE index 7c54be88..1e667bcc 100644 --- a/inst/shinyexample/NAMESPACE +++ b/inst/shinyexample/NAMESPACE @@ -1,10 +1,11 @@ # Generated by roxygen2: do not edit by hand export(run_app) -import(config) import(shiny) +importFrom(config,get) importFrom(golem,activate_js) importFrom(golem,add_resource_path) +importFrom(golem,bundle_resources) importFrom(golem,favicon) importFrom(golem,with_golem_options) importFrom(shiny,shinyApp) From e177f20902fb05b09e32dbad37a69ac59f2b3801 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Sun, 9 Feb 2020 22:08:30 +0100 Subject: [PATCH 189/211] fix #178 --- R/add_deploy_helpers.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 03699ff0..45599ae7 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -463,7 +463,8 @@ dock_from_desc <- function( dock$RUN( paste( "apt-get update && apt-get install -y ", - paste(system_requirement, collapse = " ") + paste(system_requirement, collapse = " "), + "&& rm -rf /var/lib/apt/lists/*" ) ) } else { @@ -471,6 +472,7 @@ dock_from_desc <- function( for ( sr in system_requirement ){ dock$RUN( paste("apt-get install -y ", sr) ) } + dock$RUN("rm -rf /var/lib/apt/lists/*") } } From 79456e8c71cd5bcfd05b26ebb5a0d81a4e05493b Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Wed, 19 Feb 2020 22:12:35 +0100 Subject: [PATCH 190/211] add rstudio::conf video and slide on readme page --- README.Rmd | 3 ++- README.md | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.Rmd b/README.Rmd index fb1a935e..c22d23a2 100644 --- a/README.Rmd +++ b/README.Rmd @@ -56,13 +56,14 @@ _Building Big Shiny Apps_ - 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) ### Video - [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) - [Hands-on demonstration of {golem}](https://shinydevseries.com/post/golem-demo/) - 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://resources.rstudio.com/rstudio-conf-2020/production-grade-shiny-apps-with-golem-colin-fay) ### Cheatsheet diff --git a/README.md b/README.md index 3d1c54ec..685a6d37 100644 --- a/README.md +++ b/README.md @@ -47,19 +47,28 @@ This package is part of a series of tools for Shiny, which includes: - 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) -### Video + - rstudio::conf(2020) : [Production-grade Shiny Apps with + golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) + \#\#\# Video - [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) + - [Hands-on demonstration of {golem}](https://shinydevseries.com/post/golem-demo/) + - 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://resources.rstudio.com/rstudio-conf-2020/production-grade-shiny-apps-with-golem-colin-fay) + ### Cheatsheet - [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) @@ -86,8 +95,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: From 40368b6eac7cf2b4fc7a0fbab7066401d0d58c0e Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 24 Feb 2020 16:26:24 +0100 Subject: [PATCH 191/211] correct typo in Readme --- README.Rmd | 1 + README.md | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/README.Rmd b/README.Rmd index c22d23a2..65d34c34 100644 --- a/README.Rmd +++ b/README.Rmd @@ -57,6 +57,7 @@ _Building Big Shiny Apps_ - 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) + ### Video - [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) diff --git a/README.md b/README.md index 685a6d37..03aa793f 100644 --- a/README.md +++ b/README.md @@ -47,25 +47,20 @@ This package is part of a series of tools for Shiny, which includes: - 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) - \#\#\# Video + +### Video - [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) - - [Hands-on demonstration of {golem}](https://shinydevseries.com/post/golem-demo/) - - 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://resources.rstudio.com/rstudio-conf-2020/production-grade-shiny-apps-with-golem-colin-fay) From 93def4e90c28ba05801e58a00a8445dcd56724d0 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 28 Feb 2020 22:33:49 +0100 Subject: [PATCH 192/211] typo in comment --- R/add_deploy_helpers.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 03699ff0..5d133553 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -560,7 +560,7 @@ dock_from_desc <- function( ) devtools::build(path = ".") } - # we use an already builded tar.gz file + # we use an already built tar.gz file dock$COPY( from = paste0(read.dcf(path)[1], "_*.tar.gz"), From 3f1155d9f509d0c4fdd6f88a04d7f34055625720 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 28 Feb 2020 23:15:40 +0100 Subject: [PATCH 193/211] re-write the dev scripts --- inst/shinyexample/dev/01_start.R | 53 +++++++++++++-------------- inst/shinyexample/dev/02_dev.R | 60 +++++++++++++++++++------------ inst/shinyexample/dev/03_deploy.R | 29 +++++++++++---- 3 files changed, 83 insertions(+), 59 deletions(-) diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 34cad82f..e3838ab2 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -1,65 +1,60 @@ # Building a Prod-Ready, Robust Shiny Application. # -# Each step is optional. +# README: each step of the dev files is optional, and you don't have to +# fill every dev scripts before getting started. +# 01_start.R should be filled at start. +# 02_dev.R should be used to keep track of your development during the project. +# 03_deploy.R should be used once you need to deploy your app. # -# 1 - On init # -## 1.1 - Fill the description & set options -## -## Add information about the package that will contain your app +######################################## +#### CURRENT FILE: ON START SCRIPT ##### +######################################## +## Fill the DESCRIPTION ---- +## Add meta data about your application 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 + 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) ) -## Use this desc to set {golem} options - +## Set {golem} options ---- golem::set_golem_options() -## 1.2 - Set common Files -## -## If you want to use the MIT license, README, code of conduct, lifecycle badge, and news - +## Create Common Files ---- +## See ?usethis for more information usethis::use_mit_license( name = "Golem User" ) # You can set another license here usethis::use_readme_rmd( open = FALSE ) usethis::use_code_of_conduct() usethis::use_lifecycle_badge( "Experimental" ) - usethis::use_news_md( open = FALSE ) -usethis::use_git() -## 1.3 - Add a data-raw folder -## -## If you have data in your package -usethis::use_data_raw( name = "my_dataset", open = FALSE ) # Change "my_dataset" +## Use git ---- +usethis::use_git() -## 1.4 - Init Tests -## +## Init Testing Infrastructure ---- ## Create a template for tests - golem::use_recommended_tests() -## 1.5 : Use Recommended Package - +## Use Recommended Packages ---- golem::use_recommended_deps() -## 1.6 Add various tools - +## Favicon ---- # If you want to change the favicon (default is golem's one) golem::remove_favicon() golem::use_favicon() # path = "path/to/ico". Can be an online file. -# Add helper functions +## Add helper functions ---- golem::use_utils_ui() golem::use_utils_server() -# You're now set! +# You're now set! ---- + # go to dev/02_dev.R rstudioapi::navigateToFile( "dev/02_dev.R" ) diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index 20729af7..b9026851 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -1,46 +1,60 @@ # Building a Prod-Ready, Robust Shiny Application. # -# Each step is optional. +# README: each step of the dev files is optional, and you don't have to +# fill every dev scripts before getting started. +# 01_start.R should be filled at start. +# 02_dev.R should be used to keep track of your development during the project. +# 03_deploy.R should be used once you need to deploy your app. # +# +################################### +#### CURRENT FILE: DEV SCRIPT ##### +################################### -# 2. All along your project - -## 2.1 Add modules -## -golem::add_module( name = "my_first_module" ) # Name of the module -golem::add_module( name = "my_other_module" ) # Name of the module - -## 2.2 Add dependencies +# Engineering -usethis::use_package( "thinkr" ) # To call each time you need a new package +## Add modules ---- +## Create a module infrastructure in R/ +golem::add_module( name = "name_of_module1" ) # Name of the module +golem::add_module( name = "name_of_module2" ) # Name of the module -## 2.3 Add tests +## Add helper functions ---- +## Creates ftc_* and utils_* +golem::add_fct( "helpers" ) +golem::add_utils( "helpers" ) -usethis::use_test( "app" ) +## External resources +## Creates .js and .css files at inst/app/www +golem::add_js_file( "script" ) +golem::add_js_handler( "handlers" ) +golem::add_css_file( "custom" ) -## 2.4 Add a browser button +## Add internal datasets ---- +## If you have data in your package +usethis::use_data_raw( name = "my_dataset", open = FALSE ) -golem::browser_button() +## Dependencies ---- +## Add one line by package you want to add as dependency +usethis::use_package( "thinkr" ) -## 2.5 Add external files +## Tests ---- +## Add one line by test you want to create +usethis::use_test( "app" ) -golem::add_js_file( "script" ) -golem::add_js_handler( "handlers" ) -golem::add_css_file( "custom" ) -# 3. Documentation +# Documentation -## 3.1 Vignette +## Vignette ---- usethis::use_vignette("shinyexample") devtools::build_vignettes() -## 3.2 Code coverage -## You'll need GitHub there +## Code coverage ---- +## (You'll need GitHub there) usethis::use_github() usethis::use_travis() usethis::use_appveyor() -# You're now set! +# You're now set! ---- # go to dev/03_deploy.R rstudioapi::navigateToFile("dev/03_deploy.R") diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index 82268d21..758a967f 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -1,22 +1,37 @@ -# Deploy a Prod-Ready, Robust Shiny Application. +# Building a Prod-Ready, Robust Shiny Application. # -# 4. Test my package +# README: each step of the dev files is optional, and you don't have to +# fill every dev scripts before getting started. +# 01_start.R should be filled at start. +# 02_dev.R should be used to keep track of your development during the project. +# 03_deploy.R should be used once you need to deploy your app. +# +# +###################################### +#### CURRENT FILE: DEPLOY SCRIPT ##### +###################################### + +# Test your app +## Run checks ---- +## Check the package before sending to prod devtools::test() rhub::check_for_cran() -# 5. Deployment elements +# Deploy -## 5.1 If you want to deploy on RStudio related platforms +## RStudio ---- +## If you want to deploy on RStudio related platforms golem::add_rstudioconnect_file() golem::add_shinyappsio_file() golem::add_shinyserver_file() -## 5.2 If you want to deploy via a generic Dockerfile +## Docker ---- +## If you want to deploy via a generic Dockerfile golem::add_dockerfile() -## 5.2 If you want to deploy to ShinyProxy +## If you want to deploy to ShinyProxy golem::add_dockerfile_shinyproxy() -## 5.2 If you want to deploy to Heroku +## If you want to deploy to Heroku golem::add_dockerfile_heroku() From 39911be23e0a32dba149aba25171ca23d3314402 Mon Sep 17 00:00:00 2001 From: colin Date: Sun, 1 Mar 2020 22:34:58 +0100 Subject: [PATCH 194/211] update version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0e4d3b50..6ae7311d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.1.0.9700 +Version: 0.2.0 Authors@R: c(person(given = "Vincent", family = "Guyader", From 95f8cf03de034171a67bb7fc9a46ff1ce4d4a79b Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Mon, 2 Mar 2020 10:13:27 +0100 Subject: [PATCH 195/211] wip 372 --- R/use_favicon.R | 31 ++++++++++++++++++++++++++++--- tests/testthat/test-favicon.R | 17 +++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/R/use_favicon.R b/R/use_favicon.R index 9c602cc3..f62b51c0 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -7,6 +7,7 @@ #' #' @importFrom attempt stop_if_not #' @importFrom fs path_abs path file_copy +#' @importFrom tools file_ext #' #' @examples #' \donttest{ @@ -17,9 +18,9 @@ #' } use_favicon <- function( path, - pkg = get_golem_wd() + pkg = get_golem_wd(), + method = "curl" ){ - if (missing(path)){ path <- golem_sys("shinyexample/inst/app/www", "favicon.ico") } @@ -31,7 +32,31 @@ use_favicon <- function( "favicon must have .ico or .png extension" ) - path <- path_abs(path) + + local <- file.exists(path) + + if ( !local ){ + x <- attempt::attempt(curlGetHeaders(path), silent = TRUE) + # > attempt::is_try_error(x) + if ( attr(x, 'status') == 200 ){ + + destfile <- tempfile(fileext = paste0(".",ext),pattern = "favicon") + download.file(path, destfile , method = method) + path <- path_abs(destfile) + } else { + return(stop("can't reach the favicon, check your internet connection")) + } + } + + + if ( !file_exists(path) ){ + return(stop("can't reach the favicon, check your internet connection")) + + + } + + + # path <- path_abs(path) old <- setwd(path_abs(pkg)) on.exit(setwd(old)) diff --git a/tests/testthat/test-favicon.R b/tests/testthat/test-favicon.R index 92d587ef..b67bb94e 100644 --- a/tests/testthat/test-favicon.R +++ b/tests/testthat/test-favicon.R @@ -20,6 +20,23 @@ test_that("test use_favicon",{ }) }) +test_that("test use_favicon online",{ + with_dir(pkg,{ + golem::remove_favicon() + expect_false(file.exists("inst/app/www/favicon.ico")) + use_favicon(path = "https://fr.wikipedia.org//static/favicon/wikipedia.ico") + expect_true(file.exists("inst/app/www/favicon.ico")) + }) +}) +test_that("test use_favicon online fail",{ + with_dir(pkg,{ + golem::remove_favicon() + expect_false(file.exists("inst/app/www/favicon.ico")) + use_favicon(path = "https://fr.wikipedia.org//static/favicon/dontexist.ico") + expect_false(file.exists("inst/app/www/favicon.ico")) + }) +}) + test_that("test favicon",{ with_dir(pkg,{ expect_is( From 40939e8e5a9f93d5a88affab956b4d6baeead122 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 3 Mar 2020 00:00:27 +0100 Subject: [PATCH 196/211] close #372 --- NAMESPACE | 1 + R/use_favicon.R | 1 + man/favicon.Rd | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NAMESPACE b/NAMESPACE index 1836f90b..65ee5a6a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -98,6 +98,7 @@ importFrom(shiny,includeScript) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) +importFrom(tools,file_ext) importFrom(usethis,proj_set) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) diff --git a/R/use_favicon.R b/R/use_favicon.R index f62b51c0..d5d911f1 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -2,6 +2,7 @@ #' #' @param path Path to your favicon file (.ico or .png) #' @param pkg Path to the root of the package. Default is `get_golem_wd()` +#' @param method Method to be used for downloading files, 'curl' is default see [utils::download.file] #' @rdname favicon #' @export #' diff --git a/man/favicon.Rd b/man/favicon.Rd index bec29755..cd0f5d3e 100644 --- a/man/favicon.Rd +++ b/man/favicon.Rd @@ -6,7 +6,7 @@ \alias{favicon} \title{Add a favicon to your shinyapp} \usage{ -use_favicon(path, pkg = get_golem_wd()) +use_favicon(path, pkg = get_golem_wd(), method = "curl") remove_favicon(path = "inst/app/www/favicon.ico") @@ -17,6 +17,8 @@ favicon(ico = "www/favicon.ico", rel = "shortcut icon") \item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}} +\item{method}{Method to be used for downloading files, 'curl' is default see \link[utils:download.file]{utils::download.file}} + \item{ico}{path to favicon file} \item{rel}{rel} From 6309b509bca388b27fdb6335e6c043f8cb8f7b0e Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 3 Mar 2020 00:15:23 +0100 Subject: [PATCH 197/211] update test-favicon --- tests/testthat/test-favicon.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-favicon.R b/tests/testthat/test-favicon.R index b67bb94e..837412ef 100644 --- a/tests/testthat/test-favicon.R +++ b/tests/testthat/test-favicon.R @@ -32,7 +32,7 @@ test_that("test use_favicon online fail",{ with_dir(pkg,{ golem::remove_favicon() expect_false(file.exists("inst/app/www/favicon.ico")) - use_favicon(path = "https://fr.wikipedia.org//static/favicon/dontexist.ico") + expect_error(use_favicon(path = "https://fr.wikipedia.org//static/favicon/dontexist.ico")) expect_false(file.exists("inst/app/www/favicon.ico")) }) }) From 5f8ca1da7a0375ba926ceeacf2e87b978bdb8fba Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 3 Mar 2020 00:30:01 +0100 Subject: [PATCH 198/211] remove explicit devtools call #371 --- R/add_deploy_helpers.R | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 8ce7ac5d..8885c66b 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -560,7 +560,13 @@ dock_from_desc <- function( read.dcf(path)[1,][['Version']] ) ) - devtools::build(path = ".") + if (rlang::is_installed("pkgbuild")) { + pkgbuild::build(path = ".") + } else { + stop("please install {pkgbuild}") + } + + } # we use an already built tar.gz file From b19d2dec9aed65e2d6570ae35168e25693f28b44 Mon Sep 17 00:00:00 2001 From: Vincent Guyader Date: Tue, 3 Mar 2020 00:35:01 +0100 Subject: [PATCH 199/211] use getFromNamespace("build","pkgbuild") of for #371 --- R/add_deploy_helpers.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 8885c66b..31f9021c 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -561,7 +561,7 @@ dock_from_desc <- function( ) ) if (rlang::is_installed("pkgbuild")) { - pkgbuild::build(path = ".") + getFromNamespace("build","pkgbuild")(path = ".") } else { stop("please install {pkgbuild}") } From 65ddaf9f34d8cd9fe7392556bc68b451375c7a5e Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 10:08:01 +0100 Subject: [PATCH 200/211] close #376 --- R/utils.R | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/R/utils.R b/R/utils.R index f46fa456..2d7e2ac7 100644 --- a/R/utils.R +++ b/R/utils.R @@ -198,7 +198,10 @@ after_creation_message_js <- function( if ( desc_exist(pkg) ) { - if (dir != "inst/app/www"){ + if ( + fs::path_abs(dir) != fs::path_abs("inst/app/www") & + packageVersion("golem") < "0.2.0" + ){ cat_red_bullet( sprintf( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$script(src="www/%s.js")`', @@ -218,7 +221,9 @@ after_creation_message_css <- function( if ( desc_exist(pkg) ) { - if (dir != "inst/app/www"){ + if (fs::path_abs(dir) != fs::path_abs("inst/app/www") & + packageVersion("golem") < "0.2.0" + ){ cat_red_bullet( sprintf( 'To link to this file, go to the `golem_add_external_resources()` function in `app_ui.R` and add `tags$link(rel="stylesheet", type="text/css", href="www/.css")`', @@ -240,7 +245,7 @@ file_created_dance <- function( open ){ cat_created(where) - + fun(pkg, dir, name) open_or_go_to(where, open) From c212935516bc8031d8d411297401fc850be95d3c Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 15:57:33 +0100 Subject: [PATCH 201/211] close #371 --- DESCRIPTION | 1 + R/add_deploy_helpers.R | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6ae7311d..89ee793f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -63,6 +63,7 @@ Suggests: devtools, glue, knitr, + pkgbuild, pkgdown, purrr, rcmdcheck, diff --git a/R/add_deploy_helpers.R b/R/add_deploy_helpers.R index 31f9021c..6b049655 100644 --- a/R/add_deploy_helpers.R +++ b/R/add_deploy_helpers.R @@ -561,7 +561,7 @@ dock_from_desc <- function( ) ) if (rlang::is_installed("pkgbuild")) { - getFromNamespace("build","pkgbuild")(path = ".") + pkgbuild::build(".") } else { stop("please install {pkgbuild}") } From f377e1f93d8a8b71042990b21056b17d9de38be2 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 15:57:43 +0100 Subject: [PATCH 202/211] vignettes renaming --- vignettes/{f_config.Rmd => e_config.Rmd} | 10 +++++----- .../{e_golem_cheatsheet.Rmd => z_golem_cheatsheet.Rmd} | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename vignettes/{f_config.Rmd => e_config.Rmd} (84%) rename vignettes/{e_golem_cheatsheet.Rmd => z_golem_cheatsheet.Rmd} (100%) diff --git a/vignettes/f_config.Rmd b/vignettes/e_config.Rmd similarity index 84% rename from vignettes/f_config.Rmd rename to vignettes/e_config.Rmd index e5403705..feba8b10 100644 --- a/vignettes/f_config.Rmd +++ b/vignettes/e_config.Rmd @@ -47,9 +47,9 @@ glue::as_glue( ) ``` -+ default/golem_name, default/golem_version, default/app_prod are usable across the whole life of your golem app: while developping, and also when in production. ++ 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. + production/app_prod might be used for adding elements that are to be used once the app is in production. -+ dev/golem_wd is in a `dev` config because the only moment you might reliably use this config is while developping your app. Use the `app_sys()` function if you want to rely on the package path once the app is deployed. ++ dev/golem_wd is in a `dev` config because the only moment you might reliably use this config is while developing your app. Use the `app_sys()` function if you want to rely on the package path once the app is deployed. The good news is that if you don't want/need to use `{config}`, you can safely ignore this file, __just leave it where it is: it is used internally by the `{golem}` functions__. @@ -133,12 +133,12 @@ There is two ways to configure golem apps: + The `golem_opts` in the `run_app()` function + The `golem-config.yml` file -The big difference between these two is that the golem options from `run_app()` are meant to be configured during runtime: you'll be doing `run_app(val = "this")`, whereas the `golem-config` is meant to be used in the backend, and will not be linked to the parameters passed to `run_app()` (even if this is technically possible, this is not the main objective),. +The big difference between these two is that the golem options from `run_app()` are meant to be configured during runtime: you'll be doing `run_app(val = "this")`, whereas the `golem-config` is meant to be used in the back-end, and will not be linked to the parameters passed to `run_app()` (even if this is technically possible, this is not the main objective),. It's also linked to the `R_CONFIG_ACTIVE` environment variable, just as any `{config}` file. -The idea is also that the `golem-config.yml` file is sharable across `{golem}` projects (`golem_opts` are application specific), and will be tracked by version control systems. +The idea is also that the `golem-config.yml` file is shareable across `{golem}` projects (`golem_opts` are application specific), and will be tracked by version control systems. -## Note for pre `{golem}` 0.2.0 users +## 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 diff --git a/vignettes/e_golem_cheatsheet.Rmd b/vignettes/z_golem_cheatsheet.Rmd similarity index 100% rename from vignettes/e_golem_cheatsheet.Rmd rename to vignettes/z_golem_cheatsheet.Rmd From 3afc8fb1fe9f33dddedcb71ae74a7168a24c8cb2 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 17:39:40 +0100 Subject: [PATCH 203/211] close #373 --- R/bundle_resources.R | 78 +++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/R/bundle_resources.R b/R/bundle_resources.R index f6cd6ee4..9b2542aa 100644 --- a/R/bundle_resources.R +++ b/R/bundle_resources.R @@ -22,30 +22,56 @@ bundle_resources <- function( all_files = TRUE ){ - htmltools::htmlDependency( - name, - version, - src = path, - script = list.files( - path, - pattern = "\\.js$" - ), - stylesheet = list.files( - path, - pattern = "\\.css$" - ), - meta = meta, - head = c( - as.character( - tags$title(app_title) - ), - as.character( - golem::activate_js() - ), - head - ), - attachment = attachment, - package = package, - all_files = all_files - ) + if ( + length( + list.files(path) + ) > 0 + ){ + res <- list() + res[[ + length(res) + 1 + ]] <- htmltools::htmlDependency( + name, + version, + src = path, + script = list.files( + path, + pattern = "\\.js$" + ), + meta = meta, + head = c( + as.character( + tags$title(app_title) + ), + as.character( + golem::activate_js() + ), + head + ), + attachment = attachment, + package = package, + all_files = all_files + ) + # For some reason `htmlDependency` doesn't bundle css, + # So add them by hand + css_nms <- paste0( + basename(path), + "/", + list.files( + path, + pattern = "\\.css$" + ) + ) + + for (i in css_nms ){ + res[[ + length(res) + 1 + ]] <- tags$link( + href = i, + rel = "stylesheet" + ) + } + + res + } } \ No newline at end of file From a0658d53816242bcb062723444cfe990e7450eaa Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 17:45:24 +0100 Subject: [PATCH 204/211] close #378 --- inst/shinyexample/dev/03_deploy.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index 758a967f..4e36df69 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -15,7 +15,7 @@ ## Run checks ---- ## Check the package before sending to prod -devtools::test() +devtools::check() rhub::check_for_cran() # Deploy From b8e7198dc2ec1e63dd42575d2ef216c48ffdb4b5 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 18:15:41 +0100 Subject: [PATCH 205/211] close #379 --- inst/utils/golem_utils_server.R | 41 ++++- inst/utils/golem_utils_ui.R | 263 +++++++++++++++++++++----------- 2 files changed, 210 insertions(+), 94 deletions(-) diff --git a/inst/utils/golem_utils_server.R b/inst/utils/golem_utils_server.R index ab0ac6d6..97bc2d1d 100644 --- a/inst/utils/golem_utils_server.R +++ b/inst/utils/golem_utils_server.R @@ -1,16 +1,34 @@ -# Inverted versions of in, is.null and is.na +#' Inverted versions of in, is.null and is.na +#' +#' @noRd +#' +#' @examples +#' 1 %not_in% 1:10 +#' not_null(NULL) `%not_in%` <- Negate(`%in%`) not_null <- Negate(is.null) not_na <- Negate(is.na) -# Removes the null from a vector +#' Removes the null from a vector +#' +#' @noRd +#' +#' @example +#' drop_nulls(list(1, NULL, 2)) drop_nulls <- function(x){ x[!sapply(x, is.null)] } -# If x is null, return y, otherwise return x +#' If x is `NULL`, return y, otherwise return x +#' +#' @param x,y Two elements to test, one potentially `NULL` +#' +#' @noRd +#' +#' @examples +#' NULL %||% 1 "%||%" <- function(x, y){ if (is.null(x)) { y @@ -18,7 +36,15 @@ drop_nulls <- function(x){ x } } -# If x is NA, return y, otherwise return x + +#' If x is `NA`, return y, otherwise return x +#' +#' @param x,y Two elements to test, one potentially `NA` +#' +#' @noRd +#' +#' @examples +#' NA %||% 1 "%|NA|%" <- function(x, y){ if (is.na(x)) { y @@ -27,7 +53,12 @@ drop_nulls <- function(x){ } } -# typing reactiveValues is too long +#' Typing reactiveValues is too long +#' +#' @inheritParams reactiveValues +#' @inheritParams reactiveValuesToList +#' +#' @noRd rv <- shiny::reactiveValues rvtl <- shiny::reactiveValuesToList diff --git a/inst/utils/golem_utils_ui.R b/inst/utils/golem_utils_ui.R index 973b9ccb..a7af43ad 100644 --- a/inst/utils/golem_utils_ui.R +++ b/inst/utils/golem_utils_ui.R @@ -1,68 +1,123 @@ -# Turn an R list into an HTML list -# -# @param list An R list -# @param class a class for the list -# @return an HTML list -# @examples -# list_to_li(c("a","b")) -# +#' Turn an R list into an HTML list +#' +#' @param list An R list +#' @param class a class for the list +#' +#' @return an HTML list +#' @noRd +#' +#' @examples +#' list_to_li(c("a","b")) +#' #' @importFrom htmltools tags tagAppendAttributes tagList list_to_li <- function(list, class = NULL){ if (is.null(class)){ - tagList(lapply(list, tags$li)) + tagList( + lapply( + list, + tags$li + ) + ) } else { - res <- lapply(list, tags$li) - res <- lapply(res, function(x) tagAppendAttributes(x, class = class)) + res <- lapply( + list, + tags$li + ) + res <- lapply( + res, + function(x) { + tagAppendAttributes( + x, + class = class + ) + } + ) tagList(res) } - + } #' @importFrom htmltools tags tagAppendAttributes tagList list_to_p <- function(list, class = NULL){ if (is.null(class)){ - tagList(lapply(list, tags$p)) + tagList( + lapply( + list, + tags$p + ) + ) } else { - res <- lapply(list, tags$p) - res <- lapply(res, function(x) tagAppendAttributes(x, class = class)) + res <- lapply( + list, + tags$p + ) + res <- lapply( + res, + function(x) { + tagAppendAttributes( + x, + class = class + ) + } + ) tagList(res) } - + } -#' @importFrom glue glue #' @importFrom htmltools tags tagAppendAttributes tagList named_to_li <- function(list, class = NULL){ if(is.null(class)){ res <- mapply( function(x, y){ - tags$li(HTML(glue("{y}: {x}"))) + tags$li( + HTML( + sprintf("%s: %s", y, x) + ) + ) }, - list, names(list), SIMPLIFY = FALSE) - #res <- lapply(res, HTML) + list, + names(list), + SIMPLIFY = FALSE + ) tagList(res) } else { res <- mapply( function(x, y){ - tags$li(HTML(glue("{y}: {x}"))) + tags$li( + HTML( + sprintf("%s: %s", y, x) + ) + ) }, - list, names(list), SIMPLIFY = FALSE) - res <- lapply(res, function(x) tagAppendAttributes(x, class = class)) + list, + names(list), + SIMPLIFY = FALSE + ) + res <- lapply( + res, + function(x) { + tagAppendAttributes( + x, + class = class + ) + } + ) tagList(res) } } -# Remove a tag attribute -# -# @param tag the tag -# @param ... the attributes to remove -# -# @return a new tag -# @export -# -# @examples -# a <- shiny::tags$p(src = "plop", "pouet") -# tagRemoveAttributes(a, "src") +#' Remove a tag attribute +#' +#' @param tag the tag +#' @param ... the attributes to remove +#' +#' @return a new tag +#' @noRd +#' +#' @examples +#' a <- shiny::tags$p(src = "plop", "pouet") +#' tagRemoveAttributes(a, "src") tagRemoveAttributes <- function(tag, ...) { attrs <- as.character(list(...)) for (i in seq_along(attrs)) { @@ -71,21 +126,31 @@ tagRemoveAttributes <- function(tag, ...) { tag } -# Hide or display a tag -# @param tag the tag -# @return a tag -# @examples -# ## Hide -# a <- shiny::tags$p(src = "plop", "pouet") -# undisplay(a) -# b <- shiny::actionButton("go_filter", "go") -# undisplay(b) - +#' Hide or display a tag +#' +#' @param tag the tag +#' +#' @return a tag +#' @noRd +#' +#' @examples +#' ## Hide +#' a <- shiny::tags$p(src = "plop", "pouet") +#' undisplay(a) +#' b <- shiny::actionButton("go_filter", "go") +#' undisplay(b) +#' #' @importFrom htmltools tagList undisplay <- function(tag) { # if not already hidden - if (!is.null(tag$attribs$style) && !grepl("display:\\s+none", tag$attribs$style)) { - tag$attribs$style <- paste("display: none;", tag$attribs$style) + if ( + !is.null(tag$attribs$style) && + !grepl("display:\\s+none", tag$attribs$style) + ) { + tag$attribs$style <- paste( + "display: none;", + tag$attribs$style + ) } else { tag$attribs$style <- "display: none;" } @@ -94,30 +159,43 @@ undisplay <- function(tag) { #' @importFrom htmltools tagList display <- function(tag) { - if (!is.null(tag$attribs$style) && grepl("display:\\s+none", tag$attribs$style)) { - tag$attribs$style <- gsub("(\\s)*display:(\\s)*none(\\s)*(;)*(\\s)*", "", tag$attribs$style) + if ( + !is.null(tag$attribs$style) && + grepl("display:\\s+none", tag$attribs$style) + ) { + tag$attribs$style <- gsub( + "(\\s)*display:(\\s)*none(\\s)*(;)*(\\s)*", + "", + tag$attribs$style + ) } tag } -# Hide an elements by calling jquery hide on it +#' Hide an elements by calling jquery hide on it +#' +#' @param id the id of the element to hide +#' +#' @noRd +#' #' @importFrom htmltools tags jq_hide <- function(id) { tags$script(sprintf("$('#%s').hide()", id)) } -# Add a red star at the end of the text -# -# Adds a red star at the end of the text -# (for example for indicating mandatory fields). -# -# @param text the HTLM text to put before the red star -# -# @return an html element -# -# @examples -# with_red_star("Enter your name here") -# +#' Add a red star at the end of the text +#' +#' Adds a red star at the end of the text +#' (for example for indicating mandatory fields). +#' +#' @param text the HTLM text to put before the red star +#' +#' @return an html element +#' @noRd +#' +#' @examples +#' with_red_star("Enter your name here") +#' #' @importFrom htmltools tags HTML with_red_star <- function(text) { htmltools::tags$span( @@ -134,42 +212,44 @@ with_red_star <- function(text) { -# Repeat tags$br -# -# @param times the number of br to return -# -# @return the number of br specified in times -# @export -# -# @examples -# rep_br(5) -# +#' Repeat tags$br +#' +#' @param times the number of br to return +#' +#' @return the number of br specified in times +#' @noRd +#' +#' @examples +#' rep_br(5) +#' #' @importFrom htmltools HTML rep_br <- function(times = 1) { HTML(rep("
", times = times)) } -# Create an url -# -# @param url the URL -# @param text the text to display -# -# @return an a tag -# @export -# -# @examples -# enurl("https://www.thinkr.fr", "ThinkR") +#' Create an url +#' +#' @param url the URL +#' @param text the text to display +#' +#' @return an a tag +#' @noRd +#' +#' @examples +#' enurl("https://www.thinkr.fr", "ThinkR") +#' +#' @importFrom htmltools tags enurl <- function(url, text){ tags$a(href = url, text) } -# Columns wrappers -# -# These are convenient wrappers around -# `column(12, ...)`, `column(6, ...)`, `column(4, ...)`... -# -# @export -# @rdname columns +#' Columns wrappers +#' +#' These are convenient wrappers around +#' `column(12, ...)`, `column(6, ...)`, `column(4, ...)`... +#' +#' @noRd +#' #' @importFrom shiny column col_12 <- function(...){ column(12, ...) @@ -190,21 +270,25 @@ col_6 <- function(...){ column(6, ...) } + #' @importFrom shiny column col_4 <- function(...){ column(4, ...) } + #' @importFrom shiny column col_3 <- function(...){ column(3, ...) } + #' @importFrom shiny column col_2 <- function(...){ column(2, ...) } + #' @importFrom shiny column col_1 <- function(...){ column(1, ...) @@ -237,7 +321,8 @@ col_1 <- function(...){ #' path, #' output_format = 'md_document', #' output_dir = tempdir(), -#' output_file = md,quiet = TRUE) +#' output_file = md,quiet = TRUE +#' ) #' #' html <- markdown::markdownToHTML(md, fragment.only = TRUE) #' From 3b7bce4aa15bce7ddc55dfb7000d51f8412a99fa Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 19:05:42 +0100 Subject: [PATCH 206/211] refactored favicon creation --- R/use_favicon.R | 100 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/R/use_favicon.R b/R/use_favicon.R index d5d911f1..90750d72 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -8,7 +8,6 @@ #' #' @importFrom attempt stop_if_not #' @importFrom fs path_abs path file_copy -#' @importFrom tools file_ext #' #' @examples #' \donttest{ @@ -22,11 +21,12 @@ use_favicon <- function( pkg = get_golem_wd(), method = "curl" ){ - if (missing(path)){ + if ( missing(path) ){ path <- golem_sys("shinyexample/inst/app/www", "favicon.ico") } ext <- file_ext(path) + stop_if_not( ext, ~ .x %in% c("png",'ico'), @@ -34,33 +34,42 @@ use_favicon <- function( ) - local <- file.exists(path) + local <- fs::file_exists(path) if ( !local ){ - x <- attempt::attempt(curlGetHeaders(path), silent = TRUE) - # > attempt::is_try_error(x) - if ( attr(x, 'status') == 200 ){ - - destfile <- tempfile(fileext = paste0(".",ext),pattern = "favicon") - download.file(path, destfile , method = method) - path <- path_abs(destfile) - } else { - return(stop("can't reach the favicon, check your internet connection")) - } - } - - - if ( !file_exists(path) ){ - return(stop("can't reach the favicon, check your internet connection")) + try_online <- attempt::attempt( + curlGetHeaders(path), + silent = TRUE + ) + attempt::stop_if( + try_online, + attempt::is_try_error, + "Provided path is neither a local path nor a reachable url." + ) + attempt::stop_if_not( + attr(try_online, 'status'), + ~ .x == 200, + "Unable to reach provided url (response code is not 200)." + ) + + destfile <- tempfile( + fileext = paste0(".",ext), + pattern = "favicon" + ) + + download.file( + path, + destfile, + method = method + ) + path <- path_abs(destfile) } + old <- setwd( path_abs(pkg) ) - # path <- path_abs(path) - - old <- setwd(path_abs(pkg)) - on.exit(setwd(old)) + on.exit( setwd(old) ) to <- path( path_abs(pkg), @@ -71,7 +80,7 @@ use_favicon <- function( ) ) - if (! (path == to)) { + if (! (path == to) ) { file_copy( path, to, @@ -86,16 +95,15 @@ use_favicon <- function( ) } - cat_rule("Change / Add in the app_ui function") cat_line( - darkgrey( - sprintf( - 'golem::favicon("www/favicon.%s")', - ext - ) + "Favicon is automatically linked in app_ui via `golem_add_external_resources()`" + ) + cat_red_bullet( + sprintf( + "No file found at %s", + path ) ) - cat_line() } @@ -132,6 +140,34 @@ remove_favicon <- function( #' #' @export #' @importFrom htmltools tags -favicon <- function( ico = "www/favicon.ico", rel="shortcut icon" ){ - tags$head(tags$link(rel= rel, href= ico)) +favicon <- function( + ico, + rel="shortcut icon", + resources_path = "www", + golem_wd = get_golem_wd() +){ + if (missing(ico)){ + ici <- list.files( + pattern = "favicon", + fs::path( + golem_wd, + "inst/app/www" + ) + ) + attempt::stop_if( + length(ici), + ~ .x > 2, + "You have 2 favicons inside your app/www folder." + ) + ico <- fs::path( + resources_path, + ici + ) + } + tags$head( + tags$link( + rel = rel, + href = ico + ) + ) } From f5e9b56e8f5375ad9dc435a271edacf1e598d50f Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 19:33:15 +0100 Subject: [PATCH 207/211] close #370 --- inst/shinyexample/dev/02_dev.R | 9 ++-- vignettes/a_start.Rmd | 81 ++++++++++++++-------------------- vignettes/b_dev.Rmd | 75 +++++++++++++++++++------------ vignettes/c_deploy.Rmd | 10 ++++- vignettes/d_js.Rmd | 10 +++-- 5 files changed, 100 insertions(+), 85 deletions(-) diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index b9026851..84017fc5 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -13,6 +13,10 @@ # Engineering +## Dependencies ---- +## Add one line by package you want to add as dependency +usethis::use_package( "thinkr" ) + ## Add modules ---- ## Create a module infrastructure in R/ golem::add_module( name = "name_of_module1" ) # Name of the module @@ -33,15 +37,10 @@ golem::add_css_file( "custom" ) ## If you have data in your package usethis::use_data_raw( name = "my_dataset", open = FALSE ) -## Dependencies ---- -## Add one line by package you want to add as dependency -usethis::use_package( "thinkr" ) - ## Tests ---- ## Add one line by test you want to create usethis::use_test( "app" ) - # Documentation ## Vignette ---- diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index f8850fc4..b06c0209 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -63,55 +63,50 @@ If you want to do it through command line, you can use: golem::create_golem(path = "path/to/package") ``` +```{r include = FALSE, eval = TRUE, error = TRUE} +x <- fs::path(tempdir(), "golex") +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__. Once you've got that, a new RStudio project will be launched. Here is the structure of this project: +```{r echo = FALSE, eval = TRUE} +z <- capture.output( fs::dir_tree(x)) +z <- z[-1] +w <- lapply( + z, function(x){ + cat(x, "\n") + } +) ``` -DESCRIPTION -¦--dev/ - ¦--01_start.R - ¦--02_dev.R - ¦--03_deploy.R - ¦--run_dev.R -¦--inst/ - ¦--app - ¦--server.R - ¦--ui.R - ¦--www/ - ¦--favicon.ico -¦--man/ - ¦--run_app.Rd -NAMESPACE -myapp.Rproj -¦--R/ - ¦--app_server.R - ¦--app_ui.R - ¦--run_app.R -``` -] + If you're already familiar with R packages, most of these files will seem very familiar to you. That's because a `{golem}` app IS a package. + `DESCRIPTION` & `NAMESPACE`: Package meta-data. -+ `dev/`: Scripts that will be used along the process of developing your app. ++ `R/app_config.R`: Used to read inside `{golem}` config file located at `inst/golem-config.yml`. -+ `inst/app`: You'll add external dependencies in `www` (images, css, etc). `app_ui` and `app_server` in case you ++ `R/app_server.R`, `R/app_ui.R`: Top level UI and server elements. -+ `man`: Package doc, to be generated by R & `{roxygen2}`. ++ `R/run_app.R`: a function to configure and launch the application. -+ `myapp.Rproj`: RStudio project. ++ `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. -+ `R/app_server.R`, `R/app_ui.R`: Top level UI and server elements. ++ `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. -+ `R/run_app.R`: a function to configure and launch the application. ++ `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. -### Fill the DESC +Note that you don't have to fill everything, event thought it's strongly recommended. + +### Fill the DESCRIPTION First, fill the DESCRIPTION by adding information about the package that will contain your app. The first function, `fill_desc()`, can be used to fill your `DESCRIPTION` file. @@ -127,41 +122,31 @@ golem::fill_desc( ) ``` +About [the DESCRIPTION file](https://r-pkgs.org/description.html). + ### Add `{golem}` options -Please DO run this line of code, as it sets a series of global options to be reused inside `{golem}`. +Please DO run this line of code, as it sets a series of global options inside `golem-config.yml` that will be reused inside `{golem}`. ```{r} golem::set_golem_options() ``` - ### Set common Files -If you want to use the MIT license, README, code of conduct, lifecycle badge, and news +If you want to use the MIT license, README, code of conduct, lifecycle badge, a news file, etc. ```{r eval = FALSE} +## See ?usethis for more information usethis::use_mit_license( name = "Golem User" ) # You can set another license here usethis::use_readme_rmd( open = FALSE ) usethis::use_code_of_conduct() usethis::use_lifecycle_badge( "Experimental" ) - usethis::use_news_md( open = FALSE ) -usethis::use_git() ``` See [`{usethis}`](https://usethis.r-lib.org/) for more info about these functions. -### Add a data-raw folder - -If you have data in your package: - -```{r} -usethis::use_data_raw() -``` - -About [data in a package](http://r-pkgs.had.co.nz/data.html). - ### Init Tests Create a template for tests: @@ -170,14 +155,14 @@ Create a template for tests: golem::use_recommended_tests() ``` -About [tests in a package](http://r-pkgs.had.co.nz/tests.html). +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("") +golem::use_recommended_deps() ``` ### Add various tools @@ -206,7 +191,6 @@ golem::use_utils_ui() golem::use_utils_server() ``` - ## Try the app To run the app, go to the `dev/run_dev.R` file, and launch the all thing. @@ -217,4 +201,7 @@ You're now set! You've successfully initiated the project and can go to dev/02_d rstudioapi::navigateToFile("dev/02_dev.R") ``` +```{r eval = TRUE} +try(fs::dir_delete(x), silent = TRUE) +``` diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index aaf9856b..3bd60013 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -25,15 +25,26 @@ knitr::opts_chunk$set( ## Day to day development with `{golem}` -Now that you're all set with your project init, time to move to development :) +Now that you're all set with your project init, time to move to development! App development should happen through the `dev/02_dev.R` file, which contains common commands for developing. + ## Launching the app To run the app, go to the `dev/run_dev.R` file, and run the all thing. ## `dev/02_dev.R` +### Add dependencies + +To be called each time you need a new package as a dependency: + +```{r} +usethis::use_package("pkg") +``` + +About [package dependencies](https://r-pkgs.org/namespace.html). + ### Add modules The `golem::add_module()` functions creates a module in the `R` folder. The file and the modules will be named after the `name` parameter, by adding `mod_` to the R file, and `mod_*_ui` and `mod_*_server` to the UI and server functions. @@ -64,59 +75,67 @@ mod_my_first_module_server <- function(input, output, session){ # callModule(mod_my_first_module_server, "my_first_module_1") ``` -In order not to make errors when putting these into your app, the end of the file will contain code that has to be copied and pasted inside your UI and server functions. +At the end of the file, you will find a piece of code that has to be copied and pasted inside your UI and server functions. -### Add dependencies - -To be called each time you need a new package as a dependency: +## Add function files ```{r} -usethis::use_package("pkg") +golem::add_fct( "helpers" ) +golem::add_utils( "helpers" ) ``` -### Add tests +These two function create `R/fct_helpers.R` and `R/utils_helpers.R`, two file you can use to add business logic functions. -Add more tests to your application: +### Add external files + +These functions create external dependencies (JavaScript and CSS). `add_js_file()` creates a simple JavaScript file, while `add_js_handler()` adds a file with a skeleton for shiny custom handlers. ```{r} -usethis::use_test( "app" ) +golem::add_js_file("script") +golem::add_js_handler("script") +golem::add_css_file("custom") ``` -### Add a browser button +Note: While the general philosophy of `{golem}` is being based on the idea that you're building a package, these functions can be used outside of a `{golem}` project. -Learn more about this: https://rtask.thinkr.fr/blog/a-little-trick-for-debugging-shiny/ +Note that you can also download external CSS and JavaScript files with: ```{r} -golem::browser_button() +golem::use_external_css_file("url") +golem::use_external_js_file("url") ``` -### Add external files +### Adding these external resources to your app -These functions create external dependencies (JavaScript and CSS). `add_js_file()` creates a simple JavaScript file, while `add_js_handler()` adds a file with a skeleton for shiny custom handlers. +You can add any external resource into `inst/app/www`. + +JavaScript and CSS are automatically linked in the `golem_add_external_resources()` function. If you add other resources (example images), you can link them in the app with the `www` prefix: ```{r} -golem::add_js_file("script") -golem::add_js_handler("script") -golem::add_css_file("custom") +tags$img(src = "www/my.png") ``` -Note: While the general philosophy of {golem} is being based on the idea that you're building a package, these functions can be used outside of a {golem} project. - -## Adding these external resources to your app +You can also list here the use of other packages, for example `useShinyalert()` from the `{shinyalert}` package. -You can add any external resource (JS, css) into `inst/app/www`. +### Add a data-raw folder -Then, You'll need to point to these external resources in `golem_add_external_resources()`. For example, if you've created a CSS file with `golem::add_css_file("custom")`, you can add the file with: +If you have data in your package: -``` r -tags$link(rel="stylesheet", type="text/css", href="www/custom.css") +```{r} +usethis::use_data_raw() ``` -Put these links into `R/app_ui.R`, in the `golem_add_external_resources()` function. +About [data in a package](https://r-pkgs.org/data.html). -You can also list here the use of other packages, for example `useShinyalert()` from the `{shinyalert}` package. +### Add tests + +Add more tests to your application: -> Note: we've chosen to leave it "raw", in the sense that there is a `addResourcePath` and a `tags$head`. If you're comfortable with `{htmltools}`, you can build a `htmltools::htmlDependency`. +```{r} +usethis::use_test( "app" ) +``` + +About [testing a package](https://r-pkgs.org/tests.html). ## Documentation @@ -127,6 +146,8 @@ usethis::use_vignette("shinyexample") devtools::build_vignettes() ``` +About [package Vignette](https://r-pkgs.org/vignettes.html). + ### Code coverage ```{r} diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index 605da7c1..27bbc998 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -28,12 +28,18 @@ 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(), server = app_server), + app = shinyApp( + 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/blog/shinyapp-runapp-shinyappdir-difference/). diff --git a/vignettes/d_js.Rmd b/vignettes/d_js.Rmd index b75b58ae..72629009 100644 --- a/vignettes/d_js.Rmd +++ b/vignettes/d_js.Rmd @@ -24,13 +24,13 @@ knitr::opts_chunk$set( ## Using `{golem}` js functions -`{golem}` comes with a series of JavaScript functions that you can call from the server. These functions are added by default with `golem::activate_js()` in `app_ui`. +`{golem}` comes with a series of JavaScript functions that you can call from the server. These functions are added by default with `bundle_resources()` . Then they are called with `golem::invoke_js("fonction", "ui_element")`. -This `ui_element` define the UI element to interact with. It can be a full jQuery selector, an id or a class. +This `ui_element` define the UI element to interact with. It can be a full jQuery selector, an id or a class. Note that you can add several elements and iterate other them. -Note that `invoke_js` can be used with any function defined in a [JavaScript Custom Handler](https://shiny.rstudio.com/articles/communicating-with-js.html#from-r-to-javascript), that you can build with `golem::add_js_handler()`. +`invoke_js` can be used with any function defined in a [JavaScript Custom Handler](https://shiny.rstudio.com/articles/communicating-with-js.html#from-r-to-javascript), that you can build with `golem::add_js_handler()`. ### `golem::invoke_js()` @@ -52,7 +52,9 @@ golem::invoke_js("showhref", "panel2") + `disable` & `reable` able and disable specific element, using the full jQuery selector. -### About jQuery selectors +See `?golem::activate_js()` for the full list. + +### A quick intro to jQuery selectors + `#plop`: the element with the id `plop` From e7a737b25de1f511fe0132ce0c35ffac8a77ac07 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 19:43:35 +0100 Subject: [PATCH 208/211] redocumented --- NAMESPACE | 1 - R/use_favicon.R | 4 +++- man/favicon.Rd | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 65ee5a6a..1836f90b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -98,7 +98,6 @@ importFrom(shiny,includeScript) importFrom(testthat,expect) importFrom(testthat,expect_equal) importFrom(testthat,quasi_label) -importFrom(tools,file_ext) importFrom(usethis,proj_set) importFrom(usethis,use_build_ignore) importFrom(usethis,use_package) diff --git a/R/use_favicon.R b/R/use_favicon.R index 90750d72..df7b4d96 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -137,6 +137,8 @@ remove_favicon <- function( #' #' @param ico path to favicon file #' @param rel rel +#' @param resources_path prefix of the resource path of the app +#' @inheritParams add_modules #' #' @export #' @importFrom htmltools tags @@ -144,7 +146,7 @@ favicon <- function( ico, rel="shortcut icon", resources_path = "www", - golem_wd = get_golem_wd() + pkg = get_golem_wd() ){ if (missing(ico)){ ici <- list.files( diff --git a/man/favicon.Rd b/man/favicon.Rd index cd0f5d3e..70b8167c 100644 --- a/man/favicon.Rd +++ b/man/favicon.Rd @@ -10,7 +10,8 @@ use_favicon(path, pkg = get_golem_wd(), method = "curl") remove_favicon(path = "inst/app/www/favicon.ico") -favicon(ico = "www/favicon.ico", rel = "shortcut icon") +favicon(ico, rel = "shortcut icon", resources_path = "www", + pkg = get_golem_wd()) } \arguments{ \item{path}{Path to your favicon file (.ico or .png)} @@ -22,6 +23,8 @@ favicon(ico = "www/favicon.ico", rel = "shortcut icon") \item{ico}{path to favicon file} \item{rel}{rel} + +\item{resources_path}{prefix of the resource path of the app} } \description{ This function adds the favicon from \code{ico} to your shiny app. From 5b8324418aaf4571a1bec42e90cc8fea250078db Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 3 Mar 2020 19:46:30 +0100 Subject: [PATCH 209/211] forgot to rename --- R/use_favicon.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/use_favicon.R b/R/use_favicon.R index df7b4d96..68d56a5e 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -152,14 +152,14 @@ favicon <- function( ici <- list.files( pattern = "favicon", fs::path( - golem_wd, + pkg, "inst/app/www" ) ) attempt::stop_if( length(ici), ~ .x > 2, - "You have 2 favicons inside your app/www folder." + "You have 2 favicons inside your app/www folder, please remove one." ) ico <- fs::path( resources_path, From 0fcef6ffa8264c80fb5a0507684a053758abea70 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 4 Mar 2020 09:29:57 +0100 Subject: [PATCH 210/211] NEWS update --- NEWS.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index d136caa2..b7b868d1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,10 +4,12 @@ ## New functions -+ `add_dockerfile()` was completely refactore. 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()` 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()` now builds the tar.gz (#273) + + `add_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). + We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). @@ -16,10 +18,15 @@ + `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) -+ There is now an `app_sys()` function, which is a wrapper around `system.file(..., package = "myapp")` (#207, @novica) ## New features ++ `{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. + ++ 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) + You can now create a golem without any comment (#171, @ArthurData) @@ -40,12 +47,28 @@ + The `rsconnect` folder is now added to `.Rbuildignore` (#244) ++ `devtools::test()` in 03_deploy.R is now `devtools::check()` + ++ modules bow have a placeholder for content + ++ 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) + `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) + ++ 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) + ++ 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) + ## Bug fix + The Dockerfile is now correctly added to .Rbuildignore (#81) @@ -68,6 +91,8 @@ + `{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) + ## Internal changes + We no longer depend on `{stringr}` (#201, @TomerPacific) @@ -90,11 +115,13 @@ + The `installed.packages()` function is no longer used. ++ 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/ From 5fb1715ea87e0f98fe6811f9c855fcb02c76cadf Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 4 Mar 2020 09:32:44 +0100 Subject: [PATCH 211/211] skip favicon test on CRAN --- tests/testthat/test-favicon.R | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testthat/test-favicon.R b/tests/testthat/test-favicon.R index 837412ef..cf316b4d 100644 --- a/tests/testthat/test-favicon.R +++ b/tests/testthat/test-favicon.R @@ -22,6 +22,7 @@ test_that("test use_favicon",{ test_that("test use_favicon online",{ with_dir(pkg,{ + skip_on_cran() golem::remove_favicon() expect_false(file.exists("inst/app/www/favicon.ico")) use_favicon(path = "https://fr.wikipedia.org//static/favicon/wikipedia.ico")