diff --git a/.Rbuildignore b/.Rbuildignore index c460e46a..40b216b1 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -22,3 +22,5 @@ ^Meta$ ^\.github$ ^revdep$ +README.html +^CRAN-SUBMISSION$ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..2f36bb85 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: ColinFay + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. +1. ... +2. ... + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**session info** + +please insert here the output of `devtools::session_info()` + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..524940e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FR]" +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/todo.md b/.github/ISSUE_TEMPLATE/todo.md new file mode 100644 index 00000000..d6bcae43 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/todo.md @@ -0,0 +1,34 @@ +--- +name: todo +about: Internal issue template for golem developers +title: '' +labels: '' +assignees: '' + +--- + +## User - Validation + ++ [ ] First thing to do to validate this as an external user ++ [ ] Second thing to do to validate this as an external user + +## Dev - Tech + +_A paragraph that details what this functionality does and how it will be implemented._ + ++ [ ] A sentence that sums up a feature + + [ ] Implementation number 1 + + [ ] Implementation number 2 + + [ ] Technical validation ++ [ ] A sentence that sums up a feature + + [ ] Implementation number 1 + + [ ] Implementation number 2 + + [ ] Technical validation + +## Estimated time + +01h00. + ++ [ ] Add the estimate as a comment with `/estimate` ++ [ ] When I close this issue, `devtools::check()` is 0 0 0 ++ [ ] When I close the issue, I've written the time spent solving this issue, using `/spent` diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 00000000..a3ac6182 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,49 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: R-CMD-check + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true diff --git a/.github/workflows/check-standard.yaml b/.github/workflows/check-standard.yaml deleted file mode 100644 index 0b0fa965..00000000 --- a/.github/workflows/check-standard.yaml +++ /dev/null @@ -1,112 +0,0 @@ -# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. -# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions -on: - push: - branches: - - main - - master - - dev - pull_request: - branches: - - main - - master - - dev - -name: R-CMD-check - -jobs: - R-CMD-check: - runs-on: ${{ matrix.config.os }} - - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - - strategy: - fail-fast: false - matrix: - config: - - { os: windows-latest, r: "release" } - - { os: macOS-latest, r: "release" } - - { - os: ubuntu-20.04, - r: "release", - rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest", - } - - env: - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - - steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@v1 - with: - r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - run: | - while read -r cmd - do - eval sudo $cmd - done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: | - options(crayon.enabled = TRUE) - rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} - - - name: Manual Test - if: runner.os != 'Windows' - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - run: | - source("inst/mantests/build.R") - shell: Rscript {0} - - # - name: Pushes to another repository - # uses: cpina/github-action-push-to-another-repository@main - # if: runner.os == 'Linux' - # env: - # API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} - # with: - # source-directory: "inst/golemmetrics" - # destination-github-username: "colinfay" - # destination-repository-username: "thinkr-open" - # destination-repository-name: "golemmetrics" - # user-email: contact@colinfay.me - # target-branch: main - - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main - with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 00000000..2c5bb502 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: test-coverage + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage + + - name: Test coverage + run: | + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) + shell: Rscript {0} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/CRAN-SUBMISSION b/CRAN-SUBMISSION new file mode 100644 index 00000000..8608538e --- /dev/null +++ b/CRAN-SUBMISSION @@ -0,0 +1,3 @@ +Version: 0.4.0 +Date: 2023-03-10 13:02:19 UTC +SHA: ce67fa5e76d40f1ca68b6dda10064bae37fbcb6e diff --git a/DESCRIPTION b/DESCRIPTION index 5117b403..61338070 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,72 +1,44 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.2.9004 -Authors@R: - c(person(given = "Colin", - family = "Fay", - role = c("cre", "aut"), - email = "contact@colinfay.me", - comment = c(ORCID = "0000-0001-7343-1846")), - person(given = "Vincent", - family = "Guyader", - role = "aut", - email = "vincent@thinkr.fr", - comment = c(ORCID = "0000-0003-0671-9270", "previous maintainer")), - person(given = "Sébastien", - family = "Rochette", - role = "aut", - email = "sebastien@thinkr.fr", - comment = c(ORCID = "0000-0002-1565-9313")), - person(given = "Cervan", - family = "Girard", - role = "aut", - email = "cervan@thinkr.fr", - comment = c(ORCID = "0000-0002-4816-4624")), - person(given = "Novica", - family = "Nakov", - role = "ctb", - email = "nnovica@gmail.com"), - person(given = "David", - family = "Granjon", - role = "ctb", - email = "dgranjon@ymail.com"), - person(given = "Arthur", - family = "Bréant", - role = "ctb", - email = "arthur@thinkr.fr"), - person(given = "Antoine", - family = "Languillaume", - role = "ctb", - email = "antoine@thinkr.fr"), - person(given = "ThinkR", - role = "cph")) +Version: 0.4.10 +Authors@R: c( + person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), + comment = c(ORCID = "0000-0001-7343-1846")), + person("Vincent", "Guyader", , "vincent@thinkr.fr", role = "aut", + comment = c(ORCID = "0000-0003-0671-9270", "previous maintainer")), + person("Sébastien", "Rochette", , "sebastien@thinkr.fr", role = "aut", + comment = c(ORCID = "0000-0002-1565-9313")), + person("Cervan", "Girard", , "cervan@thinkr.fr", role = "aut", + comment = c(ORCID = "0000-0002-4816-4624")), + person("Novica", "Nakov", , "nnovica@gmail.com", role = "ctb"), + person("David", "Granjon", , "dgranjon@ymail.com", role = "ctb"), + person("Arthur", "Bréant", , "arthur@thinkr.fr", role = "ctb"), + person("Antoine", "Languillaume", , "antoine@thinkr.fr", role = "ctb"), + person("ThinkR", role = "cph") + ) Description: An opinionated framework for building a production-ready 'Shiny' application. This package contains a series of tools for building a robust 'Shiny' application from start to finish. License: MIT + file LICENSE URL: https://github.com/ThinkR-open/golem BugReports: https://github.com/ThinkR-open/golem/issues -Depends: +Depends: R (>= 3.0) -Imports: +Imports: attempt (>= 0.3.0), - cli (>= 2.0.0), config, - crayon, - desc, - fs, here, htmltools, - rlang, - rstudioapi, + rlang (>= 1.0.0), shiny (>= 1.5.0), - usethis (>= 1.6.0), utils, yaml Suggests: covr, + cli (>= 2.0.0), + crayon, devtools, - dockerfiler (>= 0.1.4), + dockerfiler (>= 0.2.0), knitr, pkgload, pkgbuild, @@ -82,11 +54,16 @@ Suggests: testthat, tools, withr, - attachment -VignetteBuilder: + attachment (>= 0.2.5), + renv, + usethis (>= 1.6.0), + fs, + rstudioapi, + desc +VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.3 diff --git a/NAMESPACE b/NAMESPACE index 468dedd2..5c44bf87 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,9 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) +export(add_dockerfile_with_renv) +export(add_dockerfile_with_renv_heroku) +export(add_dockerfile_with_renv_shinyproxy) export(add_fct) export(add_html_template) export(add_js_file) @@ -12,6 +15,7 @@ export(add_js_handler) export(add_js_input_binding) export(add_js_output_binding) export(add_module) +export(add_partial_html_template) export(add_resource_path) export(add_rstudioconnect_file) export(add_sass_file) @@ -37,15 +41,19 @@ export(expect_shinytag) export(expect_shinytaglist) export(favicon) export(fill_desc) +export(get_current_config) export(get_golem_name) export(get_golem_options) export(get_golem_version) export(get_golem_wd) export(get_sysreqs) +export(install_dev_deps) export(invoke_js) +export(is_golem) export(is_running) export(js_handler_template) export(js_template) +export(maintenance_page) export(make_dev) export(message_dev) export(module_template) @@ -72,6 +80,7 @@ export(use_internal_file) export(use_internal_html_template) export(use_internal_js_file) export(use_module_test) +export(use_readme_rmd) export(use_recommended_deps) export(use_recommended_tests) export(use_utils_server) @@ -85,43 +94,13 @@ importFrom(attempt,is_try_error) importFrom(attempt,stop_if) importFrom(attempt,stop_if_not) importFrom(attempt,without_warning) -importFrom(cli,cat_bullet) -importFrom(cli,cat_line) -importFrom(cli,cat_rule) -importFrom(cli,cli_alert_info) importFrom(config,get) -importFrom(desc,desc_get_deps) -importFrom(desc,description) -importFrom(fs,dir_copy) -importFrom(fs,dir_create) -importFrom(fs,dir_exists) -importFrom(fs,file_copy) -importFrom(fs,file_create) -importFrom(fs,file_delete) -importFrom(fs,file_exists) -importFrom(fs,path) -importFrom(fs,path_abs) -importFrom(fs,path_file) importFrom(htmltools,htmlDependency) -importFrom(rstudioapi,documentSaveAll) -importFrom(rstudioapi,getSourceEditorContext) -importFrom(rstudioapi,hasFun) -importFrom(rstudioapi,isAvailable) -importFrom(rstudioapi,modifyRange) -importFrom(rstudioapi,navigateToFile) -importFrom(rstudioapi,openProject) -importFrom(rstudioapi,sourceMarkers) importFrom(shiny,addResourcePath) importFrom(shiny,getShinyOption) +importFrom(shiny,htmlTemplate) importFrom(shiny,includeScript) importFrom(shiny,tags) -importFrom(usethis,create_project) -importFrom(usethis,proj_set) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_latest_dependencies) -importFrom(usethis,use_package) -importFrom(usethis,use_spell_check) -importFrom(usethis,use_testthat) importFrom(utils,capture.output) importFrom(utils,file.edit) importFrom(utils,getFromNamespace) diff --git a/NEWS.md b/NEWS.md index 4fc05971..b75e8e37 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,107 +1,162 @@ -> Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. +> Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. -# golem 0.3.2.9000+ +# 0.4.1 to 0.5.0 -### Soft deprecated +## New functions +* `is_golem()` tries to guess if the current folder is a `{golem}`-based app (#836) -### Hard deprecated +* `use_readme_rmd()` adds a {golem} specific READM.Rmd (@ilyaZar, #1011) +## New features / user visible changes -## New functions ++ `add_fct()` now adds the skeleton for a function (#1004, @ilyaZar) ++ The module skeleton now stick to tidyverse style (#1019, @ni2scmn) -## New features ++ Better comments to `fill_desc()` in `01_start.R` (#1021, @ilyaZar) + ++ `01_start.R` now has a call to `usethis::use_git_remote()` (#1015, @ilyaZar) + ++ Test for utilsui and server now has full code coverate (#1020, @ilyaZar) + ++ When setting a new name, golem now browses tests & vignettes (#805, @ilyaZar) + +## Bug fix + ++ Docker commands now take the `-it` flag so it can be killed with `^C` (#, @ivokwee) -+ The following hard dependencies have been moved to soft dependencies, as you'll only need them while developing: `{pkgload}`, `{roxygen2}` ++ add_module() now behaves correctly when trying to use `mod_mod_XXX` (#997, @ilyaZar) -+ Soft dependency check is now done via `rlang::check_installed()` (#835) +## Internal changes -+ `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. +# 0.4.0 + +## New functions + +- Add `add_partial_html_template()` to create a partial html template, with only a div and a `{{ }}` (@nathansquan #858). + +## New features / user visible changes + +- Dev hard dependencies have been moved to soft dependencies. You can see the list with `golem:::dev_deps`. They can be installed via `golem::install_dev_deps()`. +- Soft dependency check is now done via `rlang::check_installed()` (#835) +- `golem::run_dev()` has been refactored to match the behavior of other functions, notably it now uses `golem::get_golem_wd()` to find the current working dir. +- `{golem}` now depends on `{rlang}` version >= 1.0.0 +- Functions that print to the console are now quiet if `options("golem.quiet" = TRUE)`, #793 +- Small documentation update in dockerfile related functions (#939) +- `fill_desc()` now allows to set the version (#877) +- Setting the Environment variable `GOLEM_MAINTENANCE_ACTIVE` to `TRUE` active the maintenance mode of your app +- `golem::run_dev()` now save all open documents before sourcing the `dev/run_dev.R` file +- When creating an app, you'll get a message if the dev deps are not all installed (#915) +- 03_deploy now contains an example of sending the app to PositConnect or Shinyapps.io (#923) ## Bug fix -+ The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) +- The message after htmlTemplate creation now suggests to add in the UI, not only in app_ui.R (#861) +- The Deprecation message for `use_recommended_deps` no longer suggest to user `use_recommended_deps` (#900) +- The setting of the config file has been unified so that we are sure to keep the !expr in `golem_set_wd()`, and the codebase has been simplified for this (#709). +- The functions adding files can no longer take a `name` argument that has length() > 1. This used to cause some bugs (#781) +- The typo in `install.packages()` in 02_dev.R has been corrected (@asiripanich) +- `add_dockerfile_with_renv()` now works well with uppercase in package name +- improve `get_golem_options()` documentation ## Internal changes ++ `add_dockerfile_with_renv` now works well with uppercase in package name + +# golem 0.3.5 +Update in the tests for CRAN (commented a test that made new version of testthat fail). + +# golem 0.3.4 + +Update in the tests for CRAN (skip not installed + examples). + +# golem 0.3.3 + +## New functions + +- `add_dockerfile_with_renv()`, `add_dockerfile_with_renv_heroku()` and `add_dockerfile_with_renv_shinyproxy()` build Dockerfiles that rely on `{renv}` + +### Soft deprecated + +- `add_dockerfile`, `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` now recommend to switch to their `_with_renv_` counterpart # golem 0.3.2 (CRAN VERSION) ### Soft deprecated -+ `use_recommended_deps()` is now soft deprecated (#786) +- `use_recommended_deps()` is now soft deprecated (#786) ### Hard deprecated -+ The `html` parameter in `expect_html_equal()` is no longer in use (#55). +- The `html` parameter in `expect_html_equal()` is no longer in use (#55). ## New functions -+ `add_sass_file()` creates a .sass file in inst/app/www (#768) +- `add_sass_file()` creates a .sass file in inst/app/www (#768) -+ `use_module_test()` creates a test skeleton for a module (#725) +- `use_module_test()` creates a test skeleton for a module (#725) ## New features -+ The `02_dev.R` file now suggests using `attachment::att_amend_desc()` (#787) +- The `02_dev.R` file now suggests using `attachment::att_amend_desc()` (#787) -+ `use_code_of_conduct()` in dev script now has the contact param (#812) +- `use_code_of_conduct()` in dev script now has the contact param (#812) -+ All `with_test` params are now TRUE in the dev script (#801) - -+ `test-golem-recommended` now has two new tests for `app_sys` and `get_golem_config` (#751) +- All `with_test` params are now TRUE in the dev script (#801) -+ `use_utils_ui()` `use_utils_server()` & now come with a `with_test` parameter that adds a test file for theses functions (#625 & #801) +- `test-golem-recommended` now has two new tests for `app_sys` and `get_golem_config` (#751) -+ `{golem}` now checks if a module exists before adding a module related file (#779) +- `use_utils_ui()` `use_utils_server()` & now come with a `with_test` parameter that adds a test file for theses functions (#625 & #801) -+ Every `{rstudioapi}` calls is now conditionned by the availabily of this function (#776) +- `{golem}` now checks if a module exists before adding a module related file (#779) -+ `use_external_*` functions no longer suggest to "Go to" (#713, @novica) - -+ `create_golem()` now comes with `with_git` parameter that can be used to initialize git repository while creating a project template +- Every `{rstudioapi}` calls is now conditionned by the availabily of this function (#776) -+ `use_recommended_tests()` now comes with `testServer` (#720). +- `use_external_*` functions no longer suggest to "Go to" (#713, @novica) -+ `expect_html_equal()` now uses `testthat::expect_snapshot()` (#55). +- `create_golem()` now comes with `with_git` parameter that can be used to initialize git repository while creating a project template -+ `add_modules()`, `add_fct()` and `add_utils()` now come with a `with_test` parameter that can be turned on to add a test file to the module (#719 & #141) +- `use_recommended_tests()` now comes with `testServer` (#720). -+ /!\ All docker related functions have been moved to `{dockerfiler}`. This is more or less a breaking change, cause you'll need to install `{dockerfiler}` > 0.1.4 in order to build the Dockerfile __but__ `{golem}` will ask you to install `{dockerfiler}` > 0.1.4 if it can't find it, (#412) +- `expect_html_equal()` now uses `testthat::expect_snapshot()` (#55). -+ Modules ID no longer contain an `_ui_` element, (#651, @MargotBr) +- `add_modules()`, `add_fct()` and `add_utils()` now come with a `with_test` parameter that can be turned on to add a test file to the module (#719 & #141) -+ run_dev now has `options(shiny.port = httpuv::randomPort())` to prevent the browser from caching the CSS & JS files (#675) +- /!\ All docker related functions have been moved to `{dockerfiler}`. This is more or less a breaking change, cause you'll need to install `{dockerfiler}` > 0.1.4 in order to build the Dockerfile **but** `{golem}` will ask you to install `{dockerfiler}` > 0.1.4 if it can't find it, (#412) -+ You can now specify the path to R in `expect_running()`. +- Modules ID no longer contain an `_ui_` element, (#651, @MargotBr) + +- run_dev now has `options(shiny.port = httpuv::randomPort())` to prevent the browser from caching the CSS & JS files (#675) + +- You can now specify the path to R in `expect_running()`. ## Bug fix -+ Fixed a bug in the printing of the htmlTemplate code (#827) +- Fixed a bug in the printing of the htmlTemplate code (#827) -+ We now require the correct `{usethis}` version (822) +- We now require the correct `{usethis}` version (822) -+ `golem::amend_config()` now keeps the `!expr` (#709, @teofiln) +- `golem::amend_config()` now keeps the `!expr` (#709, @teofiln) -+ recommended tests now use `expect_type()` instead of `expect_is`, which was deprecated from `{testthat}` (#671) +- recommended tests now use `expect_type()` instead of `expect_is`, which was deprecated from `{testthat}` (#671) -+ Fixed check warning when using `golem::use_utils_server()` (#678), +- Fixed check warning when using `golem::use_utils_server()` (#678), -+ Fixed issue with expect_running & path to R (#700, @waiteb5) +- Fixed issue with expect_running & path to R (#700, @waiteb5) -+ `expect_running()` now find R.exe on windows. +- `expect_running()` now find R.exe on windows. -+ `use_recommended_tests()` no longer add `{processx}` to the `DESCRIPTION` (#710) +- `use_recommended_tests()` no longer add `{processx}` to the `DESCRIPTION` (#710) -+ `bundle_resource()` does not include empty stylesheet anymore (#689, @erikvona) +- `bundle_resource()` does not include empty stylesheet anymore (#689, @erikvona) ## Internal changes -+ Create `{golem}` is more robust and now comes with an `overwrite` argument (#777) +- Create `{golem}` is more robust and now comes with an `overwrite` argument (#777) -+ `{testthat}` and `{rlang}` are no longer hard dependencies (#742) +- `{testthat}` and `{rlang}` are no longer hard dependencies (#742) # golem 0.3.1 (CRAN Version) @@ -109,316 +164,315 @@ ### `add_*` -+ You can now create a skeleton for a Shiny input binding using the `golem::add_js_binding("name")` function (#452, @DivadNojnarg) +- You can now create a skeleton for a Shiny input binding using the `golem::add_js_binding("name")` function (#452, @DivadNojnarg) -+ You can now create a skeleton for a Shiny output binding using the `golem::add_js_output_binding("name")` function (@DivadNojnarg) +- You can now create a skeleton for a Shiny output binding using the `golem::add_js_output_binding("name")` function (@DivadNojnarg) -+ `add_html_template()` creates an htmlTemplate. +- `add_html_template()` creates an htmlTemplate. ### `use_*` -+ `use_external_file()` allows to add any file to the `www` folder, `use_external_css_file()`, `use_external_html_template()`, and `use_external_js_file()` will download them from a URL (#295, #491). +- `use_external_file()` allows to add any file to the `www` folder, `use_external_css_file()`, `use_external_html_template()`, and `use_external_js_file()` will download them from a URL (#295, #491). -+ `use_internal_css_file()`, `use_internal_file()`, `use_internal_html_template()`, `use_internal_js_file()` functions allow to any file from the current computer to the `www` folder (@KasperThystrup, #529) +- `use_internal_css_file()`, `use_internal_file()`, `use_internal_html_template()`, `use_internal_js_file()` functions allow to any file from the current computer to the `www` folder (@KasperThystrup, #529) ### Tests helper -+ `expect_running()` expects the current shiny app to be running. +- `expect_running()` expects the current shiny app to be running. ### Hooks -+ Every `{golem}` project now have a `project_hook` that is launched after the project creation. +- Every `{golem}` project now have a `project_hook` that is launched after the project creation. -+ `module_template()` is the default function for `{golem}` module creation. Users will now be able to define a custom `module_template()` function for `add_module()`, allowing to extend `{golem}` with your own module creation function. See ?golem::module_template for more info (#365) +- `module_template()` is the default function for `{golem}` module creation. Users will now be able to define a custom `module_template()` function for `add_module()`, allowing to extend `{golem}` with your own module creation function. See ?golem::module_template for more info (#365) -+ `add_js_` and `add_css_` functions now have a template function, allowing to pass a file constructor. +- `add_js_` and `add_css_` functions now have a template function, allowing to pass a file constructor. ### Misc -+ `is_running()` checks if the current running application is a `{golem}` based application (#366) +- `is_running()` checks if the current running application is a `{golem}` based application (#366) -+ `utils_ui.R` now contains a "make_action_button()" function (#457, @DivadNojnarg) +- `utils_ui.R` now contains a "make_action_button()" function (#457, @DivadNojnarg) -+ `run_dev()` launches the `run_dev.R` script (#478, @KoderKow) +- `run_dev()` launches the `run_dev.R` script (#478, @KoderKow) -+ `run_dev()` performs a check on golem name. +- `run_dev()` performs a check on golem name. -+ `sanity_check()` function has been added to check for any 'browser()' or commented #TODO / #TOFIX / #BUG in the code (#1354 @Swechhya) +- `sanity_check()` function has been added to check for any 'browser()' or commented #TODO / #TOFIX / #BUG in the code (#1354 @Swechhya) ## New features -+ The modules are now created with the new skeleton when the installed version of `{shiny}` is >= 1.5.0. +- The modules are now created with the new skeleton when the installed version of `{shiny}` is >= 1.5.0. -+ `use_external_*()` function don't open files by default (#404) +- `use_external_*()` function don't open files by default (#404) -+ `use_recommended_tests*()` now calls ` use_spell_check()` (#430) +- `use_recommended_tests*()` now calls ` use_spell_check()` (#430) -+ The `02_dev.R` now includes more CI links +- The `02_dev.R` now includes more CI links -+ `golem::expect_running()` is now bundled in default tests +- `golem::expect_running()` is now bundled in default tests -+ Default tests now test for functions formals (#437) +- Default tests now test for functions formals (#437) -+ You can now pass arguments to internal `roxygenise()` & `load_all()` (#467) +- You can now pass arguments to internal `roxygenise()` & `load_all()` (#467) -+ `Bundle_resources()` now handle subfolders (#446) +- `Bundle_resources()` now handle subfolders (#446) -+ `run_app()` now includes the default arguments of `shinyApp()` (#254, @chasemc) +- `run_app()` now includes the default arguments of `shinyApp()` (#254, @chasemc) -+ `create_golem()` now adds strict dependency versions (#466) +- `create_golem()` now adds strict dependency versions (#466) -+ `{golem}` app now comes with a meta tags "app-builder", which default to "golem", and that can be changed or turn off in `bundle_resources()`. +- `{golem}` app now comes with a meta tags "app-builder", which default to "golem", and that can be changed or turn off in `bundle_resources()`. -+ `with_golem_options` can now explicit calls `print` on the `app` object, solving some issues with benchmarking the application. This explicit print can be turned off by setting `print` to FALSE in `with_golem_options` (#148) +- `with_golem_options` can now explicit calls `print` on the `app` object, solving some issues with benchmarking the application. This explicit print can be turned off by setting `print` to FALSE in `with_golem_options` (#148) -+ `dockerignore` is now available. +- `dockerignore` is now available. -+ The `add_helpers` and `add_utils` now have roxygen comments (Richard Pilbery, #330) +- The `add_helpers` and `add_utils` now have roxygen comments (Richard Pilbery, #330) -+ `dev/03_dev.R` now has `devtools::build()` (#603) +- `dev/03_dev.R` now has `devtools::build()` (#603) -+ `detach_all_attached()` is now silent (#605) +- `detach_all_attached()` is now silent (#605) ## Soft deprecated -+ `add_ui_server_files()` is now signaled as deprecated. Please comment on https://github.com/ThinkR-open/golem/issues/445 if you want it to be kept inside the package +- `add_ui_server_files()` is now signaled as deprecated. Please comment on https://github.com/ThinkR-open/golem/issues/445 if you want it to be kept inside the package ## Breaking changes -+ `add_dockerfile*` function now return the `{dockerfiler}` object instead of the path to it. It allows to modify the Dockerfile object programmatically. (#493) +- `add_dockerfile*` function now return the `{dockerfiler}` object instead of the path to it. It allows to modify the Dockerfile object programmatically. (#493) -+ The `get_golem_config` now first look for a `GOLEM_CONFIG_ACTIVE` before looking for `R_CONFIG_ACTIVE` (#563) +- The `get_golem_config` now first look for a `GOLEM_CONFIG_ACTIVE` before looking for `R_CONFIG_ACTIVE` (#563) ## Bug fix -+ `add_` functions no longer append to file if it already exists (#393) +- `add_` functions no longer append to file if it already exists (#393) -+ `config::get()` is no longer exported to prevent namespace conflicts with `base::get()` - -+ fixed issue with favicon when package is built (#387) +- `config::get()` is no longer exported to prevent namespace conflicts with `base::get()` -+ `use_external_*()` function don't add ext if already there (#405) +- fixed issue with favicon when package is built (#387) -+ `create_golem` function does not modify any existing file (#423, @antoine-sachet) +- `use_external_*()` function don't add ext if already there (#405) -+ `add_resources_path()` now correctly handles empty folder (#395) +- `create_golem` function does not modify any existing file (#423, @antoine-sachet) -+ test for app launching is now skipped if not interactive() +- `add_resources_path()` now correctly handles empty folder (#395) -+ `add_utils` and `add_fct` now print to the console (#427, @novica) +- test for app launching is now skipped if not interactive() -+ Multiple CRAN repo are now correctly passed to the Dockerfile (#462) +- `add_utils` and `add_fct` now print to the console (#427, @novica) -+ app_config, DESC and golem-config.yml are now updated whenever you change the name of the package using a golem function (#469 ) +- Multiple CRAN repo are now correctly passed to the Dockerfile (#462) -+ `test_recommended` now work in every case (hopefully) +- app_config, DESC and golem-config.yml are now updated whenever you change the name of the package using a golem function (#469 ) -- `usethis::use_mit_license` does not have the `name` argument anymore so if fits new version of `{usethis}` (#594) +- `test_recommended` now work in every case (hopefully) -- Typo fix preventing `invoke_js("prompt")` and `invoke_js("confirm")` to work (#606) +* `usethis::use_mit_license` does not have the `name` argument anymore so if fits new version of `{usethis}` (#594) + +* Typo fix preventing `invoke_js("prompt")` and `invoke_js("confirm")` to work (#606) ## Internal changes -+ `document_and_reload()` now has `export_all = FALSE,helpers = FALSE,attach_testthat = FALSE`, allowing the function to behave more closely to what library() does (#399) +- `document_and_reload()` now has `export_all = FALSE,helpers = FALSE,attach_testthat = FALSE`, allowing the function to behave more closely to what library() does (#399) -+ Dockerfile generation now removes the copied file and tar.gz +- Dockerfile generation now removes the copied file and tar.gz # golem 0.2.1 ## New functions -+ `add_dockerfile()` was completely refactored. It now starts from r-ver, uses explicit package versions from you local machine, and tries to set as much System Requirements as possible by using `{sysreq}`, and parses and installs the Remotes tag from the DESCRIPTION (#189, #175) - -+ `add_dockerfile()` allow now to directly use the source of the package by mounting the source folder in the container and running `remotes::install_local()` +- `add_dockerfile()` was completely refactored. It now starts from r-ver, uses explicit package versions from you local machine, and tries to set as much System Requirements as possible by using `{sysreq}`, and parses and installs the Remotes tag from the DESCRIPTION (#189, #175) -+ `add_dockerfile()` now builds the tar.gz (#273) +- `add_dockerfile()` allow now to directly use the source of the package by mounting the source folder in the container and running `remotes::install_local()` -+ `add_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). +- `add_dockerfile()` now builds the tar.gz (#273) -+ We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). +- `add_fct` and `add_utils` add new files in your R folder that can hold utils and functions (#123). -+ New JavaScript functions to use alert, prompt and confirm (#108, @zwycl) +- We switched from `shiny::addResourcePath()` to `golem::add_resource_path()`, which doesn't fail if the folder is empty (#223). -+ `use_external_js_file` and `use_external_css_file` are designed to download .js and .css file off the web to the appropriate directory (#130, @zwycl) +- New JavaScript functions to use alert, prompt and confirm (#108, @zwycl) +- `use_external_js_file` and `use_external_css_file` are designed to download .js and .css file off the web to the appropriate directory (#130, @zwycl) ## New features -+ `{golem}` now comes with an internal config file. Please refer to the `config` Vignette for more information. +- `{golem}` now comes with an internal config file. Please refer to the `config` Vignette for more information. -+ `bundle_resources()` comes with every new app and bundles all the css and js files you put inside the `inst/app/www` folder, by matchine the file extension. +- `bundle_resources()` comes with every new app and bundles all the css and js files you put inside the `inst/app/www` folder, by matchine the file extension. -+ There is now an `app_sys()` function, which is a wrapper around `system.file(..., package = "myapp")` (#207, @novica) +- There is now an `app_sys()` function, which is a wrapper around `system.file(..., package = "myapp")` (#207, @novica) -+ `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157) +- `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157) -+ You can now create a golem without any comment (#171, @ArthurData) +- You can now create a golem without any comment (#171, @ArthurData) -+ The default `app_ui()` now has a `request` parameter, to natively handle bookmarking. +- The default `app_ui()` now has a `request` parameter, to natively handle bookmarking. -+ `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157). It also uses `get_golem_wd()` as a default path, to be consistent with the rest of `{golem}` (#219, @j450h1) +- `document_and_reload()` now stops when it fails, and returns an explicit failure message (#157). It also uses `get_golem_wd()` as a default path, to be consistent with the rest of `{golem}` (#219, @j450h1) -+ `add_module` now allows to create and `fct_` and an `utils_` file (#154, @novica) +- `add_module` now allows to create and `fct_` and an `utils_` file (#154, @novica) -+ `golem::detach_all_attached()` is now silent (#186, @annakau) +- `golem::detach_all_attached()` is now silent (#186, @annakau) -+ There is now a series of addins for going to a specific golem file (#212, @novica), and also to wrap a selected text into `ns()` (#143, @kokbent) +- There is now a series of addins for going to a specific golem file (#212, @novica), and also to wrap a selected text into `ns()` (#143, @kokbent) -+ Creation of a golem project is now a little bit more talkative (#63, @novica) +- Creation of a golem project is now a little bit more talkative (#63, @novica) -+ golem apps now have a title tag in the header by default, (#172, @novica) +- golem apps now have a title tag in the header by default, (#172, @novica) -+ The `rsconnect` folder is now added to `.Rbuildignore` (#244) +- The `rsconnect` folder is now added to `.Rbuildignore` (#244) -+ `devtools::test()` in 03_deploy.R is now `devtools::check()` +- `devtools::test()` in 03_deploy.R is now `devtools::check()` -+ modules bow have a placeholder for content +- modules bow have a placeholder for content -+ Dev scripts have been rewritten and rerordered a litte bit +- Dev scripts have been rewritten and rerordered a litte bit -## Breaking changes +## Breaking changes -+ `invoke_js()` now takes a list of elements to send to JS (through `...`) instead of a vector (#155, @zwycl) +- `invoke_js()` now takes a list of elements to send to JS (through `...`) instead of a vector (#155, @zwycl) -+ `get_dependencies` was removed from this package, please use `desc::desc_get_deps()` instead (#251) +- `get_dependencies` was removed from this package, please use `desc::desc_get_deps()` instead (#251) -+ `{golem}` now uses `here::here()` to determine the default working directory (#287) +- `{golem}` now uses `here::here()` to determine the default working directory (#287) -+ Modules used to be exported by default. You now have to specify it when creating the modules (#144) +- Modules used to be exported by default. You now have to specify it when creating the modules (#144) -+ `run_app()` is no longer explicitely namespaced in the run_dev script (#267) +- `run_app()` is no longer explicitely namespaced in the run_dev script (#267) -+ JavaScript files now default to having `$(document).ready()` (#227) +- JavaScript files now default to having `$(document).ready()` (#227) -+ Every filesystem manipulation is now done with `{fs}`. That should be pretty transparent for most users but please open an issue if it causes problem (#285) +- Every filesystem manipulation is now done with `{fs}`. That should be pretty transparent for most users but please open an issue if it causes problem (#285) ## Bug fix -+ The Dockerfile is now correctly added to .Rbuildignore (#81) +- The Dockerfile is now correctly added to .Rbuildignore (#81) -+ The dockerfile for shinyproxy no longer has a typo (#156, @fmmattioni) +- The dockerfile for shinyproxy no longer has a typo (#156, @fmmattioni) -+ `normalizePath()` now has a correct winlash (@kokbent) +- `normalizePath()` now has a correct winlash (@kokbent) -+ spellcheck in files (@privefl) +- spellcheck in files (@privefl) -+ Message to link to `golem_add_external_resources()` is now conditional to R being in a golem project (#167, @novica) +- Message to link to `golem_add_external_resources()` is now conditional to R being in a golem project (#167, @novica) -+ Better error on missing name in add_*, (#120, @novica) +- Better error on missing name in add\_\*, (#120, @novica) -+ When adding file, the extension is now ignored if provided by the user (#231) +- When adding file, the extension is now ignored if provided by the user (#231) -+ The dots R/run_app.R are now documented by default (#243) +- The dots R/run_app.R are now documented by default (#243) -+ Bug fix of the pkgdown website (#180) +- Bug fix of the pkgdown website (#180) -+ `{golem}` now correctly handles command line creation of projet inside the current directory (#248) +- `{golem}` now correctly handles command line creation of projet inside the current directory (#248) -+ The test are now more robust when it comes to random name generation (#281) +- The test are now more robust when it comes to random name generation (#281) ## Internal changes -+ We no longer depend on `{stringr}` (#201, @TomerPacific) +- We no longer depend on `{stringr}` (#201, @TomerPacific) -+ get_golem_wd() is now used everywhere in `{golem}` (#237, @felixgolcher) +- get_golem_wd() is now used everywhere in `{golem}` (#237, @felixgolcher) -# golem 0.1.0 - CRAN release candidate, v2 +# golem 0.1.0 - CRAN release candidate, v2 -## New Functions +## New Functions -+ `get_golem_wd` allows to print the current golem working directory, and `set_golem_wd` to change it. +- `get_golem_wd` allows to print the current golem working directory, and `set_golem_wd` to change it. -## Breaking changes +## Breaking changes -+ In order to work, the functions creating files need a `golem.wd`. This working directory is set by `set_golem_options` or the first time you create a file. It default to `"."`, the current directory. +- In order to work, the functions creating files need a `golem.wd`. This working directory is set by `set_golem_options` or the first time you create a file. It default to `"."`, the current directory. -+ Changes in the name of the args in `set_golem_options`: `pkg_path` is now `golem_wd`, `pkg_name` is now `golem_name`, `pkg_version` is now `golem_version` +- Changes in the name of the args in `set_golem_options`: `pkg_path` is now `golem_wd`, `pkg_name` is now `golem_name`, `pkg_version` is now `golem_version` ## Internal changes -+ The `installed.packages()` function is no longer used. +- The `installed.packages()` function is no longer used. -+ Every filesystem manipulation is now done with `{fs}` (#285) +- Every filesystem manipulation is now done with `{fs}` (#285) # golem 0.0.1.9999 - CRAN release candidate ## Changes in the way run_app and deploy files are build -+ There is now a unique framework for run_app, that allows to deploy anywhere and can accept arguments. These arguments can then be retrieved with `get_golem_options()`. +- There is now a unique framework for run_app, that allows to deploy anywhere and can accept arguments. These arguments can then be retrieved with `get_golem_options()`. > See https://rtask.thinkr.fr/blog/shinyapp-runapp-shinyappdir-difference/ ## Breaking Changes -+ There is no need for `ui.R` and `server.R` to exist by default. Removed. Can be recreated with `add_ui_server_files()` +- There is no need for `ui.R` and `server.R` to exist by default. Removed. Can be recreated with `add_ui_server_files()` ## New function -+ There is now `add_shinyserver_file` & `add_shinyappsio_file`, #40 -+ `add_ui_server_files()` creates an ui & server.R files. +- There is now `add_shinyserver_file` & `add_shinyappsio_file`, #40 +- `add_ui_server_files()` creates an ui & server.R files. -## Small functions updates +## Small functions updates -+ Functions that create file(s) now automatically create folder if it's not there. Can be prevented with `dir_create = FALSE` -+ Functions that create file(s) can now be prevented from opening with `open = FALSE`, #75 -+ We have made explicit how to add external files (css & js) to the app, #78 -+ Launch test is now included in the default tests #48 +- Functions that create file(s) now automatically create folder if it's not there. Can be prevented with `dir_create = FALSE` +- Functions that create file(s) can now be prevented from opening with `open = FALSE`, #75 +- We have made explicit how to add external files (css & js) to the app, #78 +- Launch test is now included in the default tests #48 # golem 0.0.1.6000+ -## Changes +## Changes -* `create_golem()` now switch to the newly created project -* `use_git()` is not listed in `dev/01_start.R` +- `create_golem()` now switch to the newly created project +- `use_git()` is not listed in `dev/01_start.R` -## Breaking changes +## Breaking changes -* Renamed `add_rconnect_file()` to `add_rstudioconnect_file()` -* Renamed `create_shiny_template()` to `create_golem()` -* Renamed `js()` to `activate_js()` -* Renamed `use_recommended_dep()` to `use_recommended_deps()` +- Renamed `add_rconnect_file()` to `add_rstudioconnect_file()` +- Renamed `create_shiny_template()` to `create_golem()` +- Renamed `js()` to `activate_js()` +- Renamed `use_recommended_dep()` to `use_recommended_deps()` -## New functions +## New functions -* `invoke_js()` allows to call JS functions from the server side. #52 +- `invoke_js()` allows to call JS functions from the server side. #52 # golem 0.0.1.5000 -## Changes +## Changes -* The dev files are now split in three - start / dev / deploy +- The dev files are now split in three - start / dev / deploy -* Every function that adds a file now check if the file already exists, and ask the user if they want to overwrite it (#15) +- Every function that adds a file now check if the file already exists, and ask the user if they want to overwrite it (#15) -* Every module is now named mod_x_ui / mod_x_server, for consistency. +- Every module is now named mod_x_ui / mod_x_server, for consistency. -* You can now create package with "illegal" names, using the command line `golem::create_shiny_template()`. #18 +- You can now create package with "illegal" names, using the command line `golem::create_shiny_template()`. #18 -* `add_browser_button()` is now named `browser_button()`, so that all the `add_*` function are only reserved for function adding files to the `golem`. +- `add_browser_button()` is now named `browser_button()`, so that all the `add_*` function are only reserved for function adding files to the `golem`. -+ `add_*_files` now check if the folder exists, if not suggests to create it. #36 +* `add_*_files` now check if the folder exists, if not suggests to create it. #36 ## New functions -* You now have a `browser_dev()` function that behaves like `warning_dev` and friends. #46 +- You now have a `browser_dev()` function that behaves like `warning_dev` and friends. #46 -* Added `set_golem_options()` to add local options used internally by {golem} && added it to the `01_start.R`. #49 +- Added `set_golem_options()` to add local options used internally by {golem} && added it to the `01_start.R`. #49 -* Added `add_dockerfile()` to create a Dockerfile from a DESCRIPTION. +- Added `add_dockerfile()` to create a Dockerfile from a DESCRIPTION. -* Added `add_dockerfile_shinyproxy()` to create a Dockerfile from a DESCRIPTION, to be used in Shiny Proxy. +- Added `add_dockerfile_shinyproxy()` to create a Dockerfile from a DESCRIPTION, to be used in Shiny Proxy. -* Added `add_dockerfile_heroku()` to create a Dockerfile from a DESCRIPTION, to be used with Heroku. +- Added `add_dockerfile_heroku()` to create a Dockerfile from a DESCRIPTION, to be used with Heroku. -* `add_css_file()`, `add_js_file()` and `add_js_handler()` create a CSS, JS, and JS with Shiny custom handler files. +- `add_css_file()`, `add_js_file()` and `add_js_handler()` create a CSS, JS, and JS with Shiny custom handler files. ## Removed -* `use_utils_prod` is now included in golem so you don't have to explicitly include the functions. +- `use_utils_prod` is now included in golem so you don't have to explicitly include the functions. -## Docs +## Docs -* Golem now has four vignettes +- Golem now has four vignettes # golem 0.0.1.0002 @@ -426,6 +480,6 @@ Last round of functions, and some documentation cleanup. # golem 0.0.0.9000 -* Moved from {shinytemplate} to {golem} +- Moved from {shinytemplate} to {golem} -* Added a `NEWS.md` file to track changes to the package. +- Added a `NEWS.md` file to track changes to the package. diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index a94e9c00..5d1c54d5 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -1,7 +1,20 @@ +talk_once <- function(.f, msg = "") { + talk <- TRUE + function(...) { + if (talk) { + talk <<- FALSE + cat_red_bullet(msg) + } + .f(...) + } +} + #' Create a Dockerfile for your App #' -#' Build a container containing your Shiny App. `add_dockerfile()` creates -#' a generic Dockerfile, while `add_dockerfile_shinyproxy()` and +#' Build a container containing your Shiny App. `add_dockerfile()` and +#' `add_dockerfile_with_renv()` and `add_dockerfile_with_renv()` creates +#' a generic Dockerfile, while `add_dockerfile_shinyproxy()`, +#' `add_dockerfile_with_renv_shinyproxy()` , `add_dockerfile_with_renv_shinyproxy()` and #' `add_dockerfile_heroku()` creates platform specific Dockerfile. #' #' @inheritParams add_module @@ -9,288 +22,390 @@ #' @param path path to the DESCRIPTION file to use as an input. #' @param output name of the Dockerfile output. #' @param from The FROM of the Dockerfile. Default is -#' FROM rocker/r-ver:`R.Version()$major`.`R.Version()$minor`. +#' +#' FROM rocker/verse +#' +#' without renv.lock file passed +#' `R.Version()$major`.`R.Version()$minor` is used as tag +#' #' @param as The AS of the Dockerfile. Default it NULL. #' @param port The `options('shiny.port')` on which to run the App. #' Default is 80. #' @param host The `options('shiny.host')` on which to run the App. #' Default is 0.0.0.0. -#' @param sysreqs boolean. If TRUE, the Dockerfile will contain sysreq installation. +#' @param sysreqs boolean. If TRUE, RUN statements to install packages +#' system requirements will be included in the Dockerfile. #' @param repos character. The URL(s) of the repositories to use for `options("repos")`. #' @param expand boolean. If `TRUE` each system requirement will have its own `RUN` line. -#' @param open boolean. Should the Dockerfile be open after creation? Default is `TRUE`. +#' @param open boolean. Should the Dockerfile/README/README be open after creation? Default is `TRUE`. #' @param build_golem_from_source boolean. If `TRUE` no tar.gz is created and #' the Dockerfile directly mount the source folder. #' @param update_tar_gz boolean. If `TRUE` and `build_golem_from_source` is also `TRUE`, #' an updated tar.gz is created. #' @param extra_sysreqs character vector. Extra debian system requirements. -#' Will be installed with apt-get install. #' #' @export #' @rdname dockerfiles #' -#' @importFrom usethis use_build_ignore -#' @importFrom desc desc_get_deps -#' @importFrom rstudioapi navigateToFile isAvailable hasFun -#' @importFrom fs path path_file #' #' @examples #' \donttest{ #' # Add a standard Dockerfile -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile() #' } +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem using docker based on {renv} +#' if (interactive() & requireNamespace("dockerfiler")) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock", # uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } #' # Add a Dockerfile for ShinyProxy -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_shinyproxy() #' } +#' +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem with ShinyProxy using docker based on {renv} +#' if (interactive() & requireNamespace("dockerfiler")) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock",# uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } +#' #' # Add a Dockerfile for Heroku -#' if (interactive()) { +#' if (interactive() & requireNamespace("dockerfiler")) { #' add_dockerfile_heroku() #' } #' } #' @return The `{dockerfiler}` object, invisibly. add_dockerfile <- function( - 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 = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL -) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(path_file(where)) - - dock <- dockerfiler::dock_from_desc( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { + add_dockerfile_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, + port = port, + host = host, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$EXPOSE(port) +add_dockerfile_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { + where <- fs_path(pkg, output) - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", - port, - host, - read.dcf(path)[1] + usethis_use_build_ignore( + basename(where) ) - ) - dock$write(output) + dock <- dockerfiler_dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + dock$EXPOSE(port) + + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');library(%3$s);%3$s::run_app()\"", + port, + host, + read.dcf(path)[1] + ) + ) + + dock$write(output) + + if (open) { + rstudioapi_navigateToFile(output) } - } - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv() instead." +) #' @export #' @rdname dockerfiles -#' @importFrom fs path path_file add_dockerfile_shinyproxy <- function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/r-ver:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL -) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { + add_dockerfile_shinyproxy_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$EXPOSE(3838) - dock$CMD(sprintf( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", - read.dcf(path)[1] - )) - dock$write(output) +add_dockerfile_shinyproxy_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { + where <- fs_path(pkg, output) + + usethis_use_build_ignore(output) + + dock <- dockerfiler_dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) + + dock$EXPOSE(3838) + dock$CMD(sprintf( + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"]", + read.dcf(path)[1] + )) + dock$write(output) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + if (open) { + rstudioapi_navigateToFile(output) } - } - alert_build( - path, - output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path, + output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile_shinyproxy() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_shinyproxy() instead." +) #' @export #' @rdname dockerfiles -#' @importFrom fs path path_file add_dockerfile_heroku <- function( - path = "DESCRIPTION", - output = "Dockerfile", - pkg = get_golem_wd(), - from = paste0( - "rocker/r-ver:", - R.Version()$major, - ".", - R.Version()$minor - ), - as = NULL, - sysreqs = TRUE, - repos = c(CRAN = "https://cran.rstudio.com/"), - expand = FALSE, - open = TRUE, - update_tar_gz = TRUE, - build_golem_from_source = TRUE, - extra_sysreqs = NULL -) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { + add_dockerfile_heroku_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", - read.dcf(path)[1] +add_dockerfile_heroku_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL) { + where <- fs_path(pkg, output) + + usethis_use_build_ignore(output) + + dock <- dockerfiler_dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs ) - ) - dock$write(output) - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"", + read.dcf(path)[1] + ) + ) + dock$write(output) - apps_h <- gsub( - "\\.", - "-", - sprintf( - "%s-%s", - read.dcf(path)[1], - read.dcf(path)[1, ][["Version"]] + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source ) - ) - cat_rule("From your command line, run:") - cat_line("heroku container:login") - cat_line( - sprintf("heroku create %s", apps_h) - ) - cat_line( - sprintf("heroku container:push web --app %s", apps_h) - ) - cat_line( - sprintf("heroku container:release web --app %s", apps_h) - ) - cat_line( - sprintf("heroku open --app %s", apps_h) - ) - cat_red_bullet("Be sure to have the heroku CLI installed.") - cat_red_bullet( - sprintf("You can replace %s with another app name.", apps_h) - ) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + read.dcf(path)[1], + read.dcf(path)[1, ][["Version"]] + ) + ) + + cli_cat_rule("From your command line, run:") + cli_cat_line("heroku container:login") + cli_cat_line( + sprintf("heroku create %s", apps_h) + ) + cli_cat_line( + sprintf("heroku container:push web --app %s", apps_h) + ) + cli_cat_line( + sprintf("heroku container:release web --app %s", apps_h) + ) + cli_cat_line( + sprintf("heroku open --app %s", apps_h) + ) + cat_red_bullet("Be sure to have the heroku CLI installed.") + cat_red_bullet( + sprintf("You can replace %s with another app name.", apps_h) + ) + if (open) { + rstudioapi_navigateToFile(output) } - } - usethis::use_build_ignore(files = output) - return(invisible(dock)) -} + usethis_use_build_ignore(files = output) + return(invisible(dock)) + }, + " +golem::add_dockerfile_heroku() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_heroku() instead. +" +) alert_build <- function( - path, - output, - build_golem_from_source -) { + path, + output, + build_golem_from_source) { cat_created(output, "Dockerfile") if (!build_golem_from_source) { cat_red_bullet( diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R new file mode 100644 index 00000000..24afc09f --- /dev/null +++ b/R/add_dockerfiles_renv.R @@ -0,0 +1,374 @@ +add_dockerfile_with_renv_ <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + FROM = "rocker/verse", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + document = FALSE, + ... + # build_golem_from_source = TRUE, + ) { + if (is.null(lockfile)) { + rlang::check_installed( + c("renv", "attachment"), + reason = "to build a Dockerfile with automatic renv.lock creation. Use the `lockfile` parameter to pass your own `renv.lock` file." + ) + } + + + # Small hack to prevent warning from rlang::lang() in tests + # This should be managed in {attempt} later on + x <- suppressWarnings({ + rlang::lang(print) + }) + + dir.create(output_dir) + + # add output_dir in Rbuildignore if the output is inside the golem + if (normalizePath(dirname(output_dir)) == normalizePath(source_folder)) { + usethis_use_build_ignore(output_dir) + } + + if (is.null(lockfile)) { + if (isTRUE(document)) { + cli_cat_line("You set `document = TRUE` and you did not pass your own renv.lock file,") + cli_cat_line("as a consequence {golem} will use `attachment::att_amend_desc()` to update your ") + cli_cat_line("DESCRIPTION file before creating the renv.lock file") + cli_cat_line("") + cli_cat_line("you can set `document = FALSE` to use your actual DESCRIPTION file,") + cli_cat_line("or pass you own renv.lock to use, using the `lockfile` parameter") + cli_cat_line("") + cli_cat_line("In any case be sure to have no Error or Warning at `devtools::check()`") + } + + + + + lockfile <- attachment_create_renv_for_prod( + path = source_folder, + check_if_suggests_is_installed = FALSE, document = document, + output = file.path(output_dir, "renv.lock.prod"), + ... + ) + } + + # fs_file_copy( + # path = lockfile, + # new_path = output_dir, + # overwrite = TRUE + # ) + file.copy(from = lockfile, to = output_dir) + socle <- dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + repos = repos, + AS = AS, + sysreqs = sysreqs, + expand = expand, + extra_sysreqs = extra_sysreqs + ) + + socle$write(as = file.path(output_dir, "Dockerfile_base")) + + + my_dock <- dockerfiler_Dockerfile()$new(FROM = tolower(tolower(paste0(golem::get_golem_name(), "_base")))) + + my_dock$COPY("renv.lock.prod", "renv.lock") + + my_dock$RUN("R -e 'renv::restore()'") + + if (update_tar_gz) { + old_version <- list.files(path = output_dir, pattern = paste0(golem::get_golem_name(), "_*.*.tar.gz"), full.names = TRUE) + # file.remove(old_version) + if (length(old_version) > 0) { + lapply(old_version, file.remove) + lapply(old_version, unlink, force = TRUE) + cat_red_bullet( + sprintf( + "%s were removed from folder", + paste( + old_version, + collapse = ", " + ) + ) + ) + } + + if ( + isTRUE( + requireNamespace( + "pkgbuild", + quietly = TRUE + ) + ) + ) { + out <- pkgbuild::build( + path = ".", + dest_path = output_dir, + vignettes = FALSE + ) + if (missing(out)) { + cat_red_bullet("Error during tar.gz building") + } else { + cat_green_tick( + sprintf( + " %s created.", + out + ) + ) + } + } else { + stop("please install {pkgbuild}") + } + } + + # we use an already built tar.gz file + my_dock$COPY( + from = + paste0(golem::get_golem_name(), "_*.tar.gz"), + to = "/app.tar.gz" + ) + my_dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\",upgrade=\"never\")'") + my_dock$RUN("rm /app.tar.gz") + my_dock +} + +#' @param source_folder path to the Package/golem source folder to deploy. +#' default is current folder '.' +#' @param lockfile path to the renv.lock file to use. default is `NULL` +#' @param output_dir folder to export everything deployment related. +#' @param distro One of "focal", "bionic", "xenial", "centos7", or "centos8". +#' See available distributions at https://hub.docker.com/r/rstudio/r-base/. +#' @param document boolean. If TRUE (by default), DESCRIPTION file is updated using [attachment::att_amend_desc()] before creating the renv.lock file +#' @param dockerfile_cmd What is the CMD to add to the Dockerfile. If NULL, the default, +#' the CMD will be `R -e "options('shiny.port'={port},shiny.host='{host}');library({appname});{appname}::run_app()\` +#' @param ... Other arguments to pass to [renv::snapshot()] +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +add_dockerfile_with_renv <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + document = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL, + ...) { + base_dock <- add_dockerfile_with_renv_( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + document = document, + ... + ) + if (!is.null(port)) { + base_dock$EXPOSE(port) + } + if (is.null(dockerfile_cmd)) { + dockerfile_cmd <- sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');library(%3$s);%3$s::run_app()\"", + port, + host, + golem::get_golem_name() + ) + } + base_dock$CMD( + dockerfile_cmd + ) + base_dock + base_dock$write(as = file.path(output_dir, "Dockerfile")) + + out <- sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s . +docker build -f Dockerfile --progress=plain -t %s . +docker run -p %s:%s %s +# then go to 127.0.0.1:%s", + tolower(paste0(golem::get_golem_name(), "_base")), + tolower(paste0(golem::get_golem_name(), ":latest")), + port, + port, + tolower(paste0(golem::get_golem_name(), ":latest")), + port + ) + + cat(out, file = file.path(output_dir, "README")) + + open_or_go_to( + where = file.path(output_dir, "README"), + open_file = open + ) +} + +#' @inheritParams add_dockerfile_with_renv +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_shinyproxy <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + document = TRUE, + update_tar_gz = TRUE, + ...) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = 3838, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = open, + document = document, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=3838,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"", + golem::get_golem_name() + ), + ... + ) +} + +#' @inheritParams add_dockerfile_with_renv +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_heroku <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + document = TRUE, + update_tar_gz = TRUE, + ...) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = NULL, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = FALSE, + document = document, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');library(%1$s);%1$s::run_app()\"", + golem::get_golem_name() + ), + ... + ) + + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + golem::get_golem_name(), + golem::get_golem_version() + ) + ) + + readme_output <- fs_path( + output_dir, + "README" + ) + + write_there <- function(...) { + write(..., file = readme_output, append = TRUE) + } + + write_there("From your command line, run:\n") + + write_there( + sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s .", + paste0(golem::get_golem_name(), "_base") + ) + ) + + write_there( + sprintf( + "docker build -f Dockerfile --progress=plain -t %s .\n", + paste0(golem::get_golem_name(), ":latest") + ) + ) + + write_there("Then, to push on heroku:\n") + + write_there("heroku container:login") + write_there( + sprintf("heroku create %s", apps_h) + ) + write_there( + sprintf("heroku container:push web --app %s", apps_h) + ) + write_there( + sprintf("heroku container:release web --app %s", apps_h) + ) + write_there( + sprintf("heroku open --app %s\n", apps_h) + ) + write_there("> Be sure to have the heroku CLI installed.") + + write_there( + sprintf("> You can replace %s with another app name.", apps_h) + ) + + # The open is deported here just to be sure + # That we open the README once it has been populated + open_or_go_to( + where = readme_output, + open_file = open + ) +} diff --git a/R/add_files.R b/R/add_files.R index bfe88086..663cdd2d 100644 --- a/R/add_files.R +++ b/R/add_files.R @@ -22,9 +22,7 @@ #' @export #' @rdname add_files #' @importFrom attempt stop_if -#' @importFrom cli cat_bullet #' @importFrom utils file.edit -#' @importFrom fs path_abs path file_create file_exists #' #' @note `add_ui_server_files` will be deprecated in future version of `{golem}` #' @@ -43,12 +41,14 @@ add_js_file <- function( ) { stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -61,16 +61,15 @@ add_js_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) - + if (!fs_file_exists(where)) { + fs_file_create(where) if (with_doc_ready) { write_there <- function(...) { write(..., file = where, append = TRUE) @@ -99,8 +98,6 @@ add_js_file <- function( #' @export #' @rdname add_files -#' -#' @importFrom fs path_abs path file_create file_exists add_js_handler <- function( name, pkg = get_golem_wd(), @@ -112,12 +109,14 @@ add_js_handler <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -130,15 +129,15 @@ add_js_handler <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- file.path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) template(path = where, ...) @@ -160,7 +159,6 @@ add_js_handler <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_js_input_binding <- function( name, pkg = get_golem_wd(), @@ -176,9 +174,11 @@ add_js_input_binding <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + attempt::stop_if( length(events$name) == 0, msg = "At least one event is required" @@ -195,7 +195,7 @@ add_js_input_binding <- function( sprintf("input-%s", name) ) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -208,15 +208,15 @@ add_js_input_binding <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- file.path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) { write(..., file = where, append = TRUE) @@ -307,7 +307,6 @@ add_js_input_binding <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_js_output_binding <- function( name, pkg = get_golem_wd(), @@ -317,16 +316,18 @@ add_js_output_binding <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + raw_name <- name name <- file_path_sans_ext( sprintf("output-%s", name) ) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -339,15 +340,15 @@ add_js_output_binding <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- file.path( + where <- fs_path( dir, sprintf("%s.js", name) ) - if (!file.exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) { write(..., file = where, append = TRUE) @@ -388,7 +389,6 @@ add_js_output_binding <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_css_file <- function( name, pkg = get_golem_wd(), @@ -400,12 +400,14 @@ add_css_file <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -418,9 +420,9 @@ add_css_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, sprintf( "%s.css", @@ -428,8 +430,8 @@ add_css_file <- function( ) ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) template(path = where, ...) file_created_dance( where, @@ -449,8 +451,6 @@ add_css_file <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists -#' @importFrom cli cli_alert_info add_sass_file <- function( name, pkg = get_golem_wd(), @@ -462,12 +462,14 @@ add_sass_file <- function( ) { attempt::stop_if( missing(name), - msg = "Name is required" + msg = "`name` is required" ) + check_name_length(name) + name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -480,9 +482,9 @@ add_sass_file <- function( return(invisible(FALSE)) } - dir_abs <- path_abs(dir) + dir_abs <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir_abs, sprintf( "%s.sass", @@ -490,8 +492,8 @@ add_sass_file <- function( ) ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) template(path = where, ...) file_created_dance( where, @@ -502,9 +504,13 @@ add_sass_file <- function( open ) - add_sass_code(where = where, dir = dir, name = name) + add_sass_code( + where = where, + dir = dir, + name = name + ) - cli_alert_info( + cat_green_tick( "After running the compilation, your CSS file will be automatically link in `golem_add_external_resources()`." ) } else { @@ -517,7 +523,6 @@ add_sass_file <- function( #' @export #' @rdname add_files -#' @importFrom fs path_abs path file_create file_exists add_html_template <- function( name = "template.html", pkg = get_golem_wd(), @@ -527,7 +532,9 @@ add_html_template <- function( ) { name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + check_name_length(name) + + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -540,9 +547,9 @@ add_html_template <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, sprintf( "%s.html", @@ -550,8 +557,8 @@ add_html_template <- function( ) ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) write(..., file = where, append = TRUE) write_there("") write_there("") @@ -584,10 +591,66 @@ add_html_template <- function( } } +#' @export +#' @rdname add_files +add_partial_html_template <- function( + name = "partial_template.html", + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +) { + name <- file_path_sans_ext(name) + check_name_length(name) + + old <- setwd(fs_path_abs(pkg)) + on.exit(setwd(old)) + + dir_created <- create_if_needed( + dir, + type = "directory" + ) + + if (!dir_created) { + cat_dir_necessary() + return(invisible(FALSE)) + } + + dir <- fs_path_abs(dir) + + where <- fs_path( + dir, + sprintf( + "%s.html", + name + ) + ) + + if (!fs_file_exists(where)) { + fs_file_create(where) + write_there <- function(...) write(..., file = where, append = TRUE) + write_there("
") + write_there(" {{ content }}") + write_there("
") + write_there("") + file_created_dance( + where, + after_creation_message_html_template, + pkg, + dir, + name, + open + ) + } else { + file_already_there_dance( + where = where, + open_file = open + ) + } +} #' @export #' @rdname add_files -#' @importFrom fs path_abs file_create add_ui_server_files <- function( pkg = get_golem_wd(), dir = "inst/app", @@ -595,7 +658,7 @@ add_ui_server_files <- function( ) { .Deprecated(msg = "This function will be deprecated in a future version of {golem}.\nPlease comment on https://github.com/ThinkR-open/golem/issues/445 if you want it to stay.") - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -608,13 +671,13 @@ add_ui_server_files <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) # UI - where <- path(dir, "ui.R") + where <- fs_path(dir, "ui.R") - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) write(..., file = where, append = TRUE) @@ -635,9 +698,8 @@ add_ui_server_files <- function( "server.R" ) - - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(...) write(..., file = where, append = TRUE) diff --git a/R/add_r_files.R b/R/add_r_files.R index ef05a1b0..eb8a42af 100644 --- a/R/add_r_files.R +++ b/R/add_r_files.R @@ -1,4 +1,3 @@ -#' @importFrom fs path_abs path file_create add_r_files <- function( name, ext = c("fct", "utils"), @@ -10,7 +9,9 @@ add_r_files <- function( ) { name <- file_path_sans_ext(name) - old <- setwd(path_abs(pkg)) + check_name_length(name) + + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -29,27 +30,31 @@ add_r_files <- function( # Remove the "mod_" if any module <- mod_remove(module) if (!is_existing_module(module)) { - stop( - sprintf( - "The module '%s' does not exist.\nYou can call `golem::add_module('%s')` to create it.", - module, - module - ), - call. = FALSE - ) + # Check for esoteric 'mod_mod_' module names and if that fails throw error + if (!is_existing_module(paste0("mod_", module))) { + stop( + sprintf( + "The module '%s' does not exist.\nYou can call `golem::add_module('%s')` to create it.", + module, + module + ), + call. = FALSE + ) + } + module <- paste0("mod_", module) } module <- paste0("mod_", module, "_") } - where <- path( + where <- fs_path( "R", paste0(module, ext, "_", name, ".R") ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) - if (file_exists(where) & is.null(module)) { + if (fs_file_exists(where) & is.null(module)) { # Must be a function or utility file being created append_roxygen_comment( name = name, @@ -67,11 +72,7 @@ add_r_files <- function( } if (with_test) { - rlang::check_installed( - "usethis", - "to build the test structure." - ) - usethis::use_test( + usethis_use_test( basename( file_path_sans_ext( where @@ -178,4 +179,8 @@ append_roxygen_comment <- function( } else { write_there("#' @noRd") } + if (file_type == "function") { + write_there(paste(name, "<- function() {")) + write_there("}") + } } diff --git a/R/add_rstudio_files.R b/R/add_rstudio_files.R index 41966f4e..50631f0b 100644 --- a/R/add_rstudio_files.R +++ b/R/add_rstudio_files.R @@ -1,7 +1,4 @@ #' @importFrom utils capture.output -#' @importFrom cli cat_bullet -#' @importFrom usethis use_build_ignore use_package -#' @importFrom fs path file_create path_file add_rstudio_files <- function( pkg, open, @@ -12,28 +9,26 @@ add_rstudio_files <- function( ) ) { service <- match.arg(service) - where <- path(pkg, "app.R") + where <- fs_path(pkg, "app.R") rlang::check_installed( "pkgload", reason = "to deploy on RStudio products." ) - rlang::check_installed("usethis") - disable_autoload( pkg = pkg ) - if (!file_exists(where)) { - file_create(where) + if (!fs_file_exists(where)) { + fs_file_create(where) write_there <- function(..., here = where) { write(..., here, append = TRUE) } - use_build_ignore(path_file(where)) - use_build_ignore("rsconnect") + usethis_use_build_ignore(basename(where)) + usethis_use_build_ignore("rsconnect") write_there("# Launch the ShinyApp (Do not remove this comment)") write_there("# To deploy, run: rsconnect::deployApp()") write_there("# Or use the blue button on top of this file") @@ -48,11 +43,11 @@ add_rstudio_files <- function( ) # We add {pkgload} as a dep because it's required to deploy on Connect & stuff - usethis::use_package("pkgload") + usethis_use_package("pkgload") cat_created(where) - cat_line("To deploy, run:") - cat_bullet(darkgrey("rsconnect::deployApp()\n")) + cli_cat_line("To deploy, run:") + cli_cat_bullet(crayon_darkgrey("rsconnect::deployApp()\n")) cat_red_bullet( sprintf( "Note that you'll need to upload the whole package to %s", diff --git a/R/addins.R b/R/addins.R index b998a669..442c7353 100644 --- a/R/addins.R +++ b/R/addins.R @@ -4,9 +4,7 @@ #' The series of `go_to_*()` addins help you go to #' common files used in developing a `{golem}` application. #' -#' @importFrom rstudioapi getSourceEditorContext modifyRange #' @importFrom attempt stop_if_not -#' @importFrom rstudioapi modifyRange #' #' @aliases addins #' @rdname addins @@ -17,16 +15,16 @@ NULL #' @aliases addins insert_ns <- function() { stop_if_not( - rstudioapi::hasFun("getSourceEditorContext"), + rstudioapi_hasFun("getSourceEditorContext"), msg = "Your version of RStudio does not support `getSourceEditorContext`" ) stop_if_not( - rstudioapi::hasFun("modifyRange"), + rstudioapi_hasFun("modifyRange"), msg = "Your version of RStudio does not support `modifyRange`" ) - curr_editor <- rstudioapi::getSourceEditorContext() + curr_editor <- rstudioapi_getSourceEditorContext() id <- curr_editor$id sel_rng <- curr_editor$selection[[1]]$range @@ -34,28 +32,31 @@ insert_ns <- function() { mod_text <- paste0("ns(", sel_text, ")") - rstudioapi::modifyRange(sel_rng, mod_text, id = id) + rstudioapi_modifyRange( + sel_rng, + mod_text, + id = id + ) } -#' @importFrom fs path file_exists go_to <- function( file, wd = golem::get_golem_wd() ) { - file <- path( + file <- fs_path( wd, file ) - if (!file_exists(file)) { + if (!fs_file_exists(file)) { message(file, "not found.") } stop_if_not( - rstudioapi::hasFun("navigateToFile"), + rstudioapi_hasFun("navigateToFile"), msg = "Your version of RStudio does not support `navigateToFile`" ) - rstudioapi::navigateToFile(file) + rstudioapi_navigateToFile(file) } #' @rdname addins diff --git a/R/boostrap_cli.R b/R/boostrap_cli.R new file mode 100644 index 00000000..f7d6541a --- /dev/null +++ b/R/boostrap_cli.R @@ -0,0 +1,45 @@ +# All the fns here check that {cli} is installed +# before doing anything. +check_cli_installed <- function(reason = "to have attractive command line interfaces with {golem}.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`.") { + rlang::check_installed( + "cli", + version = "2.0.0", + reason = reason + ) +} + +cli_cat_bullet <- function(...) { + check_cli_installed() + do_if_unquiet({ + cli::cat_bullet(...) + }) +} + + +cli_cat_line <- function(...) { + check_cli_installed() + + do_if_unquiet({ + cli::cat_line(...) + }) +} + +cli_cat_rule <- function(...) { + check_cli_installed() + + do_if_unquiet({ + cli::cat_rule( + ... + ) + }) +} + +cli_cli_alert_info <- function(...) { + check_cli_installed() + + do_if_unquiet({ + cli::cli_alert_info( + ... + ) + }) +} diff --git a/R/boostrap_crayon.R b/R/boostrap_crayon.R new file mode 100644 index 00000000..dda3a842 --- /dev/null +++ b/R/boostrap_crayon.R @@ -0,0 +1,15 @@ +# All the fns here check that {cli} is installed +# before doing anything. +check_crayon_installed <- function(reason = "to have attractive command line interfaces with {golem}.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`.") { + rlang::check_installed( + "crayon", + reason = reason + ) +} + + +# from usethis https://github.com/r-lib/usethis/ +crayon_darkgrey <- function(x) { + check_crayon_installed() + x <- crayon::make_style("darkgrey")(x) +} diff --git a/R/boostrap_fs.R b/R/boostrap_fs.R new file mode 100644 index 00000000..55ab5b83 --- /dev/null +++ b/R/boostrap_fs.R @@ -0,0 +1,82 @@ +# All the fns here check that {fs} is installed +# before doing anything. +check_fs_installed <- function() { + rlang::check_installed( + "fs", + reason = "for file & directory manipulation.\nYou can install all {golem} dev dependencies with `golem::install_dev_deps()`." + ) +} + +fs_dir_exists <- function(path) { + check_fs_installed() + fs::dir_exists(path) +} + +fs_dir_create <- function( + path, + ..., + mode = "u=rwx,go=rx", + recurse = TRUE, + recursive +) { + check_fs_installed() + fs::dir_create( + path, + ..., + mode = mode, + recurse = recurse, + recursive = recursive + ) +} + + +fs_file_create <- function(where) { + check_fs_installed() + fs::file_create(where) +} + +fs_file_delete <- function(path) { + check_fs_installed() + fs::file_delete(path) +} + +fs_file_exists <- function(path) { + check_fs_installed() + fs::file_exists(path) +} + +fs_path_abs <- function(path) { + check_fs_installed() + fs::path_abs(path) +} + +fs_path <- function(..., ext = "") { + check_fs_installed() + fs::path(..., ext = ext) +} + +fs_file_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::file_copy( + path = path, + new_path = new_path, + overwrite + ) +} + +fs_dir_copy <- function( + path, + new_path, + overwrite = FALSE +) { + check_fs_installed() + fs::dir_copy( + path, + new_path, + overwrite + ) +} diff --git a/R/bootstrap_attachment.R b/R/bootstrap_attachment.R new file mode 100644 index 00000000..0f0e831d --- /dev/null +++ b/R/bootstrap_attachment.R @@ -0,0 +1,27 @@ +# All the fns here check that {attachment} is installed +# before doing anything. +check_attachment_installed <- function() { + rlang::check_installed( + "attachment", + version = "0.3.1", + reason = "to build a Dockerfile." + ) +} + +attachment_create_renv_for_prod <- function( + path = ".", + output = "renv.lock.prod", + dev_pkg = "remotes", + check_if_suggests_is_installed = FALSE, + document = FALSE, + ... +) { + attachment::create_renv_for_prod( + path = path, + output = output, + dev_pkg = dev_pkg, + document = document, + check_if_suggests_is_installed = check_if_suggests_is_installed, + ... + ) +} diff --git a/R/bootstrap_desc.R b/R/bootstrap_desc.R new file mode 100644 index 00000000..c9fa0ce9 --- /dev/null +++ b/R/bootstrap_desc.R @@ -0,0 +1,40 @@ +# All the fns here check that {desc} is installed +# before doing anything. +check_desc_installed <- function() { + rlang::check_installed( + "desc", + reason = "to fill DESCRIPTION." + ) +} + +desc_description <- function(file) { + check_desc_installed() + desc::description$new( + file = file + ) +} + +desc_get <- function(keys) { + check_desc_installed() + desc::desc_get(keys) +} + +desc_get_version <- function() { + check_desc_installed() + desc::desc_get_version() +} + +desc_get_deps <- function(file = NULL) { + check_desc_installed() + desc::desc_get_deps(file) +} + +desc_get_field <- function(key) { + check_desc_installed() + desc::desc_get_field(key) +} + +desc_get_author <- function() { + check_desc_installed() + desc::desc_get_author() +} diff --git a/R/bootstrap_dockerfiler.R b/R/bootstrap_dockerfiler.R new file mode 100644 index 00000000..9d284875 --- /dev/null +++ b/R/bootstrap_dockerfiler.R @@ -0,0 +1,68 @@ +# All the fns here check that {dockerfiler} is installed +# before doing anything. +check_dockerfiler_installed <- function() { + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) +} + +dockerfiler_dock_from_renv <- function( + lockfile = "renv.lock", + distro = "focal", + FROM = "rocker/r-base", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL +) { + check_dockerfiler_installed() + dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs + ) +} + +dockerfiler_dock_from_desc <- function( + path = "DESCRIPTION", + FROM = paste0( + "rocker/r-ver:", + R.Version()$major, + ".", + R.Version()$minor + ), + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + update_tar_gz = TRUE, + build_from_source = TRUE, + extra_sysreqs = NULL +) { + check_dockerfiler_installed() + dockerfiler::dock_from_desc( + path = path, + FROM = FROM, + AS = AS, + sysreqs = sysreqs, + repos = repos, + expand = expand, + update_tar_gz = update_tar_gz, + build_from_source = build_from_source, + extra_sysreqs = extra_sysreqs + ) +} + + +dockerfiler_Dockerfile <- function() { + check_dockerfiler_installed() + dockerfiler::Dockerfile +} diff --git a/R/bootstrap_pkgload.R b/R/bootstrap_pkgload.R new file mode 100644 index 00000000..7429f31b --- /dev/null +++ b/R/bootstrap_pkgload.R @@ -0,0 +1,43 @@ +# All the fns here check that {pkgload} is installed +# before doing anything. +check_pkgload_installed <- function() { + rlang::check_installed( + "pkgload", + reason = "to load the package." + ) +} + + + + +pkgload_load_all <- function( + path = ".", + reset = TRUE, + compile = NA, + attach = TRUE, + export_all = TRUE, + export_imports = export_all, + helpers = TRUE, + attach_testthat = uses_testthat(path), + quiet = NULL, + recompile = FALSE, + warn_conflicts = TRUE +) { + check_roxygen2_installed() + check_pkgload_installed() + + uses_testthat <- getFromNamespace("uses_testthat", "pkgload") + pkgload::load_all( + path = path, + reset = reset, + compile = compile, + attach = attach, + export_all = export_all, + export_imports = export_imports, + helpers = helpers, + attach_testthat = attach_testthat, + quiet = quiet, + recompile = recompile, + warn_conflicts = warn_conflicts + ) +} diff --git a/R/bootstrap_roxygen2.R b/R/bootstrap_roxygen2.R new file mode 100644 index 00000000..f958842a --- /dev/null +++ b/R/bootstrap_roxygen2.R @@ -0,0 +1,23 @@ +# All the fns here check that {roxygen2} is installed +# before doing anything. +check_roxygen2_installed <- function() { + rlang::check_installed( + "roxygen2", + reason = "to document the package." + ) +} + +roxygen2_roxygenise <- function( + package.dir = ".", + roclets = NULL, + load_code = NULL, + clean = FALSE +) { + check_roxygen2_installed() + roxygen2::roxygenise( + package.dir = package.dir, + roclets = roclets, + load_code = load_code, + clean = clean + ) +} diff --git a/R/bootstrap_rstudio_api.R b/R/bootstrap_rstudio_api.R new file mode 100644 index 00000000..7aa21fb2 --- /dev/null +++ b/R/bootstrap_rstudio_api.R @@ -0,0 +1,51 @@ +check_rstudioapi_installed <- function(reason = "to manipulate RStudio files.") { + rlang::check_installed( + "rstudioapi", + reason = reason + ) +} + + +rstudioapi_navigateToFile <- function(output) { + # Don't suggest to install + if (rlang::is_installed("rstudioapi")) { + if ( + rstudioapi::isAvailable() & + rstudioapi::hasFun("navigateToFile") + ) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } + } else { + try(file.edit(output)) + } +} + +rstudioapi_hasFun <- function(fun) { + # Default to FALSE so that it's FALSE + # If package is not installed + hasFun <- FALSE + if (rlang::is_installed("rstudioapi")) { + hasFun <- rstudioapi::hasFun(fun) + } + hasFun +} + +rstudioapi_getSourceEditorContext <- function() { + check_rstudioapi_installed() + rstudioapi::getSourceEditorContext() +} + +rstudioapi_modifyRange <- function( + location = NULL, + text = NULL, + id = NULL +) { + check_rstudioapi_installed() + rstudioapi::modifyRange( + location = location, + text = text, + id = id + ) +} diff --git a/R/bootstrap_usethis.R b/R/bootstrap_usethis.R new file mode 100644 index 00000000..a92ad3a1 --- /dev/null +++ b/R/bootstrap_usethis.R @@ -0,0 +1,127 @@ + +# All the fns here check that {usethis} is installed +# before doing anything. +check_usethis_installed <- function(reason = "for project and file manipulation.") { + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = reason + ) +} + +usethis_use_build_ignore <- function( + files, + escape = TRUE +) { + check_usethis_installed( + reason = "to ignore files in the build." + ) + usethis::use_build_ignore( + files, + escape + ) +} +usethis_use_package <- function( + package, + type = "Imports", + min_version = NULL +) { + check_usethis_installed( + reason = "to add dependencies to DESCRIPTION." + ) + usethis::use_package( + package, + type, + min_version + ) +} + +usethis_create_project <- function( + path, + rstudio = rstudioapi::isAvailable(), # rstudioap is usethis Imports, so its ok + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to create a project." + ) + usethis::create_project( + path, + rstudio, + open + ) +} +usethis_use_latest_dependencies <- function( + overwrite = FALSE, + source = c("local", "CRAN") +) { + check_usethis_installed( + reason = "to set dependency version." + ) + usethis::use_latest_dependencies( + overwrite, + source + ) +} + +usethis_proj_set <- function( + path = ".", + force = FALSE +) { + check_usethis_installed( + reason = "to set project." + ) + usethis::proj_set( + path, + force + ) +} +usethis_use_testthat <- function( + edition = NULL, + parallel = FALSE +) { + check_usethis_installed( + reason = "to add {testthat} infrastructure." + ) + usethis::use_testthat( + edition, + parallel + ) +} +usethis_use_test <- function( + name = NULL, + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to add tests." + ) + usethis::use_test( + name, + open + ) +} + +usethis_use_spell_check <- function( + vignettes = TRUE, + lang = "en-US", + error = FALSE +) { + check_usethis_installed( + reason = "to add spellcheck." + ) + usethis::use_spell_check( + vignettes, + lang, + error + ) +} + +usethis_use_readme_rmd <- function( + open = rlang::is_interactive() +) { + check_usethis_installed( + reason = "to create a readme." + ) + usethis::use_readme_rmd( + open = open + ) +} diff --git a/R/browser_button.R b/R/browser_button.R index 55e11776..4e3bff4c 100644 --- a/R/browser_button.R +++ b/R/browser_button.R @@ -6,18 +6,18 @@ #' Prints the code to the console. #' @export #' -#' @importFrom cli cat_rule cat_line + browser_button <- function() { - cat_rule("To be copied in your UI") - cat_line(darkgrey('actionButton("browser", "browser"),')) - cat_line(darkgrey('tags$script("$(\'#browser\').hide();")')) - cat_line() - cat_rule("To be copied in your server") - cat_line(darkgrey("observeEvent(input$browser,{")) - cat_line(darkgrey(" browser()")) - cat_line(darkgrey("})")) - cat_line() - cat_line("By default, this button will be hidden.") - cat_line("To show it, open your web browser JavaScript console") - cat_line("And run $('#browser').show();") + cli_cat_rule("To be copied in your UI") + cli_cat_line(crayon_darkgrey('actionButton("browser", "browser"),')) + cli_cat_line(crayon_darkgrey('tags$script("$(\'#browser\').hide();")')) + cli_cat_line() + cli_cat_rule("To be copied in your server") + cli_cat_line(crayon_darkgrey("observeEvent(input$browser,{")) + cli_cat_line(crayon_darkgrey(" browser()")) + cli_cat_line(crayon_darkgrey("})")) + cli_cat_line() + cli_cat_line("By default, this button will be hidden.") + cli_cat_line("To show it, open your web browser JavaScript console") + cli_cat_line("And run $('#browser').show();") } diff --git a/R/bundle_resources.R b/R/bundle_resources.R index faa2b2d2..8756d708 100644 --- a/R/bundle_resources.R +++ b/R/bundle_resources.R @@ -39,7 +39,7 @@ bundle_resources <- function( ) > 0 ) { res[[ - length(res) + 1 + length(res) + 1 ]] <- htmltools::htmlDependency( name, version, @@ -84,7 +84,7 @@ bundle_resources <- function( for (i in css_nms) { res[[ - length(res) + 1 + length(res) + 1 ]] <- tags$link( href = i, rel = "stylesheet" @@ -95,7 +95,7 @@ bundle_resources <- function( if (with_sparkles) { res[[ - length(res) + 1 + length(res) + 1 ]] <- htmlDependency( "sparkles", version = utils::packageVersion("golem"), diff --git a/R/config.R b/R/config.R index fcddbe10..c1b83cdb 100644 --- a/R/config.R +++ b/R/config.R @@ -1,69 +1,123 @@ +# This file contains everything related to the +# manipulation of the golem-config file + +# We first need something to guess where the file +# is. 99.99% of the time it will be +# ./inst/golem-config.yml but if for some reason +# you're somewhere else, functions still need to +# work + #' @importFrom attempt attempt is_try_error -#' @importFrom fs path path_abs guess_where_config <- function( - path = ".", + path = golem::pkg_path(), file = "inst/golem-config.yml" ) { - # Trying with "golem.config.path" option - custom_path <- getOption("golem.config.path") - if ( - !is.null(custom_path) && - file.exists(custom_path) - ) { - return(path_abs(custom_path)) - } - # Trying the path - ret_path <- path( + # We'll try to guess where the path + # to the golem-config file is + + # This one should be correct in 99% of the case + # If we don't change the default values of the params. + # => current directory /inst/golem-config.yml + ret_path <- fs_path( path, file ) - if (file_exists(ret_path)) { - return(path_abs(ret_path)) + if (fs_file_exists(ret_path)) { + return(fs_path_abs(ret_path)) } - # Trying maybe in the wd +# # Trying with "golem.config.path" option + # custom_path <- getOption("golem.config.path") + # if ( + # !is.null(custom_path) && + # file.exists(custom_path) + # ) { + # return(path_abs(custom_path)) + # } + # Maybe for some reason we are in inst/ ret_path <- "golem-config.yml" - if (file_exists(ret_path)) { - return(path_abs(ret_path)) + if (fs_file_exists(ret_path)) { + return(fs_path_abs(ret_path)) } - # Trying with pkgpath + + # Trying with pkg_path ret_path <- attempt({ - path( + fs_path( golem::pkg_path(), "inst/golem-config.yml" ) }) + if ( - !is_try_error(ret_path) & - file_exists(ret_path) + !is_try_error(ret_path) && + fs_file_exists(ret_path) ) { return( - path_abs(ret_path) + fs_path_abs(ret_path) ) } + + ret_path <- try_user_config_location(pth = golem::pkg_path()) + if (fs_file_exists(ret_path)) { + return(fs_path_abs(ret_path)) + } + return(NULL) } -#' @importFrom fs file_copy path -get_current_config <- function( - path = ".", - set_options = TRUE -) { +try_user_config_location <- function(pth) { + # I. try to retrieve from possible user-change in app_config.R + user_location_default <- file.path(pth, "R/app_config.R") + if (isFALSE(fs_file_exists(user_location_default))) return(NULL) + + # II. if successfull, read file and find line where new config is located + tmp_guess_text <- readLines(user_location_default) + tmp_guess_line <- which(grepl("app_sys\\(", tmp_guess_text)) + if (identical(integer(0), tmp_guess_line)) return(NULL) + + # III. if successfull, identify the char that gives new config-path + tmp_config_expr <- regexpr("\\(.*\\)$", tmp_guess_text[tmp_guess_line]) + tmp_config_char <- regmatches( + tmp_guess_text[tmp_guess_line], + tmp_config_expr + ) + + # IV. clean that char from artefacts of regexpr() + out_config_char <- substr( + substring(tmp_config_char, 3), + start = 1, + stop = nchar(substring(tmp_config_char, 3)) - 2 + ) + + # V. return full path to new config file including pkg-path and 'inst' + return(file.path(pth, "inst", out_config_char)) +} +#' Get the path to the current config File +#' +#' This function tries to guess where the golem-config file is located. +#' If it can't find it, this function asks the +#' user if they want to set the golem skeleton. +#' +#' @param path Path to start looking for the config +#' +#' @export +get_current_config <- function(path = getwd()) { # We check wether we can guess where the config file is path_conf <- guess_where_config(path) + # We default to inst/ if this doesn't exist if (is.null(path_conf)) { - path_conf <- path( + path_conf <- fs_path( path, "inst/golem-config.yml" ) } - if (!file_exists(path_conf)) { + if (!fs_file_exists(path_conf)) { if (interactive()) { ask <- yesno( sprintf( - "The %s file doesn't exist, create?", + "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", basename(path_conf) ) ) @@ -72,31 +126,29 @@ get_current_config <- function( return(NULL) } - file_copy( + fs_file_copy( path = golem_sys("shinyexample/inst/golem-config.yml"), - new_path = path( + new_path = fs_path( path, "inst/golem-config.yml" ) ) - file_copy( + fs_file_copy( path = golem_sys("shinyexample/R/app_config.R"), - new_path = file.path( + new_path = fs_path( path, "R/app_config.R" ) ) replace_word( - path( + fs_path( path, "R/app_config.R" ), "shinyexample", golem::pkg_name() ) - if (set_options) { - set_golem_options() - } + # TODO This should also create the dev folder } else { stop( sprintf( @@ -112,11 +164,14 @@ get_current_config <- function( ) } +# This function changes the name of the +# package in app_config when you need to +# set the {golem} name change_app_config_name <- function( name, path = get_golem_wd() ) { - pth <- fs::path(path, "R", "app_config.R") + pth <- fs_path(path, "R", "app_config.R") app_config <- readLines(pth) where_system.file <- grep("system.file", app_config) @@ -129,58 +184,3 @@ change_app_config_name <- function( ) write(app_config, pth) } - - -# find and tag expressions in a yaml -# used internally in `amend_golem_config` -#' @importFrom utils modifyList -find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) - conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) - - expr_list <- lapply(names(conf), function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - }) - names(expr_list) <- names(conf) - expr_list <- Filter(function(x) length(x) > 0, expr_list) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply(expr_list, add_expr_tag) - modifyList(conf, tagged_exprs) -} - -#' Amend golem config file -#' -#' @param key key of the value to add in `config` -#' @inheritParams config::get -#' @inheritParams add_module -#' @inheritParams set_golem_options -#' -#' @export -#' @importFrom yaml read_yaml write_yaml -#' @importFrom attempt stop_if -#' -#' @return Used for side effects. -amend_golem_config <- function( - key, - value, - config = "default", - pkg = get_golem_wd(), - talkative = TRUE -) { - conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) - invisible(TRUE) -} diff --git a/R/create_golem.R b/R/create_golem.R index 35e9e8f2..4add58e5 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -1,3 +1,37 @@ +replace_package_name <- function( + copied_files, + package_name, + path_to_golem +) { + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path_to_golem, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path_to_golem, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } +} + + + #' Create a package for a Shiny App using `{golem}` #' #' @param path Name of the folder to create the package in. @@ -21,11 +55,7 @@ #' For compatibility issue, this function turns `options(shiny.autoload.r)` #' to `FALSE`. See https://github.com/ThinkR-open/golem/issues/468 for more background. #' -#' @importFrom cli cat_rule cat_line #' @importFrom utils getFromNamespace -#' @importFrom rstudioapi isAvailable openProject hasFun -#' @importFrom usethis use_latest_dependencies create_project -#' @importFrom fs dir_copy #' @importFrom yaml write_yaml #' #' @export @@ -42,16 +72,27 @@ create_golem <- function( with_git = FALSE, ... ) { - path <- normalizePath(path, mustWork = FALSE) + path_to_golem <- normalizePath( + path, + mustWork = FALSE + ) if (check_name) { - cat_rule("Checking package name") - getFromNamespace("check_package_name", "usethis")(package_name) + cli_cat_rule("Checking package name") + rlang::check_installed( + "usethis", + version = "1.6.0", + reason = "to check the package name." + ) + getFromNamespace( + "check_package_name", + "usethis" + )(package_name) cat_green_tick("Valid package name") } - if (dir.exists(path)) { + if (fs_dir_exists(path_to_golem)) { if (!isTRUE(overwrite)) { stop( paste( @@ -66,20 +107,27 @@ create_golem <- function( cat_red_bullet("Overwriting existing project.") } } else { - cat_rule("Creating dir") - usethis::create_project( - path = path, - open = FALSE, + cli_cat_rule("Creating dir") + usethis_create_project( + path = path_to_golem, + open = FALSE ) + if (!file.exists(".here")) { + here::set_here(path_to_golem) + } cat_green_tick("Created package directory") } - cat_rule("Copying package skeleton") + cli_cat_rule("Copying package skeleton") from <- golem_sys("shinyexample") # Copy over whole directory - dir_copy(path = from, new_path = path, overwrite = TRUE) + fs_dir_copy( + path = from, + new_path = path_to_golem, + overwrite = TRUE + ) # Listing copied files ***from source directory*** copied_files <- list.files( @@ -89,59 +137,30 @@ create_golem <- function( recursive = TRUE ) - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path, f) - - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path, gsub("REMOVEME", "", f)) - } - - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } + replace_package_name( + copied_files, + package_name, + path_to_golem + ) cat_green_tick("Copied app skeleton") + old <- setwd(path_to_golem) - cat_rule("Setting the default config") - - yml_path <- file.path(path, "inst/golem-config.yml") - conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - yaml_golem_wd <- "here::here()" - attr(yaml_golem_wd, "tag") <- "!expr" - conf$dev$golem_wd <- yaml_golem_wd - conf$default$golem_name <- package_name - conf$default$golem_version <- "0.0.0.9000" - write_yaml(conf, yml_path) - - cat_green_tick("Configured app") + cli_cat_rule("Running project hook function") - - cat_rule("Running project hook function") - - old <- setwd(path) # TODO fix # for some weird reason test() fails here when using golem::create_golem # and I don't have time to search why rn if (substitute(project_hook) == "golem::project_hook") { project_hook <- getFromNamespace("project_hook", "golem") } - project_hook(path = path, package_name = package_name, ...) + project_hook( + path = path_to_golem, + package_name = package_name, + ... + ) + setwd(old) cat_green_tick("All set") @@ -150,8 +169,8 @@ create_golem <- function( if (isTRUE(without_comments)) { files <- list.files( path = c( - file.path(path, "dev"), - file.path(path, "R") + file.path(path_to_golem, "dev"), + file.path(path_to_golem, "R") ), full.names = TRUE ) @@ -162,9 +181,9 @@ create_golem <- function( if (isTRUE(with_git)) { - cat_rule("Initializing git repository") + cli_cat_rule("Initializing git repository") git_output <- system( - command = paste("git init", path), + command = paste("git init", path_to_golem), ignore.stdout = TRUE, ignore.stderr = TRUE ) @@ -176,11 +195,16 @@ create_golem <- function( } - old <- setwd(path) - use_latest_dependencies() + old <- setwd(path_to_golem) + + if (!requireNamespace("desc", quietly = TRUE)) { + check_desc_installed() + } # incase of {desc} not installed by {usethis} + + usethis_use_latest_dependencies() # No .Rprofile for now - # cat_rule("Appending .Rprofile") + # cli_cat_rule("Appending .Rprofile") # write("# Sourcing user .Rprofile if it exists ", ".Rprofile", append = TRUE) # write("home_profile <- file.path(", ".Rprofile", append = TRUE) # write(" Sys.getenv(\"HOME\"), ", ".Rprofile", append = TRUE) @@ -197,32 +221,37 @@ create_golem <- function( setwd(old) + cli_cat_rule("Done") - cat_rule("Done") - cat_line( + cli_cat_line( paste0( "A new golem named ", package_name, " was created at ", - normalizePath(path), + path_to_golem, " .\n", "To continue working on your app, start editing the 01_start.R file." ) ) + check_dev_deps_are_installed() + if (isTRUE(open)) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("openProject")) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("openProject") + ) { rstudioapi::openProject(path = path) } else { setwd(path) } } - return( invisible( - normalizePath(path) + path_to_golem ) ) } diff --git a/R/desc.R b/R/desc.R index dd120fa9..dec57239 100644 --- a/R/desc.R +++ b/R/desc.R @@ -3,16 +3,12 @@ #' @param pkg_name The name of the package #' @param pkg_title The title of the package #' @param pkg_description Description of the package -#' @param author_first_name First Name of the author -#' @param author_last_name Last Name of the author -#' @param author_email Email of the author -#' @param author_orcid ORCID of the author +#' @param authors a character string (or vector) of class person +#' (see [person()] for details) #' @param repo_url URL (if needed) +#' @param pkg_version The version of the package. Default is 0.0.0.9000 #' @param pkg Path to look for the DESCRIPTION. Default is `get_golem_wd()`. #' -#' @importFrom desc description -#' @importFrom cli cat_bullet -#' @importFrom fs path path_abs #' #' @export #' @@ -21,58 +17,82 @@ fill_desc <- function( pkg_name, pkg_title, pkg_description, - author_first_name, - author_last_name, - author_email, - author_orcid = NULL, + authors = person( + given = NULL, + family = NULL, + email = NULL, + role = NULL, + comment = NULL + ), repo_url = NULL, - pkg = get_golem_wd() + pkg_version = "0.0.0.9000", + pkg = get_golem_wd(), + author_first_name = NULL, + author_last_name = NULL, + author_email = NULL, + author_orcid = NULL ) { - path <- path_abs(pkg) - desc <- desc::description$new( - file = path(path, "DESCRIPTION") - ) - - if (!is.null(author_orcid) & !is.character(author_orcid)) { - stop("ORCID ID must be provided as a character object") - } + stopifnot(`'authors' must be of class 'person'` = inherits(authors, "person")) + # Handling retrocompatibility - if (is.null(author_orcid)) { - desc$set( - "Authors@R", - sprintf( - "person('%s', '%s', email = '%s', role = c('cre', 'aut'))", - author_first_name, - author_last_name, - author_email - ) - ) - } else { - desc$set( - "Authors@R", - sprintf( - "person('%s', '%s', email = '%s', role = c('cre', 'aut'), comment = c(ORCID = '%s'))", + # Case 1 : old author params are not null + any_author_params_is_not_null <- all( + vapply( + list( author_first_name, author_last_name, author_email, author_orcid - ) + ), is.null, logical(1) ) + ) + + if (!any_author_params_is_not_null) { + warning("The `author_first_name`, `author_last_name`, `author_email` and `author_orcid` parameters will be deprecated from fill_desc() in the next version of {golem}. \nPlease use the `authors` parameter instead.\nSee ?person for more details on how to use it.") + # Case 1.1 : old author params are null and authors is empty + if (length(authors) == 0) { + # We use the old author params to fill the DESCRIPTION file + cli_cli_alert_info( + "the `authors` argument is empty, using `author_first_name`, `author_last_name`, `author_email` and `author_orcid` to fill the DESCRIPTION file." + ) + authors <- person( + given = author_first_name, + family = author_last_name, + email = author_email, + role = NULL, + comment = c(ORCID = author_orcid) + ) + } else { + # Case 1.2, old author params are null and authors is not empty + # We keep the authors as is + cli_cli_alert_info( + "the `authors` argument is not empty, using it to fill the DESCRIPTION file, the old author params are ignored." + ) + } } + # the else here is the case 2 : old author params are null and authors is set, we keep the authors as is + + path <- fs_path_abs(pkg) + + desc <- desc_description( + file = fs_path(path, "DESCRIPTION") + ) + desc$set_authors(authors) + desc$del( keys = "Maintainer" ) desc$set_version( - version = "0.0.0.9000" + version = pkg_version ) set_golem_version( - version = "0.0.0.9000", - path = path + version = pkg_version, + pkg = path ) desc$set( - Package = pkg_name + Package = as.character(pkg_name) ) change_app_config_name( name = pkg_name, @@ -108,7 +128,7 @@ fill_desc <- function( file = "DESCRIPTION" ) - cat_bullet( + cli_cat_bullet( "DESCRIPTION file modified", bullet = "tick", bullet_col = "green" diff --git a/R/disable_autoload.R b/R/disable_autoload.R index 0e8f7993..9414ada2 100644 --- a/R/disable_autoload.R +++ b/R/disable_autoload.R @@ -10,17 +10,17 @@ #' } #' @return The path to the file, invisibly. disable_autoload <- function(pkg = get_golem_wd()) { - fls <- fs::path( + fls <- fs_path( pkg, "R", "_disable_autoload.R" ) - if (fs::file_exists(fls)) { + if (fs_file_exists(fls)) { cat_red_bullet( "_disable_autoload.R already exists, skipping its creation." ) } else { - cli::cat_rule("Creating _disable_autoload.R") + cli_cat_rule("Creating _disable_autoload.R") write( "# Disabling shiny autoload\n\n# See ?shiny::loadSupport for more information", fls diff --git a/R/enable_roxygenize.R b/R/enable_roxygenize.R index de76ac87..46173661 100644 --- a/R/enable_roxygenize.R +++ b/R/enable_roxygenize.R @@ -8,13 +8,13 @@ enable_roxygenize <- function(path = list.files( pattern = "Rproj$", full.names = TRUE )[1]) { - cat_bullet( + cli_cat_bullet( sprintf("Reading %s content ", basename(path)), bullet = "info", bullet_col = "green" ) source <- yaml::read_yaml(file = path) - cat_bullet( + cli_cat_bullet( "Enable roxygen2", bullet = "info", bullet_col = "green" diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R new file mode 100644 index 00000000..613e720f --- /dev/null +++ b/R/golem-yaml-get.R @@ -0,0 +1,101 @@ + +#' @importFrom config get +get_golem_things <- function( + value, + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path +) { + conf_path <- get_current_config( + path + ) + 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, + pkg = golem::pkg_path() +) { + path <- fs_path_abs(pkg) + + pth <- get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) + if (is.null(pth)) { + pth <- golem::pkg_path() + } + return(pth) +} + +#' @export +#' @rdname golem_opts +get_golem_name <- function( + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + pkg = golem::pkg_path() +) { + path <- fs_path_abs(pkg) + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + return(nm) +} + +#' @export +#' @rdname golem_opts +get_golem_version <- function( + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + pkg = golem::pkg_path() +) { + path <- fs_path_abs(pkg) + vers <- get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(vers)) { + vers <- golem::pkg_version() + } + return(vers) +} diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R new file mode 100644 index 00000000..b84fb461 --- /dev/null +++ b/R/golem-yaml-set.R @@ -0,0 +1,207 @@ +#' @export +#' @rdname golem_opts +set_golem_wd <- function( + golem_wd = golem::pkg_path(), + pkg = golem::pkg_path(), + talkative = TRUE + ) { + if ( + golem_wd == "golem::pkg_path()" | + normalizePath(golem_wd) == normalizePath(golem::pkg_path()) + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" + } else { + golem_yaml_path <- fs_path_abs(golem_wd) + } + + amend_golem_config( + key = "golem_wd", + value = golem_yaml_path, + config = "dev", + pkg = pkg, + talkative = talkative + ) + + invisible(golem_yaml_path) +} + +#' @export +#' @rdname golem_opts +set_golem_name <- function( + name = golem::pkg_name(), + pkg = golem::pkg_path(), + talkative = TRUE, + old_name = golem::pkg_name() +) { + path <- fs_path_abs(pkg) + + # Changing in YAML + amend_golem_config( + key = "golem_name", + value = name, + config = "default", + pkg = pkg, + talkative = talkative + ) + + # Changing in app-config.R + change_app_config_name( + name = name, + path = path + ) + + # Changing in DESCRIPTION + desc <- desc_description( + file = fs_path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) + + # Changing in ./tests/ if dir present + set_golem_name_tests( + old_name = old_name, + new_name = name, + path = path + ) + + # Changing in ./vignettes/ if dir present + set_golem_name_vignettes( + old_name = old_name, + new_name = name, + path = path + ) + + if (old_name != name){ + cli_cli_alert_info( + sprintf("Please note that the old name %s might still be in some places, for example in the ./docs folder.", old_name) + ) + cli_cli_alert_info( + "You might need to change it manually there.", + ) + } + + invisible(name) +} + +set_golem_name_tests <- function( + old_name, + new_name, + path + ) { + pth_dir_tests <- file.path( + path, + "tests" + ) + + check_dir_tests <- fs_dir_exists(pth_dir_tests) + + if (check_dir_tests) { + pth_testthat_r <- file.path(pth_dir_tests, "testthat.R") + old_testthat_r <- readLines(pth_testthat_r) + new_testthat_r <- gsub(old_name, new_name, old_testthat_r) + writeLines(new_testthat_r, pth_testthat_r) + } + + return(invisible(old_name)) +} + +set_golem_name_vignettes <- function( + old_name, + new_name, + path + ) { + pth_dir_vignettes <- file.path( + path, + "vignettes" + ) + + check_dir_vignettes <- fs_dir_exists(pth_dir_vignettes) + + if (check_dir_vignettes) { + pth_vignette_old <- file.path( + pth_dir_vignettes, + paste0(old_name, ".Rmd") + ) + old_vignette_r <- readLines(pth_vignette_old) + new_vignette_r <- gsub(old_name, new_name, old_vignette_r) + + pth_vignette_new <- file.path( + pth_dir_vignettes, + paste0(new_name, ".Rmd") + ) + writeLines(new_vignette_r, pth_vignette_new) + file.remove(pth_vignette_old) + } + + return(invisible(old_name)) +} + +#' @export +#' @rdname golem_opts +set_golem_version <- function( + version = golem::pkg_version(), + pkg = golem::pkg_path(), + talkative = TRUE + ) { + path <- fs_path_abs(pkg) + + # Changing in YAML + amend_golem_config( + key = "golem_version", + value = as.character(version), + config = "default", + pkg = pkg, + talkative = talkative + ) + + desc <- desc_description( + file = fs_path( + path, + "DESCRIPTION" + ) + ) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) + + invisible(version) +} + + +#' Set things in golem config +#' +#' @param key,value entries in the yaml +#' @param path path to the config file +#' @param talkative Should things be printed? +#' @param config config context to write to +#' +#' @noRd +#' +# set_golem_things <- function( +# key, +# value, +# config_file, +# talkative = TRUE, +# config = "default" +# ) { +# amend_golem_config( +# key = key, +# value = value, +# config = config, +# pkg = config_file, +# talkative = talkative +# ) + +# invisible(path) +# } diff --git a/R/golem-yaml-utils.R b/R/golem-yaml-utils.R new file mode 100644 index 00000000..fe495cef --- /dev/null +++ b/R/golem-yaml-utils.R @@ -0,0 +1,110 @@ +# This file contains all the necessary functions +# required to manipualte the golem-config file. + +# find and tag expressions in a yaml +# used internally in `amend_golem_config` +# This is an utilitary function to prevent +# the !expr from being lost in translation +# when manipulating the yaml +# +#' @importFrom utils modifyList +find_and_tag_exprs <- function(conf_path) { + conf <- yaml::read_yaml( + conf_path, + eval.expr = FALSE + ) + conf.eval <- yaml::read_yaml( + conf_path, + eval.expr = TRUE + ) + + expr_list <- lapply( + names(conf), + function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + } + ) + + names(expr_list) <- names(conf) + + expr_list <- Filter( + function(x) length(x) > 0, + expr_list + ) + + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply( + expr_list, + add_expr_tag + ) + + modifyList( + conf, + tagged_exprs + ) +} + +#' Amend golem config file +#' +#' @param key key of the value to add in `config` +#' @inheritParams config::get +#' @inheritParams add_module +#' @inheritParams set_golem_options +#' +#' @export +#' @importFrom yaml read_yaml write_yaml +#' @importFrom attempt stop_if +#' +#' @return Used for side effects. +amend_golem_config <- function( + key, + value, + config = "default", + pkg = golem::pkg_path(), + talkative = TRUE +) { + conf_path <- get_current_config(pkg) + + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + + cat_if_talk <- function( + ..., + fun = cat_green_tick + ) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cli_cat_line + ) + } + + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value + + write_yaml( + conf, + conf_path + ) + + invisible(TRUE) +} diff --git a/R/install_dev_deps.R b/R/install_dev_deps.R new file mode 100644 index 00000000..25595856 --- /dev/null +++ b/R/install_dev_deps.R @@ -0,0 +1,105 @@ +#' Install {golem} dev dependencies +#' +#' This function will run rlang::check_installed() on: +#' + {usethis} +#' + {pkgload} +#' + {dockerfiler} +#' + {devtools} +#' + {roxygen2} +#' + {attachment} +#' + {rstudioapi} +#' + {here} +#' + {fs} +#' + {desc} +#' + {pkgbuild} +#' + {processx} +#' + {rsconnect} +#' + {testthat} +#' + {rstudioapi} +#' +#' @param force_install If force_install is installed, +#' then the user is not interactively asked +#' to install them. +#' @param ... further arguments passed to the install function. +#' +#' @export +#' +#' @examples +#' if (interactive()) { +#' install_dev_deps() +#' } +#' +#' @return Used for side-effects +install_dev_deps <- function( + force_install = FALSE, + ...) { + if (!force_install) { + if (!interactive()) { + # In non interactive mode with force_install turned to FALSE, + # The default will be rlang::check_installed. This function + # will stop in non-interactive mode so we throw a warning message. + warning("`install_dev_deps()` will not install dev dependencies in non-interactive mode if `force_install` is not set to `TRUE`.") + } + # Case 1, which will be the standard case, the user + # runs this function in interactive mode, with force_install + # turned to FALSE. We then use rlang::check_installed() as + # an installation function, as it will ask the user if they + # want to install, which is what they want + f <- rlang::check_installed + } else { + # Case 2, the user runs this function with force_install to FALSE + # At that point, the user probably has pak installed + # If yes, the installation function + # will be pak::pkg_install, otherwise + # it is install.packages + if (rlang::is_installed("pak")) { + # We manage the install with {pak} + f <- getFromNamespace("pkg_install", "pak") + } else { + f <- utils::install.packages + } + } + + for ( + pak in dev_deps + ) { + if (!rlang::is_installed(pak)) { + f(pak, ...) + } + } +} + +dev_deps <- unique( + c( + "attachment", + "cli", + "crayon", + "desc", + "devtools", + "dockerfiler", + "fs", + "here", + "pkgbuild", + "pkgload", + "processx", + "roxygen2", + "renv", + "rsconnect", + "rstudioapi", + "testthat", + "usethis" + ) +) + +check_dev_deps_are_installed <- function() { + are_installed <- sapply( + dev_deps, + FUN = rlang::is_installed + ) + if (!all(are_installed)) { + message( + "We noticed that some dev dependencies are not installed.\n", + "You can install them with `golem::install_dev_deps()`." + ) + } +} diff --git a/R/is_golem.R b/R/is_golem.R new file mode 100644 index 00000000..d391582d --- /dev/null +++ b/R/is_golem.R @@ -0,0 +1,33 @@ +#' Is the directory a golem-based app? +#' +#' Trying to guess if `path` is a golem-based app. +#' +#' @param path Path to the directory to check. +#' Defaults to the current working directory. +#' +#' @export +#' +#' @examples +#' is_golem() +is_golem <- function(path = getwd()) { + files_from_shiny_example <- grep( + "^(?!REMOVEME).*", + list.files( + system.file("shinyexample", package = "golem"), + recursive = TRUE + ), + perl = TRUE, + value = TRUE + ) + files_from_shiny_example <- grep( + "favicon.ico", + files_from_shiny_example, + perl = TRUE, + value = TRUE, + invert = TRUE + ) + + all( + files_from_shiny_example %in% list.files(path, recursive = TRUE) + ) +} diff --git a/R/is_running.R b/R/is_running.R index 4c0c50c2..69d0e760 100644 --- a/R/is_running.R +++ b/R/is_running.R @@ -7,7 +7,6 @@ #' FALSE otherwise. #' @export #' -#' @return A boolean. #' @examples #' is_running() is_running <- function() { diff --git a/R/make_dev.R b/R/make_dev.R index ce5ff1ea..24489ad4 100644 --- a/R/make_dev.R +++ b/R/make_dev.R @@ -26,7 +26,6 @@ make_dev <- function(fun) { #' @export #' #' @rdname prod -#' @return A boolean. app_prod <- function() { getOption("golem.app.prod") %||% FALSE } diff --git a/R/modules_fn.R b/R/modules_fn.R index feee94ad..13b3293b 100644 --- a/R/modules_fn.R +++ b/R/modules_fn.R @@ -19,9 +19,7 @@ #' @note This function will prefix the `name` argument with `mod_`. #' #' @export -#' @importFrom cli cat_bullet #' @importFrom utils file.edit -#' @importFrom fs path_abs path file_create #' #' @seealso [module_template()] #' @@ -39,14 +37,26 @@ add_module <- function( module_template = golem::module_template, with_test = FALSE, ... -) { - name <- file_path_sans_ext(name) + ) { + # Let's start with the checks for the validity of the name + check_name_length(name) + check_name_syntax(name) + + # We now check that: + # - The file name has no "mod_" prefix + # - The file name has no extension + name <- mod_remove( + file_path_sans_ext(name) + ) - old <- setwd(path_abs(pkg)) + # Performing the creation inside the pkg root + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) + # The module creation only works if the R folder + # is there dir_created <- create_if_needed( - path(pkg, "R"), + fs_path(pkg, "R"), type = "directory" ) @@ -55,17 +65,23 @@ add_module <- function( return(invisible(FALSE)) } - where <- path( + # Now we build the correct module file name + where <- fs_path( "R", paste0("mod_", name, ".R") ) - if (!file_exists(where)) { - file_create(where) + # If the file doesn't exist, we create it + if (!fs_file_exists(where)) { + fs_file_create(where) - module_template(name = name, path = where, export = export, ...) + module_template( + name = name, + path = where, + export = export, + ... + ) - # write_there(" ") cat_created(where) open_or_go_to(where, open) } else { @@ -75,20 +91,38 @@ add_module <- function( ) } + # Creating all the files that come with the module if (!is.null(fct)) { - add_fct(fct, module = name, open = open) + add_fct( + fct, + module = name, + open = open + ) } if (!is.null(utils)) { - add_utils(utils, module = name, open = open) + add_utils( + utils, + module = + name, + open = open + ) } if (!is.null(js)) { - add_js_file(js, pkg = pkg, open = open) + add_js_file( + js, + pkg = pkg, + open = open + ) } if (!is.null(js_handler)) { - add_js_handler(js_handler, pkg = pkg, open = open) + add_js_handler( + js_handler, + pkg = pkg, + open = open + ) } if (with_test) { @@ -153,7 +187,7 @@ module_template <- function( ph_ui = " ", ph_server = " ", ... -) { + ) { write_there <- function(...) { write(..., file = path, append = TRUE) } @@ -172,7 +206,7 @@ module_template <- function( } write_there("#'") write_there("#' @importFrom shiny NS tagList ") - write_there(sprintf("mod_%s_ui <- function(id){", name)) + write_there(sprintf("mod_%s_ui <- function(id) {", name)) write_there(" ns <- NS(id)") write_there(" tagList(") write_there(ph_ui) @@ -189,7 +223,7 @@ module_template <- function( } else { write_there("#' @noRd ") } - write_there(sprintf("mod_%s_server <- function(input, output, session){", name)) + write_there(sprintf("mod_%s_server <- function(input, output, session) {", name)) write_there(" ns <- session$ns") write_there(ph_server) write_there("}") @@ -210,7 +244,7 @@ module_template <- function( write_there("#' @noRd ") } write_there(sprintf("mod_%s_server <- function(id){", name)) - write_there(" moduleServer( id, function(input, output, session){") + write_there(" moduleServer(id, function(input, output, session){") write_there(" ns <- session$ns") write_there(ph_server) write_there(" })") @@ -237,8 +271,7 @@ use_module_test <- function( name, pkg = get_golem_wd(), open = TRUE -) { - + ) { # Remove the extension if any name <- file_path_sans_ext(name) # Remove the "mod_" if any @@ -255,30 +288,23 @@ use_module_test <- function( ) } - # We need both testthat, usethis & fs - rlang::check_installed( - "usethis", - "to build the test structure." - ) + # We need testthat rlang::check_installed( "testthat", "to build the test structure." ) - rlang::check_installed( - "fs" - ) - old <- setwd(fs::path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) - if (!dir.exists( - path(pkg, "tests", "testthat") + if (!fs_dir_exists( + fs_path(pkg, "tests", "testthat") )) { - usethis::use_testthat() + usethis_use_testthat() } - path <- fs::path( + path <- fs_path( pkg, "tests", "testthat", @@ -289,8 +315,8 @@ use_module_test <- function( ext = "R" ) - if (!fs::file_exists(path)) { - fs::file_create(path) + if (!fs_file_exists(path)) { + fs_file_create(path) write_there <- function(...) { write(..., file = path, append = TRUE) @@ -347,9 +373,14 @@ use_module_test <- function( } mod_remove <- function(string) { - gsub( - "^mod_", - "", - string - ) + while ( + grepl("^mod_", string) + ) { + string <- gsub( + "^mod_", + "", + string + ) + } + string } diff --git a/R/options.R b/R/options.R deleted file mode 100644 index 134eb2a9..00000000 --- a/R/options.R +++ /dev/null @@ -1,333 +0,0 @@ -#' `{golem}` options -#' -#' Set and get a series of options to be used with `{golem}`. -#' These options are found inside the `golem-config.yml` file, found in most cases -#' inside the `inst` folder. -#' -#' @section Set Functions: -#' + `set_golem_options()` sets all the options, with the defaults from the functions below. -#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. -#' + `set_golem_name()` defaults `golem::pkg_name()` -#' + `set_golem_version()` defaults `golem::pkg_version()` -#' -#' @section Get Functions: -#' Reads the information from `golem-config.yml` -#' + `get_golem_wd()` -#' + `get_golem_name()` -#' + `get_golem_version()` -#' -#' @param golem_name Name of the current golem. -#' @param golem_version Version of the current golem. -#' @param golem_wd Working directory of the current golem package. -#' @param app_prod Is the `{golem}` in prod mode? -#' @param path The path to set the golem working directory. -#' Note that it will be passed to `normalizePath`. -#' @param talkative Should the messages be printed to the console? -#' @param name The name of the app -#' @param version The version of the app -#' @inheritParams config::get -#' -#' @rdname golem_opts -#' -#' @export -#' @importFrom attempt stop_if_not -#' @importFrom yaml read_yaml write_yaml -#' @importFrom usethis proj_set -#' -#' @return Used for side-effects for the setters, and values from the -#' config in the getters. -set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE -) { - change_app_config_name( - name = golem_name, - path = golem_wd - ) - - 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) -} - -#' @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(...) - } - } - - 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_opts -#' @importFrom fs path_abs -set_golem_wd <- function( - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - # Setting wd - - if (path == here::here()) { - path <- "here::here()" - attr(path, "tag") <- "!expr" - } - - set_golem_things( - "golem_wd", - path, - path, - talkative = talkative, - config = "dev" - ) - - invisible(path) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_name <- function( - name = golem::pkg_name(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - # Changing in YAML - set_golem_things( - "golem_name", - name, - path, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = path - ) - - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(name) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_version <- function( - version = golem::pkg_version(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - set_golem_things( - "golem_version", - as.character(version), - path, - talkative = talkative - ) - desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(version) -} - -#' @importFrom config get -get_golem_things <- function( - value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path -) { - conf_path <- get_current_config(path, set_options = TRUE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) -} - - -#' @export -#' @rdname golem_opts -get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) -} - -#' @export -#' @rdname golem_opts -get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - nm -} - -#' @export -#' @rdname golem_opts -get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) -} diff --git a/R/pkg_tools.R b/R/pkg_tools.R index f746c646..adf26591 100644 --- a/R/pkg_tools.R +++ b/R/pkg_tools.R @@ -1,17 +1,18 @@ # Getting the DESCRIPTION file in a data.frame daf_desc <- function( - path = ".", - entry -) { - unlist( - unname( - as.data.frame( - read.dcf( - normalizePath( - file.path(path, "DESCRIPTION") + path = ".", + entry) { + as.character( + unlist( + unname( + as.data.frame( + read.dcf( + normalizePath( + fs_path(path, "DESCRIPTION") + ) ) - ) - )[entry] + )[entry] + ) ) ) } @@ -22,11 +23,11 @@ daf_desc <- function( #' inside your project while developing #' #' @param path Path to use to read the DESCRIPTION -#' @param depth Number of parent directory to visit before -#' deciding we are not in a package. #' #' @export #' @rdname pkg_tools +#' +#' @return The value of the entry in the DESCRIPTION file pkg_name <- function(path = ".") { daf_desc(path, "Package") } @@ -37,28 +38,8 @@ pkg_version <- function(path = ".") { } #' @export #' @rdname pkg_tools -pkg_path <- function( - path = ".", - depth = 3 -) { - # We use depth 3 bcs it's the deepest you can be - # i.e. inst/app/www/ - path <- normalizePath(path) - if (file.exists(file.path(path, "DESCRIPTION"))) { - return(path) - } - for (i in 1:depth) { - path <- dirname(path) - if ( - file.exists( - file.path( - path, - "DESCRIPTION" - ) - ) - ) { - return(path) - } - } - stop("Unable to locate the package path.") +pkg_path <- function() { + # rlang::check_installed("here") + # here::here() + getwd() } diff --git a/R/reload.R b/R/reload.R index 95e062aa..cf547347 100644 --- a/R/reload.R +++ b/R/reload.R @@ -34,8 +34,8 @@ detach_all_attached <- function() { check_name_consistency <- function(pkg) { old_dir <- setwd(pkg) - package_name <- desc::desc_get("Package") - pth <- fs::path( + package_name <- desc_get(keys = "Package") + pth <- fs_path( pkg, "R", "app_config.R" @@ -88,7 +88,6 @@ check_name_consistency <- function(pkg) { #' @inheritParams pkgload::load_all #' #' @param ... Other arguments passed to `pkgload::load_all()` -#' @importFrom rstudioapi isAvailable hasFun documentSaveAll #' @export #' #' @return Used for side-effects @@ -106,13 +105,16 @@ document_and_reload <- function( check_name_consistency(pkg) rlang::check_installed("pkgload") - rlang::check_installed("roxygen2") - if (rstudioapi::isAvailable() & rstudioapi::hasFun("documentSaveAll")) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("documentSaveAll") + ) { rstudioapi::documentSaveAll() } roxed <- try({ - roxygen2::roxygenise( + roxygen2_roxygenise( package.dir = pkg, roclets = roclets, load_code = load_code, @@ -120,14 +122,14 @@ document_and_reload <- function( ) }) if (attempt::is_try_error(roxed)) { - cli::cat_rule( + cli_cat_rule( "Error documenting your package" ) dialog_if_has("Alert", "Error documenting your package") return(invisible(FALSE)) } loaded <- try({ - pkgload::load_all( + pkgload_load_all( pkg, export_all = export_all, helpers = helpers, @@ -137,7 +139,7 @@ document_and_reload <- function( }) if (attempt::is_try_error(loaded)) { - cli::cat_rule( + cli_cat_rule( "Error loading your package" ) dialog_if_has("Alert", "Error loading your package") @@ -145,8 +147,15 @@ document_and_reload <- function( } } -dialog_if_has <- function(title, message, url = "") { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("showDialog")) { +dialog_if_has <- function( + title, + message, + url = "" +) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && rstudioapi::hasFun("showDialog") + ) { rstudioapi::showDialog(title, message, url) } } diff --git a/R/run_dev.R b/R/run_dev.R index fd5f2e98..2479e8c6 100644 --- a/R/run_dev.R +++ b/R/run_dev.R @@ -1,6 +1,7 @@ #' Run run_dev.R #' #' @param file File path to `run_dev.R`. Defaults to `R/run_dev.R`. +#' @param save_all boolean. If TRUE, save all open file before sourcing `file` #' @inheritParams add_module #' #' @export @@ -8,8 +9,19 @@ #' @return Used for side-effect run_dev <- function( file = "dev/run_dev.R", - pkg = get_golem_wd() + pkg = get_golem_wd(), + save_all = TRUE ) { + if (save_all) { + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("documentSaveAll") + ) { + rstudioapi::documentSaveAll() + } + } + # We'll look for the run_dev script in the current dir try_dev <- file.path( diff --git a/R/sanity_check.R b/R/sanity_check.R index 86669ec6..3acc72fb 100644 --- a/R/sanity_check.R +++ b/R/sanity_check.R @@ -8,7 +8,6 @@ #' @rdname sanity_check #' @export #' -#' @importFrom rstudioapi sourceMarkers hasFun isAvailable #' #' @return A DataFrame if any of the words has been found. sanity_check <- function(pkg = get_golem_wd()) { @@ -41,8 +40,14 @@ sanity_check <- function(pkg = get_golem_wd()) { } if (length(source_markers) > 0) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("sourceMarkers")) { - rstudioapi::sourceMarkers("sanity_check", markers = source_markers) + if ( + rlang::is_installed("rstudioapi") && + rstudioapi::isAvailable() && + rstudioapi::hasFun("sourceMarkers")) { + rstudioapi::sourceMarkers( + "sanity_check", + markers = source_markers + ) } return(source_markers) } else { diff --git a/R/set_golem_options.R b/R/set_golem_options.R new file mode 100644 index 00000000..fbd5b5d3 --- /dev/null +++ b/R/set_golem_options.R @@ -0,0 +1,99 @@ +#' `{golem}` options +#' +#' Set and get a series of options to be used with `{golem}`. +#' These options are found inside the `golem-config.yml` file, found in most cases +#' inside the `inst` folder. +#' +#' @section Set Functions: +#' + `set_golem_options()` sets all the options, with the defaults from the functions below. +#' + `set_golem_wd()` defaults to `golem::golem_wd()`, which is the package root when starting a golem. +#' + `set_golem_name()` defaults `golem::pkg_name()` +#' + `set_golem_version()` defaults `golem::pkg_version()` +#' +#' @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 pkg The path to set the golem working directory. +#' Note that it will be passed to `normalizePath`. +#' @param talkative Should the messages be printed to the console? +#' @param name The name of the app +#' @param version The version of the app +#' @param config_file path to the {golem} config file +#' @param old_name The old name of the app, used when changing the name +#' @inheritParams config::get +#' +#' @rdname golem_opts +#' +#' @export +#' @importFrom attempt stop_if_not +#' +#' @return Used for side-effects for the setters, and values from the +#' config in the getters. +set_golem_options <- function( + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) +) { + # TODO here we'll run the + # golem_install_dev_pkg() function + + if (talkative) { + cli_cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } + + # Let's do this in the order of the + # parameters + # Setting name of the golem + set_golem_name( + name = golem_name, + pkg = golem_wd, + talkative = talkative + ) + + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path + + set_golem_wd( + pkg = golem_wd, + talkative = talkative + ) + + # Setting golem_version + set_golem_version( + version = golem_version, + pkg = golem_wd, + talkative = talkative + ) + + # Setting app_prod + amend_golem_config( + "app_prod", + app_prod, + pkg = golem_wd, + talkative = talkative + ) + + # This part is for {usethis} and {here} + if (talkative) { + cli_cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } + + usethis_proj_set(golem_wd) +} diff --git a/R/test_helpers.R b/R/test_helpers.R index 5236ecec..14baa080 100644 --- a/R/test_helpers.R +++ b/R/test_helpers.R @@ -8,9 +8,6 @@ #' @return A testthat result. #' @export #' @rdname testhelpers -#' -#' @examples -#' expect_shinytag(shiny::tags$span("1")) expect_shinytag <- function(object) { rlang::check_installed( "testthat", @@ -27,8 +24,6 @@ expect_shinytag <- function(object) { #' @export #' @rdname testhelpers -#' @examples -#' expect_shinytaglist(shiny::tagList(1)) expect_shinytaglist <- function(object) { rlang::check_installed( "testthat", diff --git a/R/use_favicon.R b/R/use_favicon.R index 65953ccc..1ffd3743 100644 --- a/R/use_favicon.R +++ b/R/use_favicon.R @@ -7,7 +7,6 @@ #' @export #' #' @importFrom attempt stop_if_not -#' @importFrom fs path_abs path file_copy #' #' @return Used for side-effects. #' @@ -34,7 +33,7 @@ use_favicon <- function( ) - local <- fs::file_exists(path) + local <- fs_file_exists(path) if (!local) { if (getRversion() >= "3.5") { @@ -65,15 +64,15 @@ use_favicon <- function( destfile, method = method ) - path <- path_abs(destfile) + path <- fs_path_abs(destfile) } - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) - to <- path( - path_abs(pkg), + to <- fs_path( + fs_path_abs(pkg), "inst/app/www", sprintf( "favicon.%s", @@ -82,7 +81,7 @@ use_favicon <- function( ) if (!(path == to)) { - file_copy( + fs_file_copy( path, to, overwrite = TRUE @@ -101,7 +100,7 @@ use_favicon <- function( "You choose a png favicon, please add `ext = 'png'` to `favicon()` within the `golem_add_external_resources()` function in 'app_ui.R'." ) } else { - cat_line( + cli_cat_line( "Favicon is automatically linked in app_ui via `golem_add_external_resources()`" ) } @@ -109,16 +108,15 @@ use_favicon <- function( #' @rdname favicon #' @export -#' @importFrom fs file_delete file_exists remove_favicon <- function(path = "inst/app/www/favicon.ico") { - if (file_exists(path)) { + if (fs_file_exists(path)) { cat_green_tick( sprintf( "Removing favicon at %s", path ) ) - file_delete(path) + fs_file_delete(path) } else { cat_red_bullet( sprintf( @@ -148,7 +146,7 @@ favicon <- function( resources_path = "www", ext = "ico" ) { - ico <- fs::path( + ico <- fs_path( resources_path, ico, ext = ext diff --git a/R/use_files.R b/R/use_files.R index e41fe94f..1c322f5d 100644 --- a/R/use_files.R +++ b/R/use_files.R @@ -14,8 +14,6 @@ #' #' @export #' @rdname use_files -#' @importFrom cli cat_bullet -#' @importFrom fs path_abs path file_exists #' #' @return The path to the file, invisibly. use_external_js_file <- function( @@ -26,13 +24,15 @@ use_external_js_file <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { name <- basename(url) } + check_name_length(name) + name <- file_path_sans_ext(name) new_file <- sprintf("%s.js", name) @@ -46,14 +46,14 @@ use_external_js_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -83,7 +83,6 @@ use_external_js_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_external_css_file <- function( url, name, @@ -92,13 +91,15 @@ use_external_css_file <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { name <- basename(url) } + check_name_length(name) + name <- file_path_sans_ext(name) new_file <- sprintf("%s.css", name) @@ -112,14 +113,14 @@ use_external_css_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -149,7 +150,6 @@ use_external_css_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_external_html_template <- function( url, name = "template.html", @@ -158,7 +158,7 @@ use_external_html_template <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) new_file <- sprintf( @@ -166,6 +166,8 @@ use_external_html_template <- function( file_path_sans_ext(name) ) + check_name_length(name) + dir_created <- create_if_needed( dir, type = "directory" @@ -176,14 +178,14 @@ use_external_html_template <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -207,7 +209,6 @@ use_external_html_template <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_external_file <- function( url, name, @@ -216,11 +217,14 @@ use_external_file <- function( open = FALSE, dir_create = TRUE ) { + check_name_length(name) + if (missing(name)) { name <- basename(url) } - old <- setwd(path_abs(pkg)) + + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -233,14 +237,14 @@ use_external_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, name ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -254,7 +258,6 @@ use_external_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_js_file <- function( path, name, @@ -263,7 +266,8 @@ use_internal_js_file <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + check_name_length(name) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { @@ -283,14 +287,14 @@ use_internal_js_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -304,7 +308,7 @@ use_internal_js_file <- function( cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) file_created_dance( where, @@ -319,7 +323,6 @@ use_internal_js_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_css_file <- function( path, name, @@ -328,7 +331,9 @@ use_internal_css_file <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + check_name_length(name) + + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) if (missing(name)) { @@ -348,14 +353,14 @@ use_internal_css_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } @@ -369,7 +374,7 @@ use_internal_css_file <- function( cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) file_created_dance( where, @@ -384,7 +389,6 @@ use_internal_css_file <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_html_template <- function( path, name = "template.html", @@ -393,9 +397,11 @@ use_internal_html_template <- function( open = FALSE, dir_create = TRUE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) + check_name_length(name) + new_file <- sprintf( "%s.html", file_path_sans_ext(name) @@ -411,21 +417,21 @@ use_internal_html_template <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, new_file ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) cat_copied(where) @@ -441,7 +447,6 @@ use_internal_html_template <- function( #' @export #' @rdname use_files -#' @importFrom fs path_abs file_exists use_internal_file <- function( path, name, @@ -454,7 +459,9 @@ use_internal_file <- function( name <- basename(path) } - old <- setwd(path_abs(pkg)) + check_name_length(name) + + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) dir_created <- create_if_needed( @@ -467,21 +474,21 @@ use_internal_file <- function( return(invisible(FALSE)) } - dir <- path_abs(dir) + dir <- fs_path_abs(dir) - where <- path( + where <- fs_path( dir, name ) - if (file_exists(where)) { + if (fs_file_exists(where)) { cat_exists(where) return(invisible(FALSE)) } cat_start_copy() - file.copy(path, where) + fs_file_copy(path, where) cat_copied(where) } diff --git a/R/use_readme.R b/R/use_readme.R new file mode 100644 index 00000000..6ed54393 --- /dev/null +++ b/R/use_readme.R @@ -0,0 +1,74 @@ +#' Generate a README.Rmd +#' @inheritParams usethis::use_readme_rmd +#' @inheritParams add_module +#' @inheritParams fill_desc +#' @param overwrite an optional \code{logical} flag; if \code{TRUE}, overwrite +#' existing \code{README.Rmd}, else throws an error if \code{README.Rmd} exists +#' +#' @return pure side-effect function that generates template \code{README.Rmd} +#' @export +use_readme_rmd <- function( + open = rlang::is_interactive(), + pkg_name = golem::get_golem_name(), + overwrite = FALSE, + pkg = golem::get_golem_wd() +) { + stopifnot(`Arg. 'overwrite' must be logical` = is.logical(overwrite)) + + # We move the working directory to perform this action, + # in case we're launching the action from somewhere else + old <- setwd(pkg) + on.exit(setwd(old)) + + # Guess the readme path + readme_path <- file.path( + pkg, + "README.Rmd" + ) + + # Removing the README if it already exists and overwrite is TRUE + check_overwrite( + overwrite, + readme_path + ) + + usethis_use_readme_rmd() + + readme_tmpl <- generate_readme_tmpl( + pkg_name = pkg_name + ) + + write( + x = readme_tmpl, + file = readme_path, + append = FALSE, + sep = "\n" + ) + return(invisible(TRUE)) +} + +check_overwrite <- function(overwrite, tmp_pth) { + # If the user wants to overwrite, we remove the file + # Otherwise, error if the file already exists + if (file.exists(tmp_pth)){ + if (isTRUE(overwrite)) { + unlink(tmp_pth, TRUE, TRUE) + } else { + stop("README.Rmd already exists. Set `overwrite = TRUE` to overwrite.") + } + } + +} + +generate_readme_tmpl <- function(pkg_name) { + + tmp_file <- readLines( + golem_sys("utils/empty_readme.Rmd") + ) + return( + sprintf( + tmp_file, + pkg_name + ) + ) +} diff --git a/R/use_recommended.R b/R/use_recommended.R index e8d62181..1a348a9c 100644 --- a/R/use_recommended.R +++ b/R/use_recommended.R @@ -10,8 +10,6 @@ #' @param recommended A vector of recommended packages. #' @param spellcheck Whether or not to use a spellcheck test. #' -#' @importFrom usethis use_testthat use_package -#' @importFrom fs path_abs #' @rdname use_recommended #' #' @export @@ -23,15 +21,14 @@ use_recommended_deps <- function( recommended = c("shiny", "DT", "attempt", "glue", "htmltools", "golem") ) { .Deprecated( - "golem::use_recommended_deps", - msg <- "use_recommended_deps() is soft deprecated and will be removed in future versions of {golem}." + msg = "use_recommended_deps() is soft deprecated and will be removed in future versions of {golem}." ) - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) for (i in sort(recommended)) { - try(use_package(i)) + try(usethis_use_package(i)) } cat_green_tick("Dependencies added") @@ -40,10 +37,8 @@ use_recommended_deps <- function( #' @rdname use_recommended #' @export -#' @importFrom usethis use_testthat use_package use_spell_check #' @importFrom utils capture.output #' @importFrom attempt without_warning stop_if -#' @importFrom fs path_abs path file_exists use_recommended_tests <- function( pkg = get_golem_wd(), spellcheck = TRUE, @@ -51,39 +46,37 @@ use_recommended_tests <- function( lang = "en-US", error = FALSE ) { - old <- setwd(path_abs(pkg)) + old <- setwd(fs_path_abs(pkg)) on.exit(setwd(old)) - if (!dir.exists( - path(path_abs(pkg), "tests") + if (!fs_dir_exists( + fs_path(fs_path_abs(pkg), "tests") )) { - without_warning(use_testthat)() + without_warning(usethis_use_testthat)() } if (!requireNamespace("processx")) { stop("Please install the {processx} package to add the recommended tests.") } stop_if( - path(old, "tests", "testthat", "test-golem-recommended.R"), - file_exists, + fs_path(old, "tests", "testthat", "test-golem-recommended.R"), + fs_file_exists, "test-golem-recommended.R already exists. \nPlease remove it first if you need to reinsert it." ) - file_copy( + fs_file_copy( golem_sys("utils", "test-golem-recommended.R"), - path(old, "tests", "testthat"), + fs_path(old, "tests", "testthat"), overwrite = TRUE ) if (spellcheck) { - use_spell_check( + usethis_use_spell_check( vignettes = vignettes, lang = lang, error = error ) } - - cat_green_tick("Tests added") } diff --git a/R/use_utils.R b/R/use_utils.R index 23bcd166..f54f786e 100644 --- a/R/use_utils.R +++ b/R/use_utils.R @@ -10,9 +10,7 @@ #' @export #' @rdname utils_files #' -#' @importFrom cli cat_bullet #' @importFrom utils capture.output -#' @importFrom usethis use_testthat #' #' @return Used for side-effects. use_utils_ui <- function( @@ -29,10 +27,10 @@ use_utils_ui <- function( cat_green_tick("Utils UI added") if (with_test) { - if (!isTRUE(dir.exists("tests"))) { - use_testthat() + if (!isTRUE(fs_dir_exists("tests"))) { + usethis_use_testthat() } - pth <- path( + pth <- fs_path( pkg, "tests", "testthat", @@ -81,10 +79,10 @@ use_utils_server <- function( cat_green_tick("Utils server added") if (with_test) { - if (!isTRUE(dir.exists("tests"))) { - use_testthat() + if (!isTRUE(fs_dir_exists("tests"))) { + usethis_use_testthat() } - pth <- path( + pth <- fs_path( pkg, "tests", "testthat", @@ -131,23 +129,27 @@ use_utils_test_server <- function(pkg = get_golem_wd()) { use_utils_test_(pkg, "server") } - -#' @importFrom fs file_copy path_abs path_file use_utils <- function( file_name, folder_name, pkg = get_golem_wd() ) { old <- setwd( - path_abs(pkg) + fs_path_abs(pkg) ) on.exit(setwd(old)) - where <- path(path_abs(pkg), folder_name, file_name) - if (file_exists(where)) { + + where <- fs_path( + fs_path_abs(pkg), + folder_name, + file_name + ) + + if (fs_file_exists(where)) { cat_exists(where) return(FALSE) } else { - file_copy( + fs_file_copy( path = golem_sys("utils", file_name), new_path = where ) diff --git a/R/utils.R b/R/utils.R index 92f2fdff..279d7a92 100644 --- a/R/utils.R +++ b/R/utils.R @@ -11,27 +11,20 @@ golem_sys <- function( ) } -# from usethis https://github.com/r-lib/usethis/ -darkgrey <- function(x) { - x <- crayon::make_style("darkgrey")(x) -} -#' @importFrom fs dir_exists file_exists -dir_not_exist <- Negate(dir_exists) -file_not_exist <- Negate(file_exists) -#' @importFrom fs dir_create file_create create_if_needed <- function( path, type = c("file", "directory"), content = NULL ) { type <- match.arg(type) + # Check if file or dir already exist if (type == "file") { - dont_exist <- file_not_exist(path) + dont_exist <- Negate(fs_file_exists)(path) } else if (type == "directory") { - dont_exist <- dir_not_exist(path) + dont_exist <- Negate(fs_dir_exists)(path) } # If it doesn't exist, ask if we are allowed # to create it @@ -50,10 +43,10 @@ create_if_needed <- function( } else { # Create the file if (type == "file") { - file_create(path) + fs_file_create(path) write(content, path, append = TRUE) } else if (type == "directory") { - dir_create(path, recurse = TRUE) + fs_dir_create(path, recurse = TRUE) } } } else { @@ -72,10 +65,9 @@ create_if_needed <- function( return(TRUE) } -#' @importFrom fs file_exists check_file_exist <- function(file) { res <- TRUE - if (file_exists(file)) { + if (fs_file_exists(file)) { if (interactive()) { res <- yesno("This file already exists, override?") } else { @@ -85,20 +77,6 @@ check_file_exist <- function(file) { return(res) } -# TODO Remove from codebase -#' @importFrom fs dir_exists -check_dir_exist <- function(dir) { - res <- TRUE - if (!dir_exists(dir)) { - if (interactive()) { - res <- yesno(sprintf("The %s does not exists, create?", dir)) - } else { - res <- FALSE - } - } - return(res) -} - # internal replace_word <- function( file, @@ -130,44 +108,48 @@ remove_comments <- function(file) { writeLines(text = lines_without_comment, con = file) } -#' @importFrom cli cat_bullet cat_green_tick <- function(...) { - cat_bullet( - ..., - bullet = "tick", - bullet_col = "green" - ) + do_if_unquiet({ + cli_cat_bullet( + ..., + bullet = "tick", + bullet_col = "green" + ) + }) } -#' @importFrom cli cat_bullet cat_red_bullet <- function(...) { - cat_bullet( - ..., - bullet = "bullet", - bullet_col = "red" - ) + do_if_unquiet({ + cli_cat_bullet( + ..., + bullet = "bullet", + bullet_col = "red" + ) + }) } -#' @importFrom cli cat_bullet cat_info <- function(...) { - cat_bullet( - ..., - bullet = "arrow_right", - bullet_col = "grey" - ) + do_if_unquiet({ + cli_cat_bullet( + ..., + bullet = "arrow_right", + bullet_col = "grey" + ) + }) } + cat_exists <- function(where) { cat_red_bullet( sprintf( "[Skipped] %s already exists.", - path_file(where) + basename(where) ) ) cat_info( sprintf( "If you want replace it, remove the %s file first.", - path_file(where) + basename(where) ) ) } @@ -179,8 +161,10 @@ cat_dir_necessary <- function() { } cat_start_download <- function() { - cat_line("") - cat_rule("Initiating file download") + do_if_unquiet({ + cli_cat_line("") + cli_cat_line("Initiating file download") + }) } cat_downloaded <- function( @@ -197,8 +181,10 @@ cat_downloaded <- function( } cat_start_copy <- function() { - cat_line("") - cat_rule("Copying file") + do_if_unquiet({ + cli_cat_line("") + cli_cat_line("Copying file") + }) } cat_copied <- function( @@ -240,11 +226,9 @@ open_or_go_to <- function( open_file ) { if ( - rstudioapi::isAvailable() && - open_file && - rstudioapi::hasFun("navigateToFile") + open_file ) { - rstudioapi::navigateToFile(where) + rstudioapi_navigateToFile(where) } else { cat_red_bullet( sprintf( @@ -257,7 +241,7 @@ open_or_go_to <- function( } desc_exist <- function(pkg) { - file_exists( + fs_file_exists( paste0(pkg, "/DESCRIPTION") ) } @@ -271,7 +255,7 @@ after_creation_message_js <- function( desc_exist(pkg) ) { if ( - fs::path_abs(dir) != fs::path_abs("inst/app/www") & + fs_path_abs(dir) != fs_path_abs("inst/app/www") & utils::packageVersion("golem") < "0.2.0" ) { cat_red_bullet( @@ -293,7 +277,7 @@ after_creation_message_css <- function( if ( desc_exist(pkg) ) { - if (fs::path_abs(dir) != fs::path_abs("inst/app/www") & + if (fs_path_abs(dir) != fs_path_abs("inst/app/www") & utils::packageVersion("golem") < "0.2.0" ) { cat_red_bullet( @@ -316,7 +300,7 @@ after_creation_message_sass <- function( if ( desc_exist(pkg) ) { - if (fs::path_abs(dir) != fs::path_abs("inst/app/www") & + if (fs_path_abs(dir) != fs_path_abs("inst/app/www") & utils::packageVersion("golem") < "0.2.0" ) { cat_red_bullet( @@ -333,13 +317,15 @@ after_creation_message_html_template <- function( dir, name ) { - cat_line("") - cat_rule("To use this html file as a template, add the following code in your UI:") - cat_line(darkgrey("htmlTemplate(")) - cat_line(darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) - cat_line(darkgrey(" body = tagList()")) - cat_line(darkgrey(" # add here other template arguments")) - cat_line(darkgrey(")")) + do_if_unquiet({ + cli_cat_line("") + cli_cat_line("To use this html file as a template, add the following code in your UI:") + cli_cat_line(crayon_darkgrey("htmlTemplate(")) + cli_cat_line(crayon_darkgrey(sprintf(' app_sys("app/www/%s.html"),', file_path_sans_ext(name)))) + cli_cat_line(crayon_darkgrey(" body = tagList()")) + cli_cat_line(crayon_darkgrey(" # add here other template arguments")) + cli_cat_line(crayon_darkgrey(")")) + }) } file_created_dance <- function( @@ -405,10 +391,51 @@ yesno <- function(...) { menu(c("Yes", "No")) == 1 } -#' @importFrom fs file_exists + +# Checking that a package is installed +check_is_installed <- function( + pak, + ... +) { + if ( + !requireNamespace(pak, ..., quietly = TRUE) + ) { + stop( + sprintf( + "The {%s} package is required to run this function.\nYou can install it with `install.packages('%s')`.", + pak, + pak + ), + call. = FALSE + ) + } +} + +required_version <- function( + pak, + version +) { + if ( + utils::packageVersion(pak) < version + ) { + stop( + sprintf( + "This function require the version '%s' of the {%s} package.\nYou can update with `install.packages('%s')`.", + version, + pak, + pak + ), + call. = FALSE + ) + } +} + + + + add_sass_code <- function(where, dir, name) { - if (file_exists(where)) { - if (file_exists("dev/run_dev.R")) { + if (fs_file_exists(where)) { + if (fs_file_exists("dev/run_dev.R")) { lines <- readLines("dev/run_dev.R") new_lines <- append( x = lines, @@ -452,4 +479,46 @@ is_existing_module <- function(module) { existing_module_files ) module %in% existing_module_names -} \ No newline at end of file +} + +# This function is used for checking +# that the name argument of the function +# creating files is not of length() > 1 +check_name_length <- function(name) { + stop_if( + name, + ~ length(.x) > 1, + sprintf( + "`name` should be of length 1. Got %d.", + length(name) + ) + ) +} + +do_if_unquiet <- function(expr) { + if ( + !getOption( + "golem.quiet", + getOption( + "usethis.quiet", + default = FALSE + ) + ) + ) { + force(expr) + } +} + +# This functions checks that the 'name' argument +# of add_module() does not start with 'mod_' as +# this is prepended by add_module() per default. +check_name_syntax <- function(name) { + if (isTRUE(grepl("^mod_", name))) { + cli_cli_alert_info( + "You set a 'name' that starts with 'mod_'." + ) + cli_cli_alert_info( + "This is not necessary as golem will prepend 'mod_' to your module name automatically." + ) + } +} diff --git a/R/with_opt.R b/R/with_opt.R index dfde7a5d..1ff0acfb 100644 --- a/R/with_opt.R +++ b/R/with_opt.R @@ -5,18 +5,27 @@ #' launch. #' #' @param app the app object. -#' @param golem_opts A list of Options to be added to the app +#' @param golem_opts A list of options to be added to the app +#' @param maintenance_page an html_document or a shiny tag list. Default is golem template. #' @param print Whether or not to print the app. Default is to `FALSE`, which -#' should be what you need 99.99% of the time In case you need to -#' actively print the app object, you can set it to `TRUE`. +#' should be what you need 99.99% of the time. In case you need to +#' actively print() the app object, you can set it to `TRUE`. #' #' @return a shiny.appObj object #' @export with_golem_options <- function( app, golem_opts, + maintenance_page = golem::maintenance_page, print = FALSE ) { + # Check if app is in maintenance + if (Sys.getenv("GOLEM_MAINTENANCE_ACTIVE", "FALSE") == "TRUE") { + app <- shiny::shinyApp( + ui = maintenance_page, + server = function(input, output, session) {} + ) + } # Setting the running option set_golem_global( @@ -41,9 +50,9 @@ with_golem_options <- function( print <- FALSE } - # Almost all cases will be ok with explicitely printing the + # Almost all cases will be ok with not explicitely printing the # application object, but for corner cases like direct shinyApp - # object manipulation, this feature can be turned off + # object manipulation, this feature can be turned on if (print) { print(app) } else { @@ -64,38 +73,50 @@ with_golem_options <- function( #' @return The value of the option. #' #' @examples -#' \dontrun{ #' #' # Define and use golem_options +#' if (interactive()) { +#' # 1. Pass parameters directly to `run_app` #' -#' # 1. Pass parameters to `run_app` -#' -#' # to set default value, edit run_app like this : -#' run_app <- function( -#' title = "this", -#' content = "that" -#' ) { -#' with_golem_options( -#' app = shinyApp( -#' ui = app_ui, -#' server = app_server -#' ), -#' golem_opts = list( -#' p1 = p1, -#' p3 = p3 -#' ) +#' run_app( +#' title = "My Golem App", +#' content = "something" #' ) -#' } #' -#' # 2. Get the values from the UI side +#' # 2. Get the values +#' # 2.1 from the UI side +#' +#' h1(get_golem_options("title")) +#' +#' # 2.2 from the server-side #' -#' h1(get_golem_options("title")) +#' output$param <- renderPrint({ +#' paste("param content = ", get_golem_options("content")) +#' }) #' -#' # 3. Get the value from the server-side +#' output$param_full <- renderPrint({ +#' get_golem_options() # list of all golem options as a list. +#' }) #' -#' output$param <- renderPrint({ -#' paste("param p2 = ", get_golem_options("p2")) -#' }) +#' # 3. If needed, to set default value, edit `run_app` like this : +#' +#' run_app <- function( +#' title = "this", +#' content = "that", +#' ... +#' ) { +#' with_golem_options( +#' app = shinyApp( +#' ui = app_ui, +#' server = app_server +#' ), +#' golem_opts = list( +#' title = title, +#' content = content, +#' ... +#' ) +#' ) +#' } #' } #' get_golem_options <- function(which = NULL) { @@ -105,3 +126,22 @@ get_golem_options <- function(which = NULL) { getShinyOption("golem_options")[[which]] } } + +#' maintenance_page +#' +#' A default html page for maintenance mode +#' +#' @importFrom shiny htmlTemplate +#' +#' @return an html_document +#' @details see the vignette \code{vignette("f_extending_golem", package = "golem")} for details. +#' @export +maintenance_page <- function() { + shiny::htmlTemplate( + filename = system.file( + "app", + "maintenance.html", + package = "golem" + ) + ) +} diff --git a/README.Rmd b/README.Rmd index c06f5466..6fcea973 100644 --- a/README.Rmd +++ b/README.Rmd @@ -7,7 +7,7 @@ output: github_document ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - eval = FALSE, + eval = TRUE, comment = "#>", fig.path = "man/figures/README-", out.width = "100%", @@ -27,12 +27,27 @@ knitr::opts_chunk$set( ## About -You're reading the doc about version : +You're reading the doc about version : `r pkgload::pkg_version()` -```{r eval = TRUE} -desc::desc_get_version() +This README has been compiled on the + +```{r} +Sys.time() ``` +Here are the test & coverage results : + +```{r} +devtools::check(quiet = TRUE) +``` + +```{r echo = FALSE} +unloadNamespace("golem") +``` + +```{r} +covr::package_coverage() +``` ## Tool series @@ -41,13 +56,14 @@ This package is part of a series of tools for Shiny, which includes: - `{golem}` - - `{shinipsum}` - - `{fakir}` - -- `{shinysnippets}` - +- `{gemstones}` - ## Resources ### The Book : - + - [paper version of the book “Engineering Production-Grade Shiny Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) ### Blog posts : @@ -73,9 +89,9 @@ This package is part of a series of tools for Shiny, which includes: - [Hands-on demonstration of {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) - useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://youtu.be/tCAan6smrjs) - `r emo::flag("France")` [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) -- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://posit.co/resources/videos/production-grade-shiny-apps-with-golem/) - `r emo::flag("France")` Rencontres R 2021 : [ Conception d'applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) - +- `r emo::flag("France")` [ Déploiement d'une application {shiny} dans docker avec {renv} et {golem}](https://www.youtube.com/watch?v=diCG4t76k78) @@ -88,7 +104,7 @@ This package is part of a series of tools for Shiny, which includes: These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - -- +- - - @@ -122,9 +138,9 @@ knitr::include_graphics("https://raw.githubusercontent.com/ThinkR-open/golem/mas ## Step by step guide -See full documentation in the {pkgdown} website: +See full documentation in the {pkgdown} website: -[CRAN] +[CRAN] [dev] diff --git a/README.md b/README.md index 8ceefc47..d00f62c7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ - [![Lifecycle: @@ -20,72 +19,141 @@ shiny applications. ## About -You’re reading the doc about version : +You’re reading the doc about version : 0.4.10 + +This README has been compiled on the + +``` r +Sys.time() +#> [1] "2023-05-16 17:15:55 CEST" +``` + +Here are the test & coverage results : ``` r -desc::desc_get_version() -#> [1] '0.3.2' +devtools::check(quiet = TRUE) +#> ℹ Loading golem +#> ── R CMD check results ─────────────────────────────────────── golem 0.4.10 ──── +#> Duration: 1m 8.3s +#> +#> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ +``` + +``` r +covr::package_coverage() +#> golem Coverage: 69.29% +#> R/addins.R: 0.00% +#> R/bootstrap_rstudio_api.R: 0.00% +#> R/enable_roxygenize.R: 0.00% +#> R/get_sysreqs.R: 0.00% +#> R/gobals.R: 0.00% +#> R/run_dev.R: 0.00% +#> R/sanity_check.R: 0.00% +#> R/use_files.R: 0.00% +#> R/with_opt.R: 22.58% +#> R/config.R: 28.21% +#> R/test_helpers.R: 30.26% +#> R/js.R: 43.75% +#> R/reload.R: 45.36% +#> R/use_recommended.R: 54.55% +#> R/utils.R: 55.31% +#> R/bootstrap_desc.R: 55.56% +#> R/install_dev_deps.R: 57.14% +#> R/bootstrap_attachment.R: 61.54% +#> R/bootstrap_dockerfiler.R: 63.33% +#> R/add_dockerfiles.R: 74.19% +#> R/boostrap_fs.R: 77.78% +#> R/bootstrap_usethis.R: 78.57% +#> R/modules_fn.R: 79.00% +#> R/use_utils.R: 83.33% +#> R/use_favicon.R: 85.56% +#> R/desc.R: 86.25% +#> R/add_resource_path.R: 88.89% +#> R/golem-yaml-set.R: 88.89% +#> R/create_golem.R: 89.47% +#> R/make_dev.R: 90.00% +#> R/add_r_files.R: 91.01% +#> R/add_files.R: 92.31% +#> R/add_rstudio_files.R: 93.10% +#> R/golem-yaml-get.R: 93.18% +#> R/add_dockerfiles_renv.R: 93.75% +#> R/use_readme.R: 97.14% +#> R/boostrap_cli.R: 100.00% +#> R/boostrap_crayon.R: 100.00% +#> R/bootstrap_pkgload.R: 100.00% +#> R/bootstrap_roxygen2.R: 100.00% +#> R/browser_button.R: 100.00% +#> R/bundle_resources.R: 100.00% +#> R/disable_autoload.R: 100.00% +#> R/golem-yaml-utils.R: 100.00% +#> R/is_golem.R: 100.00% +#> R/is_running.R: 100.00% +#> R/pkg_tools.R: 100.00% +#> R/set_golem_options.R: 100.00% +#> R/templates.R: 100.00% ``` ## Tool series This package is part of a series of tools for Shiny, which includes: - - `{golem}` - - - `{shinipsum}` - - - `{fakir}` - - - `{shinysnippets}` - +- `{golem}` - +- `{shinipsum}` - +- `{fakir}` - +- `{gemstones}` - ## Resources ### The Book : - - - - [paper version of the book “Engineering Production-Grade Shiny - Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) +- + +- [paper version of the book “Engineering Production-Grade Shiny + Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) ### Blog posts : *Building Big Shiny Apps* - - Part 1: - - - Part 2: - +- Part 1: + +- Part 2: + [*Make a Fitness App from scratch*](https://towardsdatascience.com/production-grade-r-shiny-with-golem-prototyping-51b03f37c2a9) ### Slide decks - - useR\! 2019 : [A Framework for Building Robust & Production Ready - Shiny - Apps](https://github.com/VincentGuyader/user2019/raw/master/golem_Vincent_Guyader_USER!2019.pdf) - - ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with - {golem}](https://speakerdeck.com/colinfay/production-grade-shiny-apps-with-golem) - - rstudio::conf(2020) : [Production-grade Shiny Apps with - golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) - - barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps - with - {golem}](https://www.barcelonar.org/presentations/BarcelonaR_Building_Production_Grade_Shiny_Apps_with_golem.pdf) +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny + Apps](https://github.com/VincentGuyader/user2019/raw/master/golem_Vincent_Guyader_USER!2019.pdf) +- ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with + {golem}](https://speakerdeck.com/colinfay/production-grade-shiny-apps-with-golem) +- rstudio::conf(2020) : [Production-grade Shiny Apps with + golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) +- barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps + with + {golem}](https://www.barcelonar.org/presentations/BarcelonaR_Building_Production_Grade_Shiny_Apps_with_golem.pdf) ### Video - - [{golem} and Effective Shiny Development - Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) - - [Hands-on demonstration of - {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) - - useR\! 2019 : [A Framework for Building Robust & Production Ready - Shiny Apps](https://youtu.be/tCAan6smrjs) - - 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) - - rstudio::conf(2020) : [Production-grade Shiny Apps with - golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) - - 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec - {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) +- [{golem} and Effective Shiny Development + Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) +- [Hands-on demonstration of + {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny + Apps](https://youtu.be/tCAan6smrjs) +- 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) +- rstudio::conf(2020) : [Production-grade Shiny Apps with + golem](https://posit.co/resources/videos/production-grade-shiny-apps-with-golem/) +- 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec + {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) +- 🇫🇷 [Déploiement d’une application {shiny} dans docker avec {renv} et + {golem}](https://www.youtube.com/watch?v=diCG4t76k78) ### Cheatsheet - - [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) +- [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) ### Examples apps @@ -93,30 +161,26 @@ These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - - - - - - - - +- +- +- +- You can also find apps at: - - - - +- +- ## Installation - - You can install the stable version from CRAN with: - - +- You can install the stable version from CRAN with: ``` r install.packages("golem") ``` - - You can install the development version from - [GitHub](https://github.com/Thinkr-open/golem) with: - - +- You can install the development version from + [GitHub](https://github.com/Thinkr-open/golem) with: ``` r # install.packages("remotes") @@ -125,8 +189,7 @@ remotes::install_github("Thinkr-open/golem") ## Launch the project -Create a new package with the project -template: +Create a new package with the project template: diff --git a/_pkgdown.yml b/_pkgdown.yml index a2345294..deb079c8 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -25,7 +25,7 @@ reference: - title: Create a Dockerfile desc: | - Build a container containing your Shiny App. `add_dockerfile()` creates a "classical" Dockerfile, + Build a container containing your Shiny App. `add_dockerfile()` creates a "classical" Dockerfile, while `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` creates platform specific Dockerfile. contents: - add_dockerfile @@ -34,7 +34,7 @@ reference: - title: Use files desc: | - `use_external_js_file` and `use_external_css_file`download files from external sources and install + `use_external_js_file` and `use_external_css_file`download files from external sources and install them inside the appropriate directory. `use_utils_ui` copies the golem_utils_ui.R to the R folder and `use_utils_server` copies the golem_utils_server.R to the R folder. contents: @@ -88,7 +88,7 @@ reference: - title: Addins desc: | - `insert_ns()` takes a selected character vector and wrap it in `ns()`. The series of `go_to_*()` + `insert_ns()` takes a selected character vector and wrap it in `ns()`. The series of `go_to_*()` addins help you go to common files used in developing a {golem} application. contents: - insert_ns @@ -102,7 +102,7 @@ reference: - title: JavaScript interaction functions desc: | - `activate_js` is used in your UI to insert directly the JavaScript functions contained in golem. + `activate_js` is used in your UI to insert directly the JavaScript functions contained in golem. These functions can be called from the server with `invoke_js`. `invoke_js` can also be used to launch any JS function created inside a Shiny JavaScript handler. contents: @@ -111,7 +111,7 @@ reference: - title: Options desc: | - Set and get a series of options to be used with `{golem}`. These options are found inside the + Set and get a series of options to be used with `{golem}`. These options are found inside the `golem-config.yml` file, found in most cases inside the inst folder. contents: - set_golem_options @@ -126,9 +126,11 @@ reference: desc: | `use_recommended_deps` adds shiny, DT, attempt, glue, golem, htmltools to dependencies `use_recommended_tests` adds a test folder and copy the golem tests + `install_dev_deps` install all packages needed for golem developpement contents: - use_recommended_deps - use_recommended_tests + - install_dev_deps - title: Misc contents: @@ -144,6 +146,8 @@ reference: - fill_desc - make_dev - with_golem_options + - maintenance_page + - is_golem - title: internal contents: @@ -157,3 +161,5 @@ reference: - sanity_check - js_handler_template - use_module_test + - get_current_config + - pkg_name diff --git a/codecov.yml b/codecov.yml index 8f36b6cc..04c55859 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,7 +6,9 @@ coverage: default: target: auto threshold: 1% + informational: true patch: default: target: auto threshold: 1% + informational: true diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 00000000..30c1d7ba --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,24 @@ +# R CMD check results + +0 errors ✔ | 0 warnings ✔ | 0 notes ✔ + +# Revdeps +## Failed to check (7) + +|package |version |error |warning |note | +|:---------------|:-------|:-----|:-------|:----| +|discoveR |3.1.2 |1 | | | +|loadeR |1.1.3 |1 | | | +|multiSight |? | | | | +|OlympicRshiny |1.0.0 |1 | | | +|shinyTempSignal |0.0.3 |1 | | | +|spatialLIBD |1.10.1 |1 | |2 | +|tripr |1.4.0 |1 | | | + +## New problems (1) + +|package |version |error |warning |note | +|:--------------------|:-------|:-----|:-------|:----| +|MainExistingDatasets|1.0.1 | |__+1__ |1 | + +> This has been addressed by a PR on the git repo: https://github.com/baptisteCD/MainExistingDatasets/pull/1 \ No newline at end of file diff --git a/inst/app/maintenance.html b/inst/app/maintenance.html new file mode 100644 index 00000000..ed996309 --- /dev/null +++ b/inst/app/maintenance.html @@ -0,0 +1,268 @@ + + + + + + + Under Maintenance + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 8257554c..c3875135 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -1,5 +1,42 @@ +# To test locally, run this in a docker container: +# docker run -d --rm -v $(pwd):/home/golem rocker/shiny:latest tail -f /dev/null +# Attach VSCode to the container +# Then run this script in the terminal + +# This script is launched internally by ThinkR every day by our GitLab CI +temp_app <- commandArgs(TRUE)[1] +if (is.na(temp_app)) { + temp_app <- "/golemmetrics" +} +cli::cat_bullet(temp_app) +if (dir.exists(temp_app)) { + unlink(temp_app, TRUE, TRUE) +} + +options("repos" = "https://packagemanager.posit.co/cran/__linux__/jammy/latest") + +install.packages(c("pak")) + +if (!rlang::is_installed("cli")) { + pak::pak("cli") +} +if (!rlang::is_installed("testthat")) { + pak::pak("testthat") +} + +if (!rlang::is_installed("desc")) { + pak::pak("desc") +} + +if (!rlang::is_installed("devtools")) { + pak::pak("devtools") +} + +library(desc) +library(testthat) cat("\n") + # rstudioapi::jobRunScript(here::here("inst/mantests/build.R"), workingDir = here::here()) cat_ok <- function() cli::cat_bullet("Passed", bullet = "tick", bullet_col = "green") @@ -16,420 +53,360 @@ fakename <- sprintf( gsub("[ :-]", "", Sys.time()) ) -# Just so that I can use this script locally too, -# I set a temporary lib -cli::cat_rule("Set up for lib") +cli::cat_rule("Installing the dev version of golem") +pak::pak("thinkr-open/golem@dev") -temp_app <- file.path(tempdir(), "golemmetrics") - -if (Sys.getenv("CI", "local") == "local") { - # If I'm on the CI, we don't change the lib - temp_lib <- .libPaths() -} else { - temp_lib <- file.path(tempdir(), "temp_lib") - .libPaths(c(temp_lib, .libPaths())) -} +cli::cat_rule("Install crystalmountains") +pak::pak( + "thinkr-open/crystalmountains" +) -cli::cat_bullet(temp_app) -# This will be our golem app +cli::cat_rule("Creating a golem based app") +# Going to the temp dir and create a new golem +cli::cat_rule("Creating a golem based app") +library(golem) -if (dir.exists(temp_app)) { - unlink(temp_app, TRUE, TRUE) -} +zz <- file("all.Rout", open = "wt") +sink(zz) +sink(zz, type = "message") -dir.create(temp_lib, recursive = TRUE) +# This is our temp app -install.packages( - c("remotes", "desc", "testthat", "cli", "fs", "cranlogs"), - lib = temp_lib, - repo = "https://cran.rstudio.com/" +create_golem( + temp_app, + open = FALSE, + project_hook = crystalmountains::golem_hook ) +sink(type = "message") +sink() -cli::cat_rule("Install golem") - -library(remotes, lib.loc = temp_lib) -library(desc, lib.loc = temp_lib) -library(testthat, lib.loc = temp_lib) -library(cli, lib.loc = temp_lib) -library(fs, lib.loc = temp_lib) - -# We'll need to install golem from the current branch because -# otherwise the dependency tree breaks -# install_github( -# "ThinkR-open/golem", -# ref = Sys.getenv("GITHUB_BASE_REF", "dev"), -# force = TRUE, -# lib.loc = temp_lib -# ) +expect_true( + dir.exists(temp_app) +) -# Installing the current version of golem -install_local( - lib.loc = temp_lib +expect_true( + any(grepl( + "golem::install_dev_deps()", + readLines("all.Rout") + )) ) +unlink("all.Rout", TRUE, TRUE) +golem::install_dev_deps(force = TRUE) -withr::with_tempdir({ - cli::cat_rule("Install crystalmountains") +for (pak in golem:::dev_deps) { + expect_true( + rlang::is_installed(pak) + ) +} - # tmp_cm <- tempfile(fileext = ".zip") +old <- setwd(temp_app) - # download.file( - # "https://github.com/ThinkR-open/crystalmountains/archive/refs/heads/main.zip", - # "main.zip" - # ) +usethis::use_build_ignore(".here") - # unzip(tmp_cm) +cat( + readLines("DESCRIPTION"), + sep = "\n" +) - remotes::install_github( - "thinkr-open/crystalmountains", - lib.loc = temp_lib, - update = "never" - ) - # unlink("crystalmountains-main", TRUE, TRUE) +usethis::use_build_ignore(".here") - # Going to the temp dir and create a new golem - cli::cat_rule("Creating a golem based app") - library(golem) +usethis::use_dev_package("golem") - create_golem( - temp_app, - open = FALSE, - project_hook = crystalmountains::golem_hook - ) +cat( + readLines("DESCRIPTION"), + sep = "\n" +) - expect_true( - dir.exists(temp_app) - ) +cat_ok() - old <- setwd(temp_app) - here::set_here(temp_app) +cli::cat_rule("Checking the hook has set the MIT licence") +expect_true( + file.exists("LICENSE") +) +expect_true( + desc_get("License") == "MIT + file LICENSE" +) +cat_ok() - usethis::use_build_ignore(".here") +cli::cat_rule("Checking the DESCRIPTION is correct") +expect_true( + desc_get("Package") == "golemmetrics" +) +expect_true( + desc_get("Title") == "An Amazing Shiny App" +) +expect_true( + all(desc_get_deps()$package %in% c("config", "golem", "shiny")) +) +cat_ok() + +cli::cat_rule("Checking all files are here") + +expected_files <- c( + "DESCRIPTION", + "NAMESPACE", + "R", + "R/app_config.R", + "R/app_server.R", + "R/app_ui.R", + "R/run_app.R", + "dev", + "dev/01_start.R", + "dev/02_dev.R", + "dev/03_deploy.R", + "dev/run_dev.R", + "inst", + "inst/app", + "inst/app/www", + "inst/app/www/favicon.ico", + "inst/golem-config.yml", + "man", + "man/run_app.Rd" +) +actual_files <- fs::dir_ls(recurse = TRUE) - if (Sys.getenv("GITHUB_BASE_REF") == "") { - usethis::use_dev_package( - "golem", - remote = "github::ThinkR-open/golem@dev" - ) - } else { - usethis::use_dev_package( - "golem", - remote = sprintf( - "github::ThinkR-open/golem@dev", - Sys.getenv("GITHUB_BASE_REF") - ) - ) - } +for (i in expected_files) { + expect_true(i %in% actual_files) +} +cat_ok() + +# Going through 01_start.R ---- +# +cli::cat_rule("Going through 01_start.R") +cli::cat_line() + +golem::fill_desc( + pkg = temp_app, + pkg_name = "golemmetrics", # The Name of the package containing the App + pkg_title = "A App with Metrics about 'Golem'", # The Title of the package containing the App + pkg_description = "Read metrics about {golem}.", # The Description of the package containing the App + authors = person( + given = "Colin", # Your First Name + family = "Fay", # Your Last Name + role = c("cre", "aut"), + email = "colin@thinkr.fr"), + repo_url = NULL, # The URL of the GitHub Repo (optional) + pkg_version = "0.0.0.9000" # The Version of the package containing the App +) +cli::cat_rule("checking package name") +expect_equal( + desc_get_field("Package"), + "golemmetrics" +) +cat_ok() +cli::cat_rule("checking pkg_title name") +expect_equal( + desc_get_field("Title"), + "A App with Metrics about 'Golem'" +) +cat_ok() +cli::cat_rule("checking package name") +expect_equal( + desc_get_field("Description"), + "Read metrics about {golem}." +) +cat_ok() +cli::cat_rule("checking package name") +expect_equal( + as.character(desc_get_author()), + "Colin Fay [cre, aut]" +) +cat_ok() +cli::cat_rule("checking package version") +expect_equal( + as.character(desc_get_version()), + "0.0.0.9000" +) +cat_ok() +cli::cat_rule("set_golem_options") - cat( - readLines("DESCRIPTION"), - sep = "\n" - ) +golem::set_golem_options() +expect_equal( + golem::get_golem_wd(), + golem::pkg_path() +) +expect_equal( + golem::get_golem_name(), + "golemmetrics" +) +expect_equal( + golem::get_golem_version(), + "0.0.0.9000" +) +expect_false( + golem::app_prod() +) +cat_ok() - here::set_here(temp_app) +cli::cat_rule("Create Common Files") - usethis::use_build_ignore(".here") +# usethis::use_mit_license( "Golem User" ) - usethis::use_dev_package("golem") +expect_equal( + desc_get_field("License"), + "MIT + file LICENSE" +) +expect_true( + file.exists("LICENSE") +) +usethis::use_readme_rmd(open = FALSE) +expect_true( + file.exists("README.Rmd") +) - cat( - readLines("DESCRIPTION"), - sep = "\n" - ) +usethis::use_code_of_conduct("Golem user") +expect_true( + file.exists("CODE_OF_CONDUCT.md") +) - cat_ok() +usethis::use_news_md(open = FALSE) +expect_true( + file.exists("NEWS.md") +) +cat_ok() - cli::cat_rule("Checking the hook has set the MIT licence") - expect_true( - file.exists("LICENSE") - ) - expect_true( - desc::desc_get("License") == "MIT + file LICENSE" - ) - cat_ok() +cli::cat_rule("use_recommended") - cli::cat_rule("Checking the DESCRIPTION is correct") - expect_true( - desc::desc_get("Package") == "golemmetrics" - ) - expect_true( - desc::desc_get("Title") == "An Amazing Shiny App" - ) - expect_true( - all(desc::desc_get_deps()$package %in% c("config", "golem", "shiny")) - ) - cat_ok() - - cli::cat_rule("Checking all files are here") - - expected_files <- c( - "DESCRIPTION", - "NAMESPACE", - "R", - "R/app_config.R", - "R/app_server.R", - "R/app_ui.R", - "R/run_app.R", - "dev", - "dev/01_start.R", - "dev/02_dev.R", - "dev/03_deploy.R", - "dev/run_dev.R", - "inst", - "inst/app", - "inst/app/www", - "inst/app/www/favicon.ico", - "inst/golem-config.yml", - "man", - "man/run_app.Rd" - ) - actual_files <- fs::dir_ls(recurse = TRUE) - - for (i in expected_files) { - expect_true(i %in% actual_files) - } - cat_ok() - - # Going through 01_start.R ---- - # - cli::cat_rule("Going through 01_start.R") - cli::cat_line() - - golem::fill_desc( - pkg = temp_app, - pkg_name = "golemmetrics", # The Name of the package containing the App - pkg_title = "A App with Metrics about 'Golem'", # The Title of the package containing the App - pkg_description = "Read metrics about {golem}.", # The Description of the package containing the App - author_first_name = "Colin", # Your First Name - author_last_name = "Fay", # Your Last Name - author_email = "colin@thinkr.fr", # Your Email - repo_url = NULL # The URL of the GitHub Repo (optional) - ) - - cli::cat_rule("checking package name") - expect_equal( - desc::desc_get_field("Package"), - "golemmetrics" - ) - cat_ok() - cli::cat_rule("checking pkg_title name") - expect_equal( - desc::desc_get_field("Title"), - "A App with Metrics about 'Golem'" - ) - cat_ok() - cli::cat_rule("checking package name") - expect_equal( - desc::desc_get_field("Description"), - "Read metrics about {golem}." - ) - cat_ok() - cli::cat_rule("checking package name") - expect_equal( - as.character(desc::desc_get_author()), - "Colin Fay [cre, aut]" - ) - cat_ok() - cli::cat_rule("checking package version") - expect_equal( - as.character(desc::desc_get_version()), - "0.0.0.9000" - ) - cat_ok() +golem::use_recommended_tests(spellcheck = FALSE) +expect_true( + dir.exists("tests") +) - cli::cat_rule("set_golem_options") +# golem::use_recommended_deps() - golem::set_golem_options() - expect_equal( - golem::get_golem_wd(), - here::here() - ) - expect_equal( - golem::get_golem_name(), - "golemmetrics" - ) - expect_equal( - golem::get_golem_version(), - "0.0.0.9000" - ) - expect_false( - golem::app_prod() - ) +golem::use_utils_ui(with_test = TRUE) +expect_true( + file.exists("R/golem_utils_ui.R") +) +expect_true( + file.exists("tests/testthat/test-golem_utils_ui.R") +) - cat_ok() +golem::use_utils_server() +expect_true( + file.exists("R/golem_utils_server.R") +) - cli::cat_rule("Create Common Files") +cat_ok() - # usethis::use_mit_license( "Golem User" ) +# Going through 02_dev ---- +cli::cat_rule("Going through 02_dev.R") - expect_equal( - desc::desc_get_field("License"), - "MIT + file LICENSE" - ) - expect_true( - file.exists("LICENSE") - ) - usethis::use_readme_rmd(open = FALSE) - expect_true( - file.exists("README.Rmd") - ) +cli::cat_rule("Testing usepackage") +if (!requireNamespace("cranlogs")) { + pak::pak("cranlogs") +} +usethis::use_package("cranlogs") +expect_true( + "cranlogs" %in% desc_get_deps()$package +) +cat_ok() + +cli::cat_rule("Testing modules") +golem::add_module( + name = "main", + open = FALSE, + module_template = crystalmountains::module_template, + fct = "golem_logs", + js = "golem_stars", + utils = "pretty_num" +) - usethis::use_code_of_conduct("Golem user") - expect_true( - file.exists("CODE_OF_CONDUCT.md") - ) +expect_true( + file.exists("R/mod_main.R") +) - usethis::use_news_md(open = FALSE) - expect_true( - file.exists("NEWS.md") - ) +expect_true( + file.exists("R/mod_main_fct_golem_logs.R") +) +write( + readLines( + system.file( + "golemlogs", + package = "crystalmountains" + ) + ), + "R/mod_main_fct_golem_logs.R", + append = TRUE +) +expect_true( + file.exists("R/mod_main_utils_pretty_num.R") +) +write( + readLines( + system.file( + "prettynum", + package = "crystalmountains" + ) + ), + "R/mod_main_utils_pretty_num.R", + append = TRUE +) +expect_true( + file.exists("inst/app/www/golem_stars.js") +) +unlink("inst/app/www/golem_stars.js", TRUE, TRUE) - cat_ok() +golem::document_and_reload() - cli::cat_rule("use_recommended") +cat_ok() - golem::use_recommended_tests(spellcheck = FALSE) - expect_true( - dir.exists("tests") - ) +golem::add_fct("helpers", open = FALSE) +expect_true( + file.exists("R/fct_helpers.R") +) +unlink("R/fct_helpers.R", TRUE, TRUE) +golem::add_utils("helpers", open = FALSE) +expect_true( + file.exists("R/utils_helpers.R") +) +unlink("R/utils_helpers.R", TRUE, TRUE) - golem::use_recommended_deps() +golem::add_js_file("script", template = crystalmountains::js_file, open = FALSE) +golem::add_js_handler("handlers", template = crystalmountains::js_handler, open = FALSE) +golem::add_css_file("custom", template = crystalmountains::css_file, open = FALSE) - golem::use_utils_ui(with_test = TRUE) - expect_true( - file.exists("R/golem_utils_ui.R") - ) - expect_true( - file.exists("tests/testthat/test-golem_utils_ui.R") - ) +cli::cat_rule("Testing and installing package") +golem::document_and_reload() - golem::use_utils_server() - expect_true( - file.exists("R/golem_utils_server.R") - ) +usethis::use_dev_package( + "golem", + remote = "https://github.com/ThinkR-open/golem" +) - cat_ok() +devtools::test() +cat_ok() - # Going through 02_dev ---- - cli::cat_rule("Going through 02_dev.R") +cli::cat_rule("Testing 03_dev") +devtools::check() +remotes::install_local(force = TRUE, upgrade = FALSE) +targz <- devtools::build() +remotes::install_local(targz, force = TRUE, upgrade = FALSE) - cli::cat_rule("Testing usepackage") - if (!requireNamespace("cranlogs")) { - install.packages("cranlogs") - } - usethis::use_package("cranlogs") - expect_true( - "cranlogs" %in% desc::desc_get_deps()$package - ) - cat_ok() - - cli::cat_rule("Testing modules") - golem::add_module( - name = "main", - open = FALSE, - module_template = crystalmountains::module_template, - fct = "golem_logs", - js = "golem_stars", - utils = "pretty_num" - ) +cat_ok() - expect_true( - file.exists("R/mod_main.R") - ) +golem::add_rstudioconnect_file() - expect_true( - file.exists("R/mod_main_fct_golem_logs.R") - ) - write( - readLines( - system.file( - "golemlogs", - package = "crystalmountains" - ) - ), - "R/mod_main_fct_golem_logs.R", - append = TRUE - ) - expect_true( - file.exists("R/mod_main_utils_pretty_num.R") - ) - write( - readLines( - system.file( - "prettynum", - package = "crystalmountains" - ) - ), - "R/mod_main_utils_pretty_num.R", - append = TRUE - ) - expect_true( - file.exists("inst/app/www/golem_stars.js") - ) - unlink("inst/app/www/golem_stars.js", TRUE, TRUE) +golem::add_dockerfile_with_renv( + repos = "https://packagemanager.rstudio.com/all/__linux__/focal/latest", + from = "rocker/shiny-verse:4.0.4", + extra_sysreqs = c("libxml2-dev"), + open = FALSE, + output_dir = "/golemmetrics/deploy", + document = FALSE +) - golem::document_and_reload() +# Restore old wd - cat_ok() +# unlink(temp_app, TRUE, TRUE) +# unlink(temp_golem, TRUE, TRUE) +# unlink(temp_lib, TRUE, TRUE) - golem::add_fct("helpers", open = FALSE) - expect_true( - file.exists("R/fct_helpers.R") - ) - unlink("R/fct_helpers.R", TRUE, TRUE) - golem::add_utils("helpers", open = FALSE) - expect_true( - file.exists("R/utils_helpers.R") - ) - unlink("R/utils_helpers.R", TRUE, TRUE) - - golem::add_js_file("script", template = crystalmountains::js_file) - golem::add_js_handler("handlers", template = crystalmountains::js_handler) - golem::add_css_file("custom", template = crystalmountains::css_file) - - cli::cat_rule("Testing and installing package") - golem::document_and_reload() - devtools::test() - cat_ok() - - cli::cat_rule("Testing 03_dev") - devtools::check() - remotes::install_local(force = TRUE) - targz <- devtools::build() - remotes::install_local(targz) - - cli::cat_rule("Testing 03_dev") - - cat_ok() - - if (Sys.info()["sysname"] == "Linux") { - golem::add_rstudioconnect_file() - golem::add_dockerfile( - repos = "https://packagemanager.rstudio.com/all/__linux__/focal/latest", - from = "rocker/shiny-verse:4.0.4", - extra_sysreqs = c("libxml2-dev"), - open = FALSE - ) - usethis::use_git() - dir.create(".git/hooks", recursive = TRUE) - file.create(".git/hooks/pre-commit") - install.packages("rsconnect") - rsconnect::writeManifest() - install.packages("knitr") - knitr::knit("README.Rmd") - } - - # Restore old wd - - # unlink(temp_app, TRUE, TRUE) - # unlink(temp_golem, TRUE, TRUE) - # unlink(temp_lib, TRUE, TRUE) - - cli::cat_rule("Completed") -}) \ No newline at end of file +cli::cat_rule("Completed") diff --git a/inst/shinyexample/REMOVEME.Rbuildignore b/inst/shinyexample/REMOVEME.Rbuildignore index f3162bb5..9b9a10f3 100644 --- a/inst/shinyexample/REMOVEME.Rbuildignore +++ b/inst/shinyexample/REMOVEME.Rbuildignore @@ -3,4 +3,5 @@ ^data-raw$ dev_history.R ^dev$ -$run_dev.* \ No newline at end of file +$run_dev.* +^.here$ diff --git a/inst/shinyexample/dev/01_start.R b/inst/shinyexample/dev/01_start.R index 1348b281..c4a37bfc 100644 --- a/inst/shinyexample/dev/01_start.R +++ b/inst/shinyexample/dev/01_start.R @@ -19,22 +19,29 @@ ## to change the name in the app_sys() function in app_config.R /!\ ## golem::fill_desc( - pkg_name = "shinyexample", # The Name of the package containing the App - pkg_title = "PKG_TITLE", # The Title of the package containing the App - pkg_description = "PKG_DESC.", # The Description of the package containing the App - author_first_name = "AUTHOR_FIRST", # Your First Name - author_last_name = "AUTHOR_LAST", # Your Last Name - author_email = "AUTHOR@MAIL.COM", # Your Email - repo_url = NULL # The URL of the GitHub Repo (optional) + pkg_name = "shinyexample", # The name of the golem package containing the app (typically lowercase, no underscore or periods) + pkg_title = "PKG_TITLE", # What the Package Does (One Line, Title Case, No Period) + pkg_description = "PKG_DESC.", # What the package does (one paragraph). + authors = person( + given = "AUTHOR_FIRST", # Your First Name + family = "AUTHOR_LAST", # Your Last Name + email = "AUTHOR@MAIL.COM" # Your email + ), + repo_url = NULL, # The URL of the GitHub repo (optional), + pkg_version = "0.0.0.9000" # The version of the package containing the app ) ## Set {golem} options ---- golem::set_golem_options() +## Install the required dev dependencies ---- +golem::install_dev_deps() + ## Create Common Files ---- ## See ?usethis for more information usethis::use_mit_license("Golem User") # You can set another license here -usethis::use_readme_rmd(open = FALSE) +golem::use_readme_rmd(open = FALSE) +devtools::build_readme() # Note that `contact` is required since usethis version 2.1.5 # If your {usethis} version is older, you can remove that param usethis::use_code_of_conduct(contact = "Golem User") @@ -43,6 +50,11 @@ usethis::use_news_md(open = FALSE) ## Use git ---- usethis::use_git() +## Sets the remote associated with 'name' to 'url' +usethis::use_git_remote( + name = "origin", + url = "https://github.com//.git" +) ## Init Testing Infrastructure ---- ## Create a template for tests @@ -60,4 +72,4 @@ golem::use_utils_server(with_test = TRUE) # You're now set! ---- # go to dev/02_dev.R -rstudioapi::navigateToFile("dev/02_dev.R") \ No newline at end of file +rstudioapi::navigateToFile("dev/02_dev.R") diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index 82478726..78f968ca 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -15,6 +15,7 @@ ## Dependencies ---- ## Amend DESCRIPTION with dependencies read from package code parsing +## install.packages('attachment') # if needed. attachment::att_amend_desc() ## Add modules ---- diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index 464d59fd..1f886875 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -33,10 +33,27 @@ golem::add_shinyserver_file() ## Docker ---- ## If you want to deploy via a generic Dockerfile -golem::add_dockerfile() +golem::add_dockerfile_with_renv() ## If you want to deploy to ShinyProxy -golem::add_dockerfile_shinyproxy() - -## If you want to deploy to Heroku -golem::add_dockerfile_heroku() +golem::add_dockerfile_with_renv_shinyproxy() + + +# Deploy to Posit Connect or ShinyApps.io +# In command line. +rsconnect::deployApp( + appName = desc::desc_get_field("Package"), + appTitle = desc::desc_get_field("Package"), + appFiles = c( + # Add any additional files unique to your app here. + "R/", + "inst/", + "data/", + "NAMESPACE", + "DESCRIPTION", + "app.R" + ), + appId = rsconnect::deployments(".")$appID, + lint = FALSE, + forceUpdate = TRUE +) diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml index 6ce2c787..3e66f3fb 100644 --- a/inst/shinyexample/inst/golem-config.yml +++ b/inst/shinyexample/inst/golem-config.yml @@ -1,11 +1,11 @@ -default: +default: golem_name: shinyexample golem_version: 0.0.0.9000 app_prod: no -production: +production: app_prod: yes - -dev: - golem_wd: !exp here::here() + +dev: + golem_wd: !expr golem::pkg_path() diff --git a/inst/to-do-after-merge-dev.R b/inst/to-do-after-merge-dev.R new file mode 100644 index 00000000..1d63140b --- /dev/null +++ b/inst/to-do-after-merge-dev.R @@ -0,0 +1,6 @@ +desc::desc_bump_version("patch") +rstudioapi::navigateToFile("NEWS.md") +devtools::build_readme() +gert::git_add(c("DESCRIPTION", "NEWS.md", "README.md", "NEWS.md")) +gert::git_commit("chore: version bump & news update") +gert::git_push() diff --git a/inst/utils/empty_readme.Rmd b/inst/utils/empty_readme.Rmd new file mode 100644 index 00000000..b2f575a4 --- /dev/null +++ b/inst/utils/empty_readme.Rmd @@ -0,0 +1,59 @@ +--- +output: github_document +--- + + + +```{r, include = FALSE} + knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.path = "man/figures/README-", + out.width = "100%%" +) +``` + +# `{%s}` + + + + +## Installation + +You can install the development version of `{%s}` like so: + +```{r} +# FILL THIS IN! HOW CAN PEOPLE INSTALL YOUR DEV PACKAGE? +``` + +## Run + +You can launch the application by running: + +```{r, eval = FALSE} +%s::run_app() +``` + +## About + +You are reading the doc about version : `r golem::pkg_version()` + +This README has been compiled on the + +```{r} +Sys.time() +``` + +Here are the tests results and package coverage: + +```{r, error = TRUE} +devtools::check(quiet = TRUE) +``` + +```{r echo = FALSE} +unloadNamespace("%s") +``` + +```{r, error = TRUE} +covr::package_coverage() +``` diff --git a/inst/utils/golem_utils_ui.R b/inst/utils/golem_utils_ui.R index 5a9ee8d4..609a0004 100644 --- a/inst/utils/golem_utils_ui.R +++ b/inst/utils/golem_utils_ui.R @@ -333,7 +333,7 @@ make_action_button <- function(tag, inputId = NULL) { # some obvious checks if (!inherits(tag, "shiny.tag")) stop("Must provide a shiny tag.") if (!is.null(tag$attribs$class)) { - if (grep("action-button", tag$attribs$class)) { + if (isTRUE(grepl("action-button", tag$attribs$class))) { stop("tag is already an action button") } } diff --git a/inst/utils/test-golem_utils_server.R b/inst/utils/test-golem_utils_server.R index 770d3877..c003227b 100644 --- a/inst/utils/test-golem_utils_server.R +++ b/inst/utils/test-golem_utils_server.R @@ -51,4 +51,15 @@ test_that("rv and rvtl work", { expect_true( inherits(rvtl, "function") ) + + rv_test_1 <- rv(a = "a", b = 2) + rv_test_2 <- reactiveValues(a = "a", b = 2) + shiny::reactiveConsole(TRUE) + expect_identical(rv_test_1$a, rv_test_2$a) + expect_identical(rv_test_1$b, rv_test_2$b) + expect_identical( + rvtl(rv_test_2), + shiny::reactiveValuesToList(rv_test_1) + ) + shiny::reactiveConsole(FALSE) }) diff --git a/inst/utils/test-golem_utils_ui.R b/inst/utils/test-golem_utils_ui.R index a33935c8..b9e96a4e 100644 --- a/inst/utils/test-golem_utils_ui.R +++ b/inst/utils/test-golem_utils_ui.R @@ -103,6 +103,19 @@ test_that("Test undisplay works", { as.character(b_undisplay), '' ) + + c <- shiny::tags$p(src = "plop", style = "some_style", "pouet") + expect_s3_class(c, "shiny.tag") + expect_equal( + as.character(c), + '

pouet

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

pouet

' + ) }) test_that("Test display works", { @@ -165,8 +178,9 @@ test_that("Test columns wrappers works", { }) test_that("Test make_action_button works", { + tmp_tag <- a(href = "#", "My super link", style = "color: lightblue;") button <- make_action_button( - a(href = "#", "My super link", style = "color: lightblue;"), + tmp_tag, inputId = "mylink" ) expect_s3_class(button, "shiny.tag") @@ -174,4 +188,36 @@ test_that("Test make_action_button works", { as.character(button), 'My super link' ) + expect_error( + button_2 <- make_action_button( + unclass(tmp_tag), + inputId = "mylink_2" + ) + ) + expect_error( + button_3 <- make_action_button( + button, + inputId = "mylink_3" + ) + ) + expect_error( + button_4 <- make_action_button( + tmp_tag, + inputId = NULL + ) + ) + tmp_tag_2 <- tmp_tag + tmp_tag_2$attribs$id <- "id_already_present" + expect_warning( + button_5 <- make_action_button( + tmp_tag_2, + inputId = "mylink_5" + ) + ) + tmp_tag_3 <- tmp_tag + tmp_tag_3$attribs$class <- "class_already_present" + button_6 <- make_action_button( + tmp_tag_3, + inputId = "someID" + ) }) diff --git a/man/add_files.Rd b/man/add_files.Rd index 48901a8c..ca473887 100644 --- a/man/add_files.Rd +++ b/man/add_files.Rd @@ -8,6 +8,7 @@ \alias{add_css_file} \alias{add_sass_file} \alias{add_html_template} +\alias{add_partial_html_template} \alias{add_ui_server_files} \title{Create Files} \usage{ @@ -79,6 +80,14 @@ add_html_template( dir_create = TRUE ) +add_partial_html_template( + name = "partial_template.html", + pkg = get_golem_wd(), + dir = "inst/app/www", + open = TRUE, + dir_create = TRUE +) + add_ui_server_files(pkg = get_golem_wd(), dir = "inst/app", dir_create = TRUE) } \arguments{ diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd index 208f95a3..1c803ebb 100644 --- a/man/amend_golem_config.Rd +++ b/man/amend_golem_config.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/config.R +% Please edit documentation in R/golem-yaml-utils.R \name{amend_golem_config} \alias{amend_golem_config} \title{Amend golem config file} @@ -8,7 +8,7 @@ amend_golem_config( key, value, config = "default", - pkg = get_golem_wd(), + pkg = golem::pkg_path(), talkative = TRUE ) } diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index bbeb97b4..04536333 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,16 +1,19 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_dockerfiles.R +% Please edit documentation in R/add_dockerfiles.R, R/add_dockerfiles_renv.R \name{add_dockerfile} \alias{add_dockerfile} \alias{add_dockerfile_shinyproxy} \alias{add_dockerfile_heroku} +\alias{add_dockerfile_with_renv} +\alias{add_dockerfile_with_renv_shinyproxy} +\alias{add_dockerfile_with_renv_heroku} \title{Create a Dockerfile for your App} \usage{ add_dockerfile( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", @@ -27,7 +30,7 @@ add_dockerfile_shinyproxy( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -42,7 +45,7 @@ add_dockerfile_heroku( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -52,6 +55,60 @@ add_dockerfile_heroku( build_golem_from_source = TRUE, extra_sysreqs = NULL ) + +add_dockerfile_with_renv( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + document = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL, + ... +) + +add_dockerfile_with_renv_shinyproxy( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + document = TRUE, + update_tar_gz = TRUE, + ... +) + +add_dockerfile_with_renv_heroku( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + document = TRUE, + update_tar_gz = TRUE, + ... +) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -61,7 +118,12 @@ add_dockerfile_heroku( \item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{from}{The FROM of the Dockerfile. Default is -FROM rocker/r-ver:\code{R.Version()$major}.\code{R.Version()$minor}.} + +\if{html}{\out{
}}\preformatted{FROM rocker/verse + +without renv.lock file passed +`R.Version()$major`.`R.Version()$minor` is used as tag +}\if{html}{\out{
}}} \item{as}{The AS of the Dockerfile. Default it NULL.} @@ -71,13 +133,14 @@ Default is 80.} \item{host}{The \code{options('shiny.host')} on which to run the App. Default is 0.0.0.0.} -\item{sysreqs}{boolean. If TRUE, the Dockerfile will contain sysreq installation.} +\item{sysreqs}{boolean. If TRUE, RUN statements to install packages +system requirements will be included in the Dockerfile.} \item{repos}{character. The URL(s) of the repositories to use for \code{options("repos")}.} \item{expand}{boolean. If \code{TRUE} each system requirement will have its own \code{RUN} line.} -\item{open}{boolean. Should the Dockerfile be open after creation? Default is \code{TRUE}.} +\item{open}{boolean. Should the Dockerfile/README/README be open after creation? Default is \code{TRUE}.} \item{update_tar_gz}{boolean. If \code{TRUE} and \code{build_golem_from_source} is also \code{TRUE}, an updated tar.gz is created.} @@ -85,29 +148,65 @@ an updated tar.gz is created.} \item{build_golem_from_source}{boolean. If \code{TRUE} no tar.gz is created and the Dockerfile directly mount the source folder.} -\item{extra_sysreqs}{character vector. Extra debian system requirements. -Will be installed with apt-get install.} +\item{extra_sysreqs}{character vector. Extra debian system requirements.} + +\item{source_folder}{path to the Package/golem source folder to deploy. +default is current folder '.'} + +\item{lockfile}{path to the renv.lock file to use. default is \code{NULL}} + +\item{output_dir}{folder to export everything deployment related.} + +\item{distro}{One of "focal", "bionic", "xenial", "centos7", or "centos8". +See available distributions at https://hub.docker.com/r/rstudio/r-base/.} + +\item{document}{boolean. If TRUE (by default), DESCRIPTION file is updated using \code{\link[attachment:att_amend_desc]{attachment::att_amend_desc()}} before creating the renv.lock file} + +\item{dockerfile_cmd}{What is the CMD to add to the Dockerfile. If NULL, the default, +the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}');library(\{appname\});\{appname\}::run_app()\\}} + +\item{...}{Other arguments to pass to \code{\link[renv:snapshot]{renv::snapshot()}}} } \value{ The \code{{dockerfiler}} object, invisibly. } \description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a generic Dockerfile, while \code{add_dockerfile_shinyproxy()} and +Build a container containing your Shiny App. \code{add_dockerfile()} and +\code{add_dockerfile_with_renv()} and \code{add_dockerfile_with_renv()} creates +a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, +\code{add_dockerfile_with_renv_shinyproxy()} , \code{add_dockerfile_with_renv_shinyproxy()} and \code{add_dockerfile_heroku()} creates platform specific Dockerfile. } \examples{ \donttest{ # Add a standard Dockerfile -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile() } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem using docker based on {renv} +if (interactive() & requireNamespace("dockerfiler")) { + add_dockerfile_with_renv( + # lockfile = "renv.lock", # uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} # Add a Dockerfile for ShinyProxy -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_shinyproxy() } + +# Crete a 'deploy' folder containing everything needed to deploy +# the golem with ShinyProxy using docker based on {renv} +if (interactive() & requireNamespace("dockerfiler")) { + add_dockerfile_with_renv( + # lockfile = "renv.lock",# uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} + # Add a Dockerfile for Heroku -if (interactive()) { +if (interactive() & requireNamespace("dockerfiler")) { add_dockerfile_heroku() } } diff --git a/man/document_and_reload.Rd b/man/document_and_reload.Rd index 8d0c84ec..95b60561 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -24,7 +24,7 @@ which defaults to \code{c("collate", "namespace", "rd")}.} \item{load_code}{A function used to load all the R code in the package directory. The default, \code{NULL}, uses the strategy defined by -the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load]{load_pkgload()}}. +the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load_pkgload]{load_pkgload()}}. See \link[roxygen2]{load} for more details.} \item{clean}{If \code{TRUE}, roxygen will delete all files previously diff --git a/man/figures/logo.png b/man/figures/logo.png deleted file mode 100644 index a149d478..00000000 Binary files a/man/figures/logo.png and /dev/null differ diff --git a/man/fill_desc.Rd b/man/fill_desc.Rd index 87c4885a..a851ae08 100644 --- a/man/fill_desc.Rd +++ b/man/fill_desc.Rd @@ -13,6 +13,7 @@ fill_desc( author_email, author_orcid = NULL, repo_url = NULL, + pkg_version = "0.0.0.9000", pkg = get_golem_wd() ) } @@ -33,6 +34,8 @@ fill_desc( \item{repo_url}{URL (if needed)} +\item{pkg_version}{The version of the package. Default is 0.0.0.9000} + \item{pkg}{Path to look for the DESCRIPTION. Default is \code{get_golem_wd()}.} } \value{ diff --git a/man/get_current_config.Rd b/man/get_current_config.Rd new file mode 100644 index 00000000..b795d92b --- /dev/null +++ b/man/get_current_config.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{get_current_config} +\alias{get_current_config} +\title{Get the path to the current config File} +\usage{ +get_current_config(path = getwd()) +} +\arguments{ +\item{path}{Path to start looking for the config} +} +\description{ +This function tries to guess where the golem-config file is located. +If it can't find it, this function asks the +user if they want to set the golem skeleton. +} diff --git a/man/get_golem_options.Rd b/man/get_golem_options.Rd index b768d479..6f8a2a6c 100644 --- a/man/get_golem_options.Rd +++ b/man/get_golem_options.Rd @@ -18,38 +18,50 @@ server and UI from your app, in order to call the parameters passed to \code{run_app()}. } \examples{ -\dontrun{ # Define and use golem_options +if (interactive()) { + # 1. Pass parameters directly to `run_app` -# 1. Pass parameters to `run_app` - -# to set default value, edit run_app like this : -run_app <- function( - title = "this", - content = "that" -) { - with_golem_options( - app = shinyApp( - ui = app_ui, - server = app_server - ), - golem_opts = list( - p1 = p1, - p3 = p3 - ) + run_app( + title = "My Golem App", + content = "something" ) -} -# 2. Get the values from the UI side + # 2. Get the values + # 2.1 from the UI side + + h1(get_golem_options("title")) -h1(get_golem_options("title")) + # 2.2 from the server-side -# 3. Get the value from the server-side + output$param <- renderPrint({ + paste("param content = ", get_golem_options("content")) + }) -output$param <- renderPrint({ - paste("param p2 = ", get_golem_options("p2")) -}) + output$param_full <- renderPrint({ + get_golem_options() # list of all golem options as a list. + }) + + # 3. If needed, to set default value, edit `run_app` like this : + + run_app <- function( + title = "this", + content = "that", + ... + ) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server + ), + golem_opts = list( + title = title, + content = content, + ... + ) + ) + } } } diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index fa7de33a..131887a4 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -1,75 +1,86 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\alias{set_golem_wd} -\alias{set_golem_name} -\alias{set_golem_version} +% Please edit documentation in R/golem-yaml-get.R, R/golem-yaml-set.R, +% R/set_golem_options.R +\name{get_golem_wd} \alias{get_golem_wd} \alias{get_golem_name} \alias{get_golem_version} +\alias{set_golem_wd} +\alias{set_golem_name} +\alias{set_golem_version} +\alias{set_golem_options} \title{\code{{golem}} options} \usage{ -set_golem_options( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), +get_golem_wd(use_parent = TRUE, pkg = golem::pkg_path()) + +get_golem_name( + config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), + use_parent = TRUE, + pkg = golem::pkg_path() +) + +get_golem_version( + config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), + use_parent = TRUE, + pkg = golem::pkg_path() +) + +set_golem_wd( golem_wd = golem::pkg_path(), - app_prod = FALSE, + pkg = golem::pkg_path(), talkative = TRUE ) -set_golem_wd(path = golem::pkg_path(), talkative = TRUE) - set_golem_name( name = golem::pkg_name(), - path = golem::pkg_path(), - talkative = TRUE + pkg = golem::pkg_path(), + talkative = TRUE, + old_name = golem::pkg_name() ) set_golem_version( version = golem::pkg_version(), - path = golem::pkg_path(), + pkg = golem::pkg_path(), talkative = TRUE ) -get_golem_wd(use_parent = TRUE, path = golem::pkg_path()) - -get_golem_name( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) - -get_golem_version( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() +set_golem_options( + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) ) } \arguments{ -\item{golem_name}{Name of the current golem.} +\item{use_parent}{\code{TRUE} to scan parent directories for +configuration files if the specified config file isn't found.} -\item{golem_version}{Version of the current golem.} +\item{pkg}{The path to set the golem working directory. +Note that it will be passed to \code{normalizePath}.} -\item{golem_wd}{Working directory of the current golem package.} +\item{config}{Name of configuration to read from. Defaults to +the value of the \code{R_CONFIG_ACTIVE} environment variable +("default" if the variable does not exist).} -\item{app_prod}{Is the \code{{golem}} in prod mode?} +\item{golem_wd}{Working directory of the current golem package.} \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{old_name}{The old name of the app, used when changing the name} + \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{golem_name}{Name of the current golem.} -\item{config}{Name of configuration to read from. Defaults to -the value of the \code{R_CONFIG_ACTIVE} environment variable -("default" if the variable does not exist).} +\item{golem_version}{Version of the current golem.} + +\item{app_prod}{Is the \code{{golem}} in prod mode?} + +\item{config_file}{path to the {golem} config file} } \value{ Used for side-effects for the setters, and values from the @@ -84,7 +95,7 @@ inside the \code{inst} folder. \itemize{ \item \code{set_golem_options()} sets all the options, with the defaults from the functions below. -\item \code{set_golem_wd()} defaults to \code{here::here()}, which is the package root when starting a golem. +\item \code{set_golem_wd()} defaults to \code{golem::golem_wd()}, which is the package root when starting a golem. \item \code{set_golem_name()} defaults \code{golem::pkg_name()} \item \code{set_golem_version()} defaults \code{golem::pkg_version()} } diff --git a/man/install_dev_deps.Rd b/man/install_dev_deps.Rd new file mode 100644 index 00000000..22f7fce8 --- /dev/null +++ b/man/install_dev_deps.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/install_dev_deps.R +\name{install_dev_deps} +\alias{install_dev_deps} +\title{Install {golem} dev dependencies} +\usage{ +install_dev_deps(force_install = FALSE, ...) +} +\arguments{ +\item{force_install}{If force_install is installed, +then the user is not interactively asked +to install them.} + +\item{...}{further arguments passed to the install function.} +} +\value{ +Used for side-effects +} +\description{ +This function will run rlang::check_installed() on: +\itemize{ +\item {usethis} +\item {pkgload} +\item {dockerfiler} +\item {devtools} +\item {roxygen2} +\item {attachment} +\item {rstudioapi} +\item {here} +\item {fs} +\item {desc} +\item {pkgbuild} +\item {processx} +\item {rsconnect} +\item {testthat} +\item {rstudioapi} +} +} +\examples{ +if (interactive()) { + install_dev_deps() +} + +} diff --git a/man/is_golem.Rd b/man/is_golem.Rd new file mode 100644 index 00000000..784130f9 --- /dev/null +++ b/man/is_golem.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/is_golem.R +\name{is_golem} +\alias{is_golem} +\title{Is the directory a golem-based app?} +\usage{ +is_golem(path = getwd()) +} +\arguments{ +\item{path}{Path to the directory to check. +Defaults to the current working directory.} +} +\description{ +Trying to guess if \code{path} is a golem-based app. +} +\examples{ +is_golem() +} diff --git a/man/is_running.Rd b/man/is_running.Rd index b7605526..adb6cee7 100644 --- a/man/is_running.Rd +++ b/man/is_running.Rd @@ -9,8 +9,6 @@ is_running() \value{ TRUE if the running app is a \code{{golem}} based app, FALSE otherwise. - -A boolean. } \description{ Note that this will return \code{TRUE} only if the application diff --git a/man/maintenance_page.Rd b/man/maintenance_page.Rd new file mode 100644 index 00000000..42bb5c7f --- /dev/null +++ b/man/maintenance_page.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/with_opt.R +\name{maintenance_page} +\alias{maintenance_page} +\title{maintenance_page} +\usage{ +maintenance_page() +} +\value{ +an html_document +} +\description{ +A default html page for maintenance mode +} +\details{ +see the vignette \code{vignette("f_extending_golem", package = "golem")} for details. +} diff --git a/man/pkg_tools.Rd b/man/pkg_tools.Rd index 14f802ab..d9a5869a 100644 --- a/man/pkg_tools.Rd +++ b/man/pkg_tools.Rd @@ -10,13 +10,13 @@ pkg_name(path = ".") pkg_version(path = ".") -pkg_path(path = ".", depth = 3) +pkg_path() } \arguments{ \item{path}{Path to use to read the DESCRIPTION} - -\item{depth}{Number of parent directory to visit before -deciding we are not in a package.} +} +\value{ +The value of the entry in the DESCRIPTION file } \description{ These are functions to help you navigate diff --git a/man/prod.Rd b/man/prod.Rd index bdabc03a..9255c15b 100644 --- a/man/prod.Rd +++ b/man/prod.Rd @@ -11,8 +11,6 @@ app_dev() } \value{ \code{TRUE} or \code{FALSE} depending on the status of \code{getOption( "golem.app.prod")} - -A boolean. } \description{ Is the app in dev mode or prod mode? diff --git a/man/run_dev.Rd b/man/run_dev.Rd index 3f12448a..eb25135a 100644 --- a/man/run_dev.Rd +++ b/man/run_dev.Rd @@ -4,12 +4,14 @@ \alias{run_dev} \title{Run run_dev.R} \usage{ -run_dev(file = "dev/run_dev.R", pkg = get_golem_wd()) +run_dev(file = "dev/run_dev.R", pkg = get_golem_wd(), save_all = TRUE) } \arguments{ \item{file}{File path to \code{run_dev.R}. Defaults to \code{R/run_dev.R}.} \item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} + +\item{save_all}{boolean. If TRUE, save all open file before sourcing \code{file}} } \value{ Used for side-effect diff --git a/man/testhelpers.Rd b/man/testhelpers.Rd index 6ad0ed56..0b516339 100644 --- a/man/testhelpers.Rd +++ b/man/testhelpers.Rd @@ -35,7 +35,3 @@ A testthat result. These functions are designed to be used inside the tests in your Shiny app package. } -\examples{ -expect_shinytag(shiny::tags$span("1")) -expect_shinytaglist(shiny::tagList(1)) -} diff --git a/man/use_readme_rmd.Rd b/man/use_readme_rmd.Rd new file mode 100644 index 00000000..89a04779 --- /dev/null +++ b/man/use_readme_rmd.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_readme.R +\name{use_readme_rmd} +\alias{use_readme_rmd} +\title{Generate a README.Rmd} +\usage{ +use_readme_rmd( + open = rlang::is_interactive(), + pkg_name = golem::get_golem_name(), + overwrite = FALSE, + pkg = golem::get_golem_wd() +) +} +\arguments{ +\item{open}{Open the newly created file for editing? Happens in RStudio, if +applicable, or via \code{\link[utils:file.edit]{utils::file.edit()}} otherwise.} + +\item{pkg_name}{The name of the package} + +\item{overwrite}{an optional \code{logical} flag; if \code{TRUE}, overwrite +existing \code{README.Rmd}, else throws an error if \code{README.Rmd} exists} + +\item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} +} +\value{ +pure side-effect function that generates template \code{README.Rmd} +} +\description{ +Generate a README.Rmd +} diff --git a/man/with_golem_options.Rd b/man/with_golem_options.Rd index 21bb7303..ad35ab25 100644 --- a/man/with_golem_options.Rd +++ b/man/with_golem_options.Rd @@ -4,16 +4,23 @@ \alias{with_golem_options} \title{Add Golem options to a Shiny App} \usage{ -with_golem_options(app, golem_opts, print = FALSE) +with_golem_options( + app, + golem_opts, + maintenance_page = golem::maintenance_page, + print = FALSE +) } \arguments{ \item{app}{the app object.} -\item{golem_opts}{A list of Options to be added to the app} +\item{golem_opts}{A list of options to be added to the app} + +\item{maintenance_page}{an html_document or a shiny tag list. Default is golem template.} \item{print}{Whether or not to print the app. Default is to \code{FALSE}, which -should be what you need 99.99\% of the time In case you need to -actively print the app object, you can set it to \code{TRUE}.} +should be what you need 99.99\% of the time. In case you need to +actively print() the app object, you can set it to \code{TRUE}.} } \value{ a shiny.appObj object diff --git a/tests/testthat/DESCRIPTION b/tests/testthat/DESCRIPTION index f2f99f57..d0637eec 100644 --- a/tests/testthat/DESCRIPTION +++ b/tests/testthat/DESCRIPTION @@ -1,13 +1,16 @@ Package: shinyexample Title: newtitle Version: 0.0.0.9000 -Authors@R: person('firstname', 'lastname', email = 'name@test.com', role = c('cre', 'aut')) +Authors@R: c( + person("Colin", "Fay", , "contact@colinfay.me", role = c("cre", "aut"), + comment = c(ORCID = "0000-0001-7343-1846")) + ) Description: Newdescription. License: What license is it under? Encoding: UTF-8 LazyData: true -Imports: - shiny, +Imports: + shiny, golem RoxygenNote: 6.1.0 URL: http://repo_url.com diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index d9af3ab9..81554197 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -3,6 +3,12 @@ library(golem) library(withr) old_usethis.quiet <- getOption("usethis.quiet") options("usethis.quiet" = TRUE) +# Small hack to prevent warning from rlang::lang() in tests +# This should be managed in {attempt} later on +x <- suppressWarnings({ + rlang::lang(print) +}) + ### Funs remove_file <- function(path) { if (file.exists(path)) unlink(path, force = TRUE) @@ -56,13 +62,15 @@ fakename <- sprintf( gsub("[ :-]", "", Sys.time()) ) + +## random dir +randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") + tpdir <- normalizePath(tempdir()) unlink(file.path(tpdir, fakename), recursive = TRUE) create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) -## random dir -randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") fp <- file.path("inst/app", randir) dir.create(file.path(pkg, fp), recursive = TRUE) @@ -71,8 +79,32 @@ rand_name <- function() { } withr::with_dir(pkg, { - set_golem_options() + # Some weird things with {here} + unloadNamespace("here") + if (!file.exists(".here")) { + here::set_here(path_to_golem) + } + if (requireNamespace("desc", quietly = TRUE)) { + set_golem_options() + } usethis::proj_set(pkg) - orig_test <- set_golem_wd(pkg) + orig_test <- set_golem_wd( + pkg = pkg + ) usethis::use_mit_license("Golem") }) + + +create_deploy_folder <- function(){ +file.path( + tempdir(), + make.names( + paste0( + "deploy", + round( + runif(1, min = 0, max = 99999) + ) + ) + ) + ) +} diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 8aef7553..70d9e9e0 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -1,5 +1,8 @@ test_that("add_dockerfiles", { - skip_if_not_installed("dockerfiler", "0.1.4") + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.2.5") + with_dir(pkg, { for (fun in list( add_dockerfile, @@ -9,9 +12,15 @@ test_that("add_dockerfiles", { burn_after_reading( "Dockerfile", { - output <- testthat::capture_output( - fun(pkg = pkg, sysreqs = FALSE, open = FALSE) + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- testthat::capture_output( + fun(pkg = pkg, sysreqs = FALSE, open = FALSE) + ) + } ) + expect_exists("Dockerfile") test <- stringr::str_detect( output, @@ -34,27 +43,31 @@ test_that("add_dockerfiles repos variation", { burn_after_reading( c("Dockerfile1", "Dockerfile2"), { - output1 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = "https://cran.rstudio.com/", - output = "Dockerfile1" - ) - ) - output2 <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = c("https://cran.rstudio.com/"), - output = "Dockerfile2" - ) + withr::with_options( + c("golem.quiet" = FALSE), + { + output1 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = "https://cran.rstudio.com/", + output = "Dockerfile1" + ) + ) + output2 <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + repos = c("https://cran.rstudio.com/"), + output = "Dockerfile2" + ) + ) + expect_exists("Dockerfile1") + expect_exists("Dockerfile2") + } ) - expect_exists("Dockerfile1") - expect_exists("Dockerfile2") - test1 <- stringr::str_detect( output1, @@ -76,60 +89,8 @@ test_that("add_dockerfiles repos variation", { } }) }) -test_that("add_dockerfiles multi repos", { - skip_if_not_installed("dockerfiler", "0.1.4") - - repos <- c( - bioc1 = "https://bioconductor.org/packages/3.10/data/annotation", - bioc2 = "https://bioconductor.org/packages/3.10/data/experiment", - CRAN = "https://cran.rstudio.com" - ) - with_dir(pkg, { - for (fun in list( - add_dockerfile, - add_dockerfile_heroku, - add_dockerfile_shinyproxy - )) { - burn_after_reading( - "Dockerfile", - { - output <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = repos, - output = "Dockerfile" - ) - ) - - expect_exists("Dockerfile") - - - test <- stringr::str_detect( - output, - "Dockerfile created at Dockerfile" - ) - expect_true(test) - - - to_find <- "RUN echo \"options(repos = c(bioc1 = 'https://bioconductor.org/packages/3.10/data/annotation', bioc2 = 'https://bioconductor.org/packages/3.10/data/experiment', CRAN = 'https://cran.rstudio.com'), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - # for R <= 3.4 - to_find_old <- "RUN echo \"options(repos = structure(c('https://bioconductor.org/packages/3.10/data/annotation', 'https://bioconductor.org/packages/3.10/data/experiment', 'https://cran.rstudio.com'), .Names = c('bioc1', 'bioc2', 'CRAN')), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - - expect_true( - sum( - readLines(con = "Dockerfile") %in% c(to_find, to_find_old) - ) == 1 - ) - } - ) - } - }) -}) - test_that("add_rstudio_files", { with_dir(pkg, { for (fun in list( @@ -140,11 +101,16 @@ test_that("add_rstudio_files", { burn_after_reading( "app.R", { - output <- testthat::capture_output( - fun( - pkg = pkg, - open = FALSE - ) + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- testthat::capture_output( + fun( + pkg = pkg, + open = FALSE + ) + ) + } ) expect_exists("app.R") test <- stringr::str_detect( diff --git a/tests/testthat/test-add_files.R b/tests/testthat/test-add_files.R index 456d3bee..ba8762e2 100644 --- a/tests/testthat/test-add_files.R +++ b/tests/testthat/test-add_files.R @@ -9,6 +9,12 @@ expect_add_file <- function( name <- rand_name() # Be sure to remove all files in case there are remove_files("inst/app/www", ext) + + # Checking that check_name_length is throwing an error + expect_error( + fun(c("a", "b")), + ) + # Launch the function fun(name, pkg = pak, open = FALSE) if (fun_nms == "add_js_input_binding") { @@ -108,6 +114,18 @@ test_that("add_files", { pak = pkg, fp = fp ) + expect_add_file( + add_html_template, + ext = "html", + pak = pkg, + fp = fp + ) + expect_add_file( + add_partial_html_template, + ext = "html", + pak = pkg, + fp = fp + ) }) }) diff --git a/tests/testthat/test-add_modules.R b/tests/testthat/test-add_modules.R index a1584aef..9a98bd29 100644 --- a/tests/testthat/test-add_modules.R +++ b/tests/testthat/test-add_modules.R @@ -15,7 +15,12 @@ test_that("add_module", { lapply(tools::file_ext(script), function(x) testthat::expect_equal(x, "R")) ## Test message of function remove_file("R/mod_output.R") - output <- testthat::capture_output(add_module("output", open = FALSE)) + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- testthat::capture_output(add_module("output", open = FALSE)) + } + ) expect_true( stringr::str_detect(output, "File created at R/mod_output.R") ) @@ -44,5 +49,20 @@ test_that("add_module", { remove_file("R/mod_test2.R") remove_file("R/mod_test2_fct_ftest.R") remove_file("R/mod_test2_utils_utest.R") + + # Checking that the mod_ prefix is removed and added + add_module( + "mod_mod_mod_test2.R", + open = FALSE, + pkg = pkg, + fct = "ftest", + utils = "utest" + ) + expect_true(file.exists("R/mod_test2.R")) + expect_true(file.exists("R/mod_test2_fct_ftest.R")) + expect_true(file.exists("R/mod_test2_utils_utest.R")) + remove_file("R/mod_test2.R") + remove_file("R/mod_test2_fct_ftest.R") + remove_file("R/mod_test2_utils_utest.R") }) }) diff --git a/tests/testthat/test-add_r_files.R b/tests/testthat/test-add_r_files.R index 7cc95f25..8232bc54 100644 --- a/tests/testthat/test-add_r_files.R +++ b/tests/testthat/test-add_r_files.R @@ -9,6 +9,13 @@ test_that("add_fct and add_utils", { add_fct("ui", pkg = pkg, open = FALSE, with_test = TRUE) add_utils("ui", pkg = pkg, open = FALSE, with_test = TRUE) + expect_error( + add_fct(c("a", "b")), + ) + expect_error( + add_utils(c("a", "b")), + ) + expect_true(file.exists(util_file)) expect_true(file.exists(fct_file)) expect_true(file.exists("tests/testthat/test-utils_ui.R")) @@ -21,6 +28,10 @@ test_that("add_fct and add_utils", { expect_true(file.exists(sprintf("R/mod_%s_fct_ui.R", rand))) expect_true(file.exists(sprintf("R/mod_%s_utils_ui.R", rand))) + expect_error( + add_module(c("a", "b")), + ) + # If module not yet created an error is thrown expect_error( add_fct("ui", module = "notyetcreated", pkg = pkg, open = FALSE), diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index f5256e42..3f56c660 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -1,8 +1,24 @@ test_that("config works", { with_dir(pkg, { - expect_equal(get_golem_name(), fakename) - expect_equal(get_golem_version(), "0.0.0.9000") - expect_equal(normalizePath(get_golem_wd(), mustWork = FALSE), normalizePath(pkg, mustWork = FALSE)) + # We'll try to be sure that + # golem_wd: !expr golem::pkg_path() + # is kept along the way + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + get_golem_name(), + fakename + ) + expect_equal( + get_golem_version(), + "0.0.0.9000" + ) + expect_equal( + normalizePath(get_golem_wd(), mustWork = FALSE), + normalizePath(pkg, mustWork = FALSE) + ) amend_golem_config( key = "where", value = "indev" @@ -12,26 +28,53 @@ test_that("config works", { value = "inprod", config = "production" ) - - expect_equal(config::get("where", file = "inst/golem-config.yml"), "indev") - expect_equal(config::get("where", config = "production", file = "inst/golem-config.yml"), "inprod") + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + config::get("where", file = "inst/golem-config.yml"), + "indev" + ) + expect_equal( + config::get("where", config = "production", file = "inst/golem-config.yml"), + "inprod" + ) + where_conf <- withr::with_envvar( c("R_CONFIG_ACTIVE" = "production"), { config::get("where", file = "inst/golem-config.yml") } ) - expect_equal(where_conf, "inprod") + expect_equal( + where_conf, + "inprod" + ) set_golem_name("plop") - expect_equal(get_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") + expect_equal( + get_golem_version(), + "0.0.0.9001" + ) set_golem_version("0.0.0.9000") set_golem_wd(normalizePath("inst")) - expect_equal(normalizePath(get_golem_wd()), normalizePath("inst")) + expect_equal( + normalizePath(get_golem_wd()), + normalizePath("inst") + ) set_golem_wd(pkg) + # Be sure that after setting the stuff the wd is still here::here() + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) }) }) diff --git a/tests/testthat/test-create_golem.R b/tests/testthat/test-create_golem.R index 0dd4a130..92992f06 100644 --- a/tests/testthat/test-create_golem.R +++ b/tests/testthat/test-create_golem.R @@ -2,7 +2,6 @@ ### Helpers functions ---------------------------------------------------------- is_properly_populated_golem <- function(path) { - # All files excepts *.Rproj which changes based on the project name expected_files <- c( "DESCRIPTION", @@ -62,7 +61,6 @@ dummy_dir <- tempfile(pattern = "dummy") dir.create(dummy_dir) withr::with_dir(dummy_dir, { - ## Default test_that("golem is created and properly populated", { dummy_golem_path <- file.path(dummy_dir, "koko") diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R index 7af2b91f..c1b7aa51 100644 --- a/tests/testthat/test-desc.R +++ b/tests/testthat/test-desc.R @@ -1,41 +1,80 @@ - test_that("desc works", { + testthat::skip_if_not_installed("desc") with_dir(pkg, { - output <- capture_output( - fill_desc( - fakename, - "newtitle", - "Newdescription.", - "firstname", - "lastname", - "name@test.com", - "http://repo_url.com" - ) + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- capture_output( + fill_desc( + pkg_name = fakename, + pkg_title = "newtitle", + pkg_description = "Newdescription.", + authors = person( + given = "firstname", + family = "lastname", + email = "name@test.com" + ), + repo_url = "http://repo_url.com", + pkg_version = "0.0.0.9000" + ) + ) + } ) add_desc <- c( fakename, "newtitle", "Newdescription.", - "firstname", - "lastname", - "name@test.com", - "http://repo_url.com" + "person('firstname', 'lastname', , 'name@test.com')", + "http://repo_url.com", + "0.0.0.9000" ) desc <- readLines("DESCRIPTION") expect_true( all( as.logical(lapply( - add_desc, + add_desc[-4], function(x) { any(grepl(x, desc)) } )) ) ) + # add additional test, as authors = person(...) requires parsing test + tmp_test_add_desc <- eval(parse(text = add_desc[4])) + tmp_test_desc <- eval(parse(text = desc[[5]])) + expect_identical( + tmp_test_add_desc, + tmp_test_desc + ) expect_true( stringr::str_detect(output, "DESCRIPTION file modified") ) + + # test retrocompatibility + withr::with_options( + c("golem.quiet" = FALSE), + { + expect_warning( + fill_desc( + pkg_name = fakename, + pkg_title = "newtitle", + pkg_description = "Newdescription.", + author_first_name = "firstname", + author_last_name = "lastname", + author_email = "test@test.com" + ) + ) + expect_equal( + as.character(desc::desc_get("Title")), + "newtitle" + ) + expect_equal( + as.character(desc::desc_get_authors()), + "firstname lastname " + ) + } + ) }) }) diff --git a/tests/testthat/test-extra_sysreqs.R b/tests/testthat/test-extra_sysreqs.R index 2b5f34d8..53b906cb 100644 --- a/tests/testthat/test-extra_sysreqs.R +++ b/tests/testthat/test-extra_sysreqs.R @@ -1,4 +1,9 @@ +skip_if_not_installed("dockerfiler", minimum_version = "0.2.0") + test_that("test extra sysreqs", { + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.2.5") with_dir(pkg, { for (fun in list( add_dockerfile, @@ -8,16 +13,20 @@ test_that("test extra sysreqs", { burn_after_reading( "Dockerfile", { - output <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - extra_sysreqs = c("test1", "test2"), - output = "Dockerfile" - ) + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- testthat::capture_output( + fun( + pkg = pkg, + sysreqs = FALSE, + open = FALSE, + extra_sysreqs = c("test1", "test2"), + output = "Dockerfile" + ) + ) + } ) - expect_exists("Dockerfile") test <- stringr::str_detect( output, diff --git a/tests/testthat/test-install_dev_deps.R b/tests/testthat/test-install_dev_deps.R new file mode 100644 index 00000000..8eebb5fb --- /dev/null +++ b/tests/testthat/test-install_dev_deps.R @@ -0,0 +1,32 @@ +test_that("install_dev_deps works", { + install_dev_deps( + force_install = TRUE, + repos = "https://cran.rstudio.com" + ) + paks <- unique( + c( + "usethis", + "pkgload", + "dockerfiler", + "devtools", + "roxygen2", + "attachment", + "rstudioapi", + "here", + "fs", + "desc", + "pkgbuild", + "processx", + "rsconnect", + "testthat", + "rstudioapi" + ) + ) + for ( + pak in paks + ) { + expect_true( + rlang::is_installed(pak) + ) + } +}) diff --git a/tests/testthat/test-is_golem.R b/tests/testthat/test-is_golem.R new file mode 100644 index 00000000..8fe64dd0 --- /dev/null +++ b/tests/testthat/test-is_golem.R @@ -0,0 +1,8 @@ +test_that("is_golem works", { + expect_true( + is_golem(pkg) + ) + expect_false( + is_golem(tempdir()) + ) +}) diff --git a/tests/testthat/test-maintenance.R b/tests/testthat/test-maintenance.R new file mode 100644 index 00000000..8464e04f --- /dev/null +++ b/tests/testthat/test-maintenance.R @@ -0,0 +1,4 @@ +test_that("test maintenance_page", { + html <- maintenance_page() + expect_true(inherits(html, c("html_document", "shiny.tag.list", "list"))) +}) diff --git a/tests/testthat/test-make_dev.R b/tests/testthat/test-make_dev.R index bbf46547..66013a26 100644 --- a/tests/testthat/test-make_dev.R +++ b/tests/testthat/test-make_dev.R @@ -60,7 +60,12 @@ test_that("test print_dev", { test_that("test browser_button", { - output <- capture_output_lines(browser_button()) + withr::with_options( + c("golem.quiet" = FALSE), + { + output <- capture_output_lines(browser_button()) + } + ) expect_true( grepl('actionButton\\("browser", "browser"\\)', output[2]) ) diff --git a/tests/testthat/test-pkg_tools.R b/tests/testthat/test-pkg_tools.R index d56c0a43..bc1af90a 100644 --- a/tests/testthat/test-pkg_tools.R +++ b/tests/testthat/test-pkg_tools.R @@ -1,9 +1,11 @@ +skip_if_not_installed("pkgload") + test_that("pkgtools works", { withr::with_dir(pkg, { - expect_equal(pkg_name(), fakename) - expect_equal(pkg_version(), "0.0.0.9000") + expect_equal(pkgload::pkg_name(), fakename) + expect_equal(as.character(pkgload::pkg_version()), "0.0.0.9000") # F-word windows path skip_on_os("windows") - expect_equal(pkg_path(), pkg) + expect_equal(pkgload::pkg_path(), pkg) }) }) diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R new file mode 100644 index 00000000..6c226434 --- /dev/null +++ b/tests/testthat/test-renv_stuff.R @@ -0,0 +1,65 @@ +test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all output file are present", { + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.2.5") + with_dir(pkg, { + for (fun in list( + add_dockerfile_with_renv, + add_dockerfile_with_renv_shinyproxy, + add_dockerfile_with_renv_heroku + )) { + deploy_folder <- create_deploy_folder() + desc::desc_set_authors( + person( + given = "colin", + family = "fay", + email = "contact@colinfay.me", + role = c("aut", "cre"), + comment = c(ORCID = "0000-0001-5879-4195") + ) + ) + + fun(output_dir = deploy_folder, open = FALSE) + + expect_exists(file.path(deploy_folder, "Dockerfile")) + expect_exists(file.path(deploy_folder, "Dockerfile_base")) + expect_exists(file.path(deploy_folder, "README")) + expect_exists(file.path(deploy_folder, "renv.lock.prod")) + + expect_length(list.files(path = deploy_folder, pattern = "tar.gz$"), 1) + unlink(deploy_folder, force = TRUE, recursive = TRUE) + } + }) +}) +test_that("suggested package are not in renv prod", { + skip_if_not_installed("renv") + skip_if_not_installed("dockerfiler", "0.2.0") + skip_if_not_installed("attachment", "0.3.1") + with_dir( + pkg, + { + desc_file <- file.path("DESCRIPTION") + desc_lines <- readLines(desc_file) + # desc_lines <- c(desc_lines,"Suggests: \n idontexist") + desc_lines[desc_lines == "Suggests: "] <- "Suggests: \n idontexist," + writeLines(desc_lines, desc_file) + deploy_folder <- create_deploy_folder() + desc::desc_set_authors( + person( + given = "colin", + family = "fay", + email = "contact@colinfay.me", + role = c("aut", "cre"), + comment = c(ORCID = "0000-0001-5879-4195") + ) + ) + add_dockerfile_with_renv(output_dir = deploy_folder, open = FALSE) + + base <- paste(readLines(file.path(deploy_folder, "renv.lock.prod")), collapse = " ") + expect_false(grepl(pattern = "idontexist", x = base)) + expect_true(grepl(pattern = "shiny", x = base)) + + unlink(deploy_folder, force = TRUE, recursive = TRUE) + } + ) +}) diff --git a/tests/testthat/test-test_helpers.R b/tests/testthat/test-test_helpers.R index c1e4ccad..307e7e3d 100644 --- a/tests/testthat/test-test_helpers.R +++ b/tests/testthat/test-test_helpers.R @@ -1,13 +1,23 @@ test_that("test expect_shinytag", { with_dir(pkg, { - expect_equal(capture_output(expect_shinytag(favicon("jean"))), "") - expect_error(expect_shinytag("pierre")) + withr::with_options( + c("golem.quiet" = FALSE), + { + expect_equal(capture_output(expect_shinytag(favicon("jean"))), "") + expect_error(expect_shinytag("pierre")) + } + ) }) }) test_that("test expect_shinytaglist", { with_dir(pkg, { - expect_equal(capture_output(expect_shinytaglist(shiny::tagList())), "") - expect_error(expect_shinytaglist("test")) + withr::with_options( + c("golem.quiet" = FALSE), + { + expect_equal(capture_output(expect_shinytaglist(shiny::tagList())), "") + expect_error(expect_shinytaglist("test")) + } + ) }) }) diff --git a/tests/testthat/test-use_readme.R b/tests/testthat/test-use_readme.R new file mode 100644 index 00000000..33d9f7e0 --- /dev/null +++ b/tests/testthat/test-use_readme.R @@ -0,0 +1,40 @@ +test_that("generate_readme_tmpl works", { res <- generate_readme_tmpl("my_pkg") + expect_true( + grepl("my_pkg", paste(res, collapse = " ")) + ) + expect_true( + grepl("my_pkg::run_app()", paste(res, collapse = " ")) + ) + expect_true( + grepl("covr::package_coverage", paste(res, collapse = " ")) + ) + expect_true( + grepl("unloadNamespace", paste(res, collapse = " ")) + ) + expect_true( + grepl("devtools::check", paste(res, collapse = " ")) + ) +}) + + +test_that("check_overwrite works", { + expect_error( + check_overwrite(FALSE, golem_sys("utils/empty_readme.Rmd")), + "README.Rmd already exists. Set `overwrite = TRUE` to overwrite." + ) +}) + +test_that("use_readme_rmd works", { + expect_true( + use_readme_rmd( + open = FALSE, + overwrite = TRUE, + pkg = getwd(), + pkg_name = "rand_name" + ) + ) + expect_true( + file.exists("README.Rmd") + ) + devtools:::build_readme() +}) diff --git a/tests/testthat/test-use_recomended.R b/tests/testthat/test-use_recomended.R index 738c7370..cb457b13 100644 --- a/tests/testthat/test-use_recomended.R +++ b/tests/testthat/test-use_recomended.R @@ -1,5 +1,6 @@ test_that("test use_recommended_deps", { testthat::skip_on_cran() + testthat::skip_if_not_installed("desc") with_dir(pkg, { packages <- c("shiny", "DT", "attempt", "glue", "golem", "htmltools") to_add <- c() @@ -11,7 +12,7 @@ test_that("test use_recommended_deps", { expect_warning( use_recommended_deps(recommended = to_add) ) - deps <- desc::desc_get_deps(file = "DESCRIPTION") + deps <- desc_get_deps(file = "DESCRIPTION") expect_true( all(to_add %in% deps$package) ) diff --git a/tests/testthat/test-zreload.R b/tests/testthat/test-zreload.R index b97b1f22..5f0abd54 100644 --- a/tests/testthat/test-zreload.R +++ b/tests/testthat/test-zreload.R @@ -20,9 +20,9 @@ test_that("test document_and_reload", { }) }) -test_that("test detach_all_attached", { - with_dir(pkg_reload, { - test <- detach_all_attached() - testthat::expect_true(test) - }) -}) +# test_that("test detach_all_attached", { +# with_dir(pkg_reload, { +# test <- detach_all_attached() +# testthat::expect_true(test) +# }) +# }) diff --git a/tests/testthat/test-zzzzzzzzzz.R b/tests/testthat/test-zzzzzzzzzz.R index 82aa1a70..53321f1a 100644 --- a/tests/testthat/test-zzzzzzzzzz.R +++ b/tests/testthat/test-zzzzzzzzzz.R @@ -1,11 +1,7 @@ # # For setting back old usethis settings try({ - if (exists("orig_test")) { - usethis::proj_set(orig_test) - } - if (exists("pkg")) { - unlink(pkg, TRUE, TRUE) - } - + unlink("README.Rmd", TRUE, TRUE) + unlink("README.md", TRUE, TRUE) + unlink(pkg, TRUE, TRUE) options("usethis.quiet" = old_usethis.quiet) }) diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 002671ec..8535ab89 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -1,5 +1,5 @@ --- -title: "Getting Started with {golem}" +title: "a_start" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -32,25 +32,25 @@ install.packages("golem") ``` -The development version of `{golem}` can be installed from GitHub using the `{remotes}` package: +The development version of `{golem}` can be installed from GitHub using the `{remotes}` package: ```{r} remotes::install_github("Thinkr-open/golem") ``` -## Getting started +## Getting started -Note before using `{golem}`: +Note before using `{golem}`: - A `{golem}` app is contained inside a package, so knowing how to build a package is highly recommended. On the plus side, everything you know about package development can be reused in `{golem}`. -- A `{golem}` app works better if you are working with `shiny modules`, so knowing how modules work is recommended, but not mandatory. +- A `{golem}` app works better if you are working with `shiny modules`, so knowing how modules work is recommended, but not mandatory. -In the rest of the Vignettes, we'll assume you're working in RStudio. +In the rest of the Vignettes, we'll assume you're working in RStudio. ### Create a package -Once the package is installed, you can got to File > New Project... in RStudio, and choose "Package for Shiny App Using golem" input. +Once the package is installed, you can got to File > New Project... in RStudio, and choose "Package for Shiny App Using golem" input. ```{r, echo=FALSE, out.width="80%", fig.align="center", eval=TRUE} knitr::include_graphics("golemtemplate.png") @@ -69,7 +69,7 @@ try(fs::dir_delete(x), silent = TRUE) golem::create_golem(path = x, package_name = "golex", open = FALSE) ``` -This command allows you to create "illegally-named" package (for example, `1234`) by passing the `check_name` argument to `FALSE`. Note that this is not recommended and __should only be done if you know what you are doing__. +This command allows you to create "illegally-named" package (for example, `1234`) by passing the `check_name` argument to `FALSE`. Note that this is not recommended and __should only be done if you know what you are doing__. Once you've got that, a new RStudio project will be launched. Here is the structure of this project: @@ -77,7 +77,8 @@ Once you've got that, a new RStudio project will be launched. Here is the struct z <- capture.output(fs::dir_tree(x)) z <- z[-1] w <- lapply( - z, function(x) { + z, + function(x) { cat(x, "\n") } ) @@ -90,19 +91,19 @@ If you're already familiar with R packages, most of these files will seem very f + `R/app_config.R`: Used to read inside `{golem}` config file located at `inst/golem-config.yml`. -+ `R/app_server.R`, `R/app_ui.R`: Top level UI and server elements. ++ `R/app_server.R`, `R/app_ui.R`: Top level UI and server elements. + `R/run_app.R`: a function to configure and launch the application. + `dev/`: Scripts that will be used along the process of developing your app. You don't need to fill all the script before starting: use them as a notebook for keeping track of what you're doing all along the project life. -+ `inst/app/www`: Where you will add external dependencies in `www` (images, css, etc), notably added with the `golem` functions used to create external resources. ++ `inst/app/www`: Where you will add external dependencies in `www` (images, css, etc), notably added with the `golem` functions used to create external resources. -+ `man`: Package documentation, to be generated by R & `{roxygen2}`. ++ `man`: Package documentation, to be generated by R & `{roxygen2}`. ## `dev/01_start.R` -Once you've created your project, the first file that opens is `dev/01_start.R`. This file contains a series of commands that you'll have to run once, at the beginning of the project. +Once you've created your project, the first file that opens is `dev/01_start.R`. This file contains a series of commands that you'll have to run once, at the beginning of the project. Note that you don't have to fill everything, event thought it's strongly recommended. @@ -112,13 +113,16 @@ First, fill the DESCRIPTION by adding information about the package that will co ```{r } golem::fill_desc( - pkg_name = "shinyexample", # The Name of the package containing the App - pkg_title = "PKG_TITLE", # The Title of the package containing the App - pkg_description = "PKG_DESC.", # The Description of the package containing the App - author_first_name = "AUTHOR_FIRST", # Your First Name - author_last_name = "AUTHOR_LAST", # Your Last Name - author_email = "AUTHOR@MAIL.COM", # Your Email - repo_url = NULL # The (optional) URL of the GitHub Repo + pkg_name = "shinyexample", # The name of the golem package containing the app (typically lowercase, no underscore or periods) + pkg_title = "PKG_TITLE", # What the Package Does (One Line, Title Case, No Period) + pkg_description = "PKG_DESC.", # What the package does (one paragraph). + authors = person( + given = "AUTHOR_FIRST", # Your First Name + family = "AUTHOR_LAST", # Your Last Name + email = "AUTHOR@MAIL.COM" # Your email + ), + repo_url = NULL, # The URL of the GitHub repo (optional), + pkg_version = "0.0.0.9000" # The version of the package containing the app ) ``` @@ -132,7 +136,7 @@ Please DO run this line of code, as it sets a series of global options inside `g golem::set_golem_options() ``` -### Set common Files +### Set common Files If you want to use the MIT license, README, code of conduct, lifecycle badge, a news file, etc. @@ -155,7 +159,7 @@ Create a template for tests: golem::use_recommended_tests() ``` -About [tests in a package](https://r-pkgs.org/tests.html). +About [tests in a package](https://r-pkgs.org/testing-basics.html). ### Use Recommended Packages @@ -176,7 +180,7 @@ golem::remove_favicon() golem::use_favicon(path = "path/to/favicon") ``` -Note that you can add an url, and the favicon will be downloaded to the `inst/app/www` folder. +Note that you can add an url, and the favicon will be downloaded to the `inst/app/www` folder. > **Note**: If you are deploying your app with [ShinyProxy](https://www.shinyproxy.io/), your favicon should have the `.png` extension, otherwise it is not going to work. @@ -193,7 +197,7 @@ golem::use_utils_server() ## Try the app -To run the app, launch : +To run the app, launch : ```{r} golem::run_dev() diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index aaa3dd82..367484af 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -1,5 +1,5 @@ --- -title: "Day to Day Dev with {golem}" +title: "b_dev" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -45,7 +45,7 @@ Note that the `{attachment}` package should be installed on your machine. attachment::att_amend_desc() ``` -About [package dependencies](https://r-pkgs.org/namespace.html). +About [package dependencies](https://r-pkgs.org/dependencies-mindset-background.html). ### Add modules @@ -139,7 +139,7 @@ Add more tests to your application: usethis::use_test("app") ``` -About [testing a package](https://r-pkgs.org/tests.html). +About [testing a package](https://r-pkgs.org/testing-basics.html). ## Documentation diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index 4021cd03..fd9b1cb0 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -1,5 +1,5 @@ --- -title: "Deploying Apps with {golem}" +title: "c_deploy" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -18,7 +18,7 @@ $(document).ready(function () { ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>", + comment = "#>", eval = FALSE ) ``` @@ -28,18 +28,15 @@ knitr::opts_chunk$set( When launching the app, you might have noticed that the `dev/run_dev.R` function calls `run_app()`, which has the following structure: ```{r} -run_app <- function( - ... -) { +run_app <- function(...) { with_golem_options( app = shinyApp( - ui = app_ui, + ui = app_ui, server = app_server - ), + ), golem_opts = list(...) ) } - ``` This function might looks a little bit weird, but there's a long story behind it, and you can read more about it [there](https://rtask.thinkr.fr/shinyapp-runapp-shinyappdir-difference/). @@ -48,7 +45,7 @@ But long story short, this combination of `with_golem_options` & `golem_opts = l ```{r} run_app(this = "that") -# And in the app +# And in the app this <- get_golem_options("this") ``` @@ -69,6 +66,10 @@ golem::add_shinyserver_file() ### Docker +#### without using {renv} + + + ```{r} # If you want to deploy via a generic Dockerfile golem::add_dockerfile() @@ -80,3 +81,83 @@ golem::add_dockerfile_shinyproxy() golem::add_dockerfile_heroku() ``` +#### using {renv} + + +#### CASE 1 : you didn't use renv during developpment process + + +> this functions will create a "deploy" folder containing : + +``` +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file + + +```{r} +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy") +``` + +If you would like to use {renv} during developpement, you can init a renv.lock file with + +```{r} +attachment::create_renv_for_dev(dev_pkg = c( + "renv", + "devtools", + "roxygen2", + "usethis", + "pkgload", + "testthat", + "remotes", + "covr", + "attachment", + "pak", + "dockerfiler", + "golem" +)) +``` +an activate {renv} with + +```{r} +renv::activate() +``` + + + + + +#### CASE 2 : you already have a renv.lock file for your project + + +```{r} + +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy", lockfile = "renv.lock") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy", lockfile = "renv.lock") +``` + +> this functions will create a "deploy" folder containing : + +``` +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file diff --git a/vignettes/d_js.Rmd b/vignettes/d_js.Rmd index 72629009..757b3a42 100644 --- a/vignettes/d_js.Rmd +++ b/vignettes/d_js.Rmd @@ -1,5 +1,5 @@ --- -title: "Using {golem} js functions" +title: "d_js" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 5339ec26..2ec72636 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -1,5 +1,5 @@ --- -title: "Using golem config" +title: "e_config" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{config} @@ -19,7 +19,8 @@ x <- file.path( ) unlink(x, TRUE, TRUE) -create_golem(x, package_name = "golex", open = FALSE) +x <- golem::create_golem(x, package_name = "golex", open = FALSE) +old <- setwd(x) knitr::opts_knit$set(root.dir = x) ``` @@ -27,6 +28,10 @@ knitr::opts_knit$set(root.dir = x) library(golem) ``` +```{r echo = FALSE} +old <- setwd(x) +``` + ## About `inst/golem-config.yml` When you start a new `{golem}` application, you'll find a file called `golem-config.yml` in the `inst/` folder. @@ -39,13 +44,17 @@ This config file is based on the [`{config}`](https://github.com/rstudio/config) Here is what the default config file looks like: -```{r echo = FALSE, comment= ""} -cat( - sep = "\n", - readLines( - "inst/golem-config.yml" - ) -) +``` +default: + golem_name: golex + golem_version: 0.0.0.9000 + app_prod: no + +production: + app_prod: yes + +dev: + golem_wd: !expr golem::pkg_path() ``` + default/golem_name, default/golem_version, default/app_prod are usable across the whole life of your golem app: while developing, and also when in production. @@ -60,6 +69,15 @@ These options are globally set with: set_golem_options() ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + The functions reading the options in this config file are: ```{r} @@ -76,6 +94,15 @@ set_golem_wd(".") set_golem_version("0.0.1") ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + ## Using `golem-config` @@ -95,6 +122,7 @@ amend_golem_config( ) ``` + Will result in a `golem-config.yml` file as such: ```{r echo = FALSE, comment= ""} @@ -143,4 +171,8 @@ The idea is also that the `golem-config.yml` file is shareable across `{golem}` ## Note for `{golem}` < 0.2.0 users -If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. \ No newline at end of file +If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. + +```{r echo = FALSE} +setwd(old) +``` diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index d9f6b087..4fcae1c6 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -1,5 +1,5 @@ --- -title: "Extending {golem}" +title: "f_extending_golem" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{f_extending_golem} @@ -10,23 +10,23 @@ vignette: > ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>", + comment = "#>", eval = FALSE ) ``` -This Vignette discusses how you can extend `{golem}`. +This Vignette discusses how you can extend `{golem}`. ## Project Hooks ### What it is -The `create_golem()` function comes with a `project_hook` parameter, a function run just after the `{golem}` project creation. -It can be used to modify the project structure automatically just after its creation. +The `create_golem()` function comes with a `project_hook` parameter, a function run just after the `{golem}` project creation. +It can be used to modify the project structure automatically just after its creation. This allows you to define custom behavior when creating `{golem}` based app, that can be used for: -+ Adding a different front-end template in `R/app_ui.R` ++ Adding a different front-end template in `R/app_ui.R` + Changing configuration options in `inst/golem-config.yml` @@ -36,24 +36,24 @@ This allows you to define custom behavior when creating `{golem}` based app, tha + etc. -### How it works +### How it works -The function is called __after__ the default project has been created, and is executed __in the directory of the created package__. +The function is called __after__ the default project has been created, and is executed __in the directory of the created package__. Here is a rough step by step of what happens when a project is created with `{golem}`: 1. The package name is generated -1. The directory that will receive the package is created +1. The directory that will receive the package is created 1. Default `{golem}` template is copied and pasted 1. R moves to the directory of the newly created project, and runs the `project_hook` function 1. R moves back to the previous directory, and removes comments if needed -1. Project is open +1. Project is open ### Defining your own `project_hook` -The best way to extend `{golem}` project hook functionality is by defining this `project_hook` function in an external package. -This will allow this function to be used inside the `{golem}` creation RStudio project creation widget: +The best way to extend `{golem}` project hook functionality is by defining this `project_hook` function in an external package. +This will allow this function to be used inside the `{golem}` creation RStudio project creation widget: -![](rstudioprojecthook.png) +![](rstudioprojecthook.png) > Note that inside this widget, the function should be explicitely namespaced (pkg::fun) @@ -70,31 +70,29 @@ These parameters might not be used inside your own hook, but __they need to be s Here is an example of a function that can be used to remove the `dev/` folder: ```{r} -no_dev <- function(path, package_name, ...){ - fs::dir_delete("dev") +no_dev <- function(path, package_name, ...) { + fs::dir_delete("dev") } create_golem("ici", project_hook = no_dev) ``` -This one will create a CSS: +This one will create a CSS: ```{r} -new_css <- function(path, package_name, ...){ - +new_css <- function(path, package_name, ...) { css_path <- fs::path_abs("inst/app/www/custom.css") - + fs::file_create(css_path) - - write_there <- function(...){ + + write_there <- function(...) { write(..., file = css_path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") - - cli::cat_bullet("CSS generated") - + + cli_cat_bullet("CSS generated") } create_golem("ici", project_hook = new_css) @@ -105,12 +103,12 @@ create_golem("ici", project_hook = new_css) ### What it is Module templates are a way to define your own content for creating the module script in `R/`. -It allows to extend `{golem}` module template functionality by creating your own content inside the module file. +It allows to extend `{golem}` module template functionality by creating your own content inside the module file. -### How it works +### How it works -The function is called after the file(s) creation. -Here is a step by step of what happens when the `add_module` function is called: +The function is called after the file(s) creation. +Here is a step by step of what happens when the `add_module` function is called: 1. Name is created, and so is the `R/` directory if needed. 1. The fct_ and utils_ files are created if necessary @@ -123,9 +121,9 @@ Here is a step by step of what happens when the `add_module` function is called: ### Defining your own `module_template` -You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. +You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. -Module template functions will receive, by default, the following parameters from `add_modules()`. +Module template functions will receive, by default, the following parameters from `add_modules()`. + `name`: the name of the module + `path`: the path to the file in R/ @@ -137,16 +135,16 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(name, path, export, ...){ - # Define a template that only write the name of the - # module in the file - write(name, path) +my_tmpl <- function(name, path, export, ...) { + # Define a template that only write the name of the + # module in the file + write(name, path) } golem::add_module(name = "custom", module_template = my_tmpl) -my_other_tmpl <- function(name, path, ...){ - # Copy and paste a file from somewhere else - file.copy(..., path) +my_other_tmpl <- function(name, path, ...) { + # Copy and paste a file from somewhere else + file.copy(..., path) } golem::add_module(name = "custom", module_template = my_other_tmpl) ``` @@ -159,10 +157,10 @@ golem::add_module(name = "custom", module_template = my_other_tmpl) JavaScript, CSS and Sass template allow to use your own functions to add code when creating JavaScript files, JavaScript handlers and CSS or Sass files. These templates work inside `add_js_file()`, `add_js_handler()`, `add_css_file()` and `add_sass_file()`. -### How it works +### How it works -The function is called after the file creation. -Here is a step by step of what happens when these functions are called: +The function is called after the file creation. +Here is a step by step of what happens when these functions are called: 1. Name is created 1. The path is generated @@ -174,11 +172,11 @@ Here is a step by step of what happens when these functions are called: ### Defining your own `template` -You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. +You can then define your own function inside your `{golem}` based application, but chances are you will be defining them into your own package. -File template functions will receive, by default, the following parameters from the `add_*()` function. +File template functions will receive, by default, the following parameters from the `add_*()` function. -+ `path`: the path to the file ++ `path`: the path to the file + `...` further arguments These parameters might not be used inside your own function, but __they need to be set in the function skeleton__, for compatibility reasons. @@ -186,13 +184,13 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(path, ...){ - # Define a template that only write the name of the - # module in the file - write_there <- function(...){ +my_tmpl <- function(path, ...) { + # Define a template that only write the name of the + # module in the file + write_there <- function(...) { write(..., file = path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") @@ -200,6 +198,107 @@ my_tmpl <- function(path, ...){ golem::add_css_file(name = "custom", template = my_tmpl) ``` +## Turn on the maintenance mode + +### What it is + +From time to time, you need your application to be unavailble: database update, API changes, etc. +In order to keep your app running but make it unavailable, you can use a __maintenance mode__. +When this maintenance mode is turned on, your application will be paused and a specific page will be displayed to your users. + +`{golem}` comes with a default maintenance page, and you can replace it with you own page. + +### How to set the maintenance mode +The maintenance mode will be turned on whenever the R process detects that the `GOLEM_MAINTENANCE_ACTIVE` environment variable is set to TRUE. + +To visualize the maintenance page locally, you can run the following: + +```{r eval = FALSE} +withr::with_envvar( + c("GOLEM_MAINTENANCE_ACTIVE" = TRUE), + { + golem::run_dev() + } +) +``` + + +or + +```{r eval = FALSE} +Sys.setenv("GOLEM_MAINTENANCE_ACTIVE" = TRUE) +golem::run_dev() +``` + +If you're deploying on Posit Connect, you can set this variable in the setup panel. + +If in command line, you can also do + +``` +export GOLEM_MAINTENANCE_ACTIVE=TRUE && Rscript -e "mygolem::run_app()" +``` + + +### The maintenance page + +`{golem}` comes with a default maintenance page, but you can override it and use your own custom page. + +In order to use your own page, you need to pass either an `html_document` or a `tagList` to the `with_golem_options` function in `run_app.R`: + +```{r} +run_app <- function( + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ...) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server, + onStart = onStart, + options = options, + enableBookmarking = enableBookmarking, + uiPattern = uiPattern + ), + golem_opts = list(...), + maintenance_page = tagList( + fluidRow( + h1("Under maintenance"), + span("Coming soon...") + ) + ) + ) +} +``` + +or: + +```{r} +run_app <- function( + onStart = NULL, + options = list(), + enableBookmarking = NULL, + uiPattern = "/", + ...) { + with_golem_options( + app = shinyApp( + ui = app_ui, + server = app_server, + onStart = onStart, + options = options, + enableBookmarking = enableBookmarking, + uiPattern = uiPattern + ), + golem_opts = list(...), + maintenance_page = shiny::htmlTemplate( + filename = app_sys( + "custom_maintenance_page.html" + ) + ) + ) +} +``` diff --git a/vignettes/z_golem_cheatsheet.Rmd b/vignettes/z_golem_cheatsheet.Rmd index 4c1c9199..10c3967b 100644 --- a/vignettes/z_golem_cheatsheet.Rmd +++ b/vignettes/z_golem_cheatsheet.Rmd @@ -1,5 +1,5 @@ --- -title: "{golem} cheatsheet" +title: "e_golem_cheatsheet" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{e_golem_cheatsheet}