diff --git a/.Rbuildignore b/.Rbuildignore index 628e0b4..51e7ce3 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -4,3 +4,4 @@ ^README\.Rmd$ ^cran-comments\.md$ ^dev$ +^\.github$ diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..a3ac618 --- /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/DESCRIPTION b/DESCRIPTION index 53308c8..2eb7574 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -12,6 +12,7 @@ Imports: cli, fs, here, + plumber, rlang, rstudioapi, usethis, @@ -19,11 +20,11 @@ Imports: yaml Suggests: httr, - plumber, + pkgload, rcmdcheck, testthat (>= 3.0.0), withr Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.1 +RoxygenNote: 7.2.3 diff --git a/NAMESPACE b/NAMESPACE index 9f3d0ae..74bbb95 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -25,6 +25,7 @@ importFrom(rstudioapi,openProject) importFrom(usethis,create_project) importFrom(usethis,use_r) importFrom(usethis,use_test) +importFrom(utils,file.edit) importFrom(utils,getFromNamespace) importFrom(yaml,read_yaml) importFrom(yaml,write_yaml) diff --git a/R/create_pipework.R b/R/create_pipework.R index dc9d94d..35931ab 100644 --- a/R/create_pipework.R +++ b/R/create_pipework.R @@ -36,7 +36,6 @@ create_mariobox <- function( overwrite = FALSE, package_name = basename(path) ) { - # if (check_name) { # cat_rule("Checking package name") # getFromNamespace("check_package_name", "usethis")(package_name) diff --git a/R/dep_port.R b/R/dep_port.R index e580e0d..2286904 100644 --- a/R/dep_port.R +++ b/R/dep_port.R @@ -1,3 +1,5 @@ +#' @noRd +#' @importFrom utils file.edit usethis_use_r <- function( name, pkg = ".", @@ -24,6 +26,8 @@ usethis_use_r <- function( file.edit(name) } } +#' @noRd +#' @importFrom utils file.edit usethis_use_test <- function( name = NULL, pkg = ".", diff --git a/R/new_api.R b/R/new_api.R index 4779991..d8587c9 100644 --- a/R/new_api.R +++ b/R/new_api.R @@ -1,5 +1,5 @@ #' Launch the API -#' +#' #' @param yaml_file path to the yaml #' @importFrom plumber Plumber #' @importFrom yaml read_yaml diff --git a/README.Rmd b/README.Rmd index 5ddeb08..60ae483 100644 --- a/README.Rmd +++ b/README.Rmd @@ -29,6 +29,7 @@ cat_mariobox_yaml <- function(path_pipo) { [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) [![CRAN status](https://www.r-pkg.org/badges/version/mariobox)](https://CRAN.R-project.org/package=mariobox) +[![R-CMD-check](https://github.com/ThinkR-open/mariobox/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/ThinkR-open/mariobox/actions/workflows/R-CMD-check.yaml) [DISCLAIMER] This is a Work In Progress, please use at your own risk. @@ -188,4 +189,4 @@ readLines( "plumber.R" ) ) |> cat(sep = "\n") -``` \ No newline at end of file +``` diff --git a/README.md b/README.md index 16d2fb3..ff59c81 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) [![CRAN status](https://www.r-pkg.org/badges/version/mariobox)](https://CRAN.R-project.org/package=mariobox) +[![R-CMD-check](https://github.com/ThinkR-open/mariobox/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/ThinkR-open/mariobox/actions/workflows/R-CMD-check.yaml) \[DISCLAIMER\] This is a Work In Progress, please use at your own risk. @@ -44,8 +45,8 @@ create_mariobox( open = FALSE ) ── Creating dir ──────────────────────────────────────────────────────────────── - ✔ Creating '/var/folders/5z/rm2h62lj45d332kfpj28c8zm0000gn/T/RtmpP86EN6/pipoac8920906d46/' - ✔ Setting active project to '/private/var/folders/5z/rm2h62lj45d332kfpj28c8zm0000gn/T/RtmpP86EN6/pipoac8920906d46' + ✔ Creating '/tmp/RtmpjFWgLO/pipo1d75618a38d99/' + ✔ Setting active project to '/tmp/RtmpjFWgLO/pipo1d75618a38d99' ✔ Creating 'R/' ✔ Writing a sentinel file '.here' • Build robust paths within your project via `here::here()` @@ -55,14 +56,14 @@ create_mariobox( ── Copying package skeleton ──────────────────────────────────────────────────── ✔ Copied app skeleton ── Done ──────────────────────────────────────────────────────────────────────── - A new mariobox named pipoac8920906d46 was created at /private/var/folders/5z/rm2h62lj45d332kfpj28c8zm0000gn/T/RtmpP86EN6/pipoac8920906d46 . + A new mariobox named pipo1d75618a38d99 was created at /tmp/RtmpjFWgLO/pipo1d75618a38d99 . ``` By default, you’ll find the following structure: ``` r fs::dir_tree(path_pipo) - /var/folders/5z/rm2h62lj45d332kfpj28c8zm0000gn/T//RtmpP86EN6/pipoac8920906d46 + /tmp/RtmpjFWgLO/pipo1d75618a38d99 ├── DESCRIPTION ├── NAMESPACE ├── R @@ -95,7 +96,7 @@ then do a little bit of its magic and parse this YAML to build the health_get: methods: GET path: /health - handler: health + handler: get_health ### Add/Remove endpoints @@ -148,7 +149,7 @@ add_get( ``` r fs::dir_tree(path_pipo) - /var/folders/5z/rm2h62lj45d332kfpj28c8zm0000gn/T//RtmpP86EN6/pipoac8920906d46 + /tmp/RtmpjFWgLO/pipo1d75618a38d99 ├── DESCRIPTION ├── NAMESPACE ├── R @@ -179,7 +180,7 @@ The YALML is automatically updated: health_get: methods: GET path: /health - handler: health + handler: get_health allo_get: methods: GET path: /allo @@ -200,7 +201,7 @@ remove_endpoint( ``` r fs::dir_tree(path_pipo) - /var/folders/5z/rm2h62lj45d332kfpj28c8zm0000gn/T//RtmpP86EN6/pipoac8920906d46 + /tmp/RtmpjFWgLO/pipo1d75618a38d99 ├── DESCRIPTION ├── NAMESPACE ├── R @@ -230,7 +231,7 @@ The YALML is automatically updated: health_get: methods: GET path: /health - handler: health + handler: get_health hey_get: methods: GET path: /hey @@ -256,8 +257,8 @@ your function. This format might seem weird, but the idea is to separate the concerns in the following format: -- METHOD_NAME() will handle the http elements (login, headers..) -- METHOD_NAME_f() will be a standard function returning data. +- METHOD_NAME() will handle the http elements (login, headers..) +- METHOD_NAME_f() will be a standard function returning data. That way, you can handle the data manipulation function just like a plain standard one, test it, interact with it, etc, without having to @@ -279,7 +280,7 @@ can deploy using this file. ``` r build_plumber_file(pkg = path_pipo) - ℹ Loading pipoac8920906d46 + ℹ Loading pipo1d75618a38d99 ✔ plumber.R file created ``` @@ -293,7 +294,7 @@ This will produce the following file: #* @get /health - health + get_health #* @get /hey get_hey diff --git a/dev/dev_history.R b/dev/dev_history.R index 98b5e75..ac423d1 100644 --- a/dev/dev_history.R +++ b/dev/dev_history.R @@ -5,9 +5,14 @@ attachment::att_amend_desc( extra.suggests = c( # Those packages are needed for test on marioboxexample to work - "plumber", "httr", - "yaml", + # Extra dev dependencies + "pkgload", + NULL + ), + pkg_ignore = c( + # Extra dev dependencies + "pkgload", NULL ) ) @@ -18,8 +23,26 @@ devtools::check() devtools::test() + +## Project template + +marioboxexample_path <- normalizePath("inst/marioboxexample/") + +attachment::att_amend_desc( + path = marioboxexample_path +) + +devtools::test( + pkg = marioboxexample_path +) + ### Setup ---------------------------------------------------------------------- .use_r_with_test("create_mariobox") .use_r_with_test("utils") .use_r_with_test("manage_endpoints") + + +## CI + +usethis::use_github_action_check_standard() diff --git a/inst/marioboxexample/DESCRIPTION b/inst/marioboxexample/DESCRIPTION index 941b937..9f7ca9f 100644 --- a/inst/marioboxexample/DESCRIPTION +++ b/inst/marioboxexample/DESCRIPTION @@ -2,19 +2,13 @@ Package: marioboxexample Title: A cunning plumber API Version: 0.0.0.9000 Authors@R: - person(given = "firstname", - family = "lastname", - role = c("aut", "cre"), - email = "your@email.com") + person("firstname", "lastname", , "your@email.com", role = c("aut", "cre")) Description: What the package does (one paragraph). License: What license is it under? Imports: - mariobox, - plumber, - yaml + mariobox Suggests: - httr, testthat Encoding: UTF-8 LazyData: true -RoxygenNote: 7.2.1 +RoxygenNote: 7.2.3 diff --git a/inst/marioboxexample/NAMESPACE b/inst/marioboxexample/NAMESPACE index 644173e..485737d 100644 --- a/inst/marioboxexample/NAMESPACE +++ b/inst/marioboxexample/NAMESPACE @@ -1,5 +1,4 @@ # Generated by roxygen2: do not edit by hand +export(get_health) export(run_api) -importFrom(plumber,Plumber) -importFrom(yaml,read_yaml) diff --git a/inst/marioboxexample/R/get_health.R b/inst/marioboxexample/R/get_health.R index 4a7c8fa..fdbf6a8 100644 --- a/inst/marioboxexample/R/get_health.R +++ b/inst/marioboxexample/R/get_health.R @@ -5,7 +5,7 @@ get_health <- function(req, res) { mariobox::mario_log( method = "GET", - name = "health" + name = "health" ) get_health_f() } diff --git a/inst/marioboxexample/R/run_plumber.R b/inst/marioboxexample/R/run_plumber.R index ef80329..df8bd99 100644 --- a/inst/marioboxexample/R/run_plumber.R +++ b/inst/marioboxexample/R/run_plumber.R @@ -5,10 +5,14 @@ #' #' @export run_api <- function() { + generate_api()$run() +} + +generate_api <- function() { mariobox::new_api( yaml_file = system.file( "mariobox.yml", package = "marioboxexample" ) - )$run() -} + ) +} \ No newline at end of file diff --git a/inst/marioboxexample/man/get_health.Rd b/inst/marioboxexample/man/get_health.Rd new file mode 100644 index 0000000..3bd463a --- /dev/null +++ b/inst/marioboxexample/man/get_health.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_health.R +\name{get_health} +\alias{get_health} +\title{Get the health of the API} +\usage{ +get_health(req, res) +} +\arguments{ +\item{req, res}{HTTP objects} +} +\description{ +Get the health of the API +} diff --git a/inst/marioboxexample/tests/testthat/test-health.R b/inst/marioboxexample/tests/testthat/test-health.R index f3c81f9..f7eb676 100644 --- a/inst/marioboxexample/tests/testthat/test-health.R +++ b/inst/marioboxexample/tests/testthat/test-health.R @@ -1,6 +1,6 @@ test_that("health_f() works", { expect_equal( - health_f(), + get_health_f(), "ok" ) }) diff --git a/inst/marioboxexample/tests/testthat/test-run_plumber.R b/inst/marioboxexample/tests/testthat/test-run_plumber.R index 8254822..e941015 100644 --- a/inst/marioboxexample/tests/testthat/test-run_plumber.R +++ b/inst/marioboxexample/tests/testthat/test-run_plumber.R @@ -1,5 +1,5 @@ -test_that("new_api() works", { - api <- new_api() +test_that("generate_api() works", { + api <- generate_api() expect_s3_class( api, c("Plumber", "Hookable", "R6") diff --git a/mariobox.Rproj b/mariobox.Rproj new file mode 100644 index 0000000..21a4da0 --- /dev/null +++ b/mariobox.Rproj @@ -0,0 +1,17 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source diff --git a/tests/testthat/test-create_pipework.R b/tests/testthat/test-create_pipework.R index 61714ae..7806f35 100644 --- a/tests/testthat/test-create_pipework.R +++ b/tests/testthat/test-create_pipework.R @@ -1,6 +1,5 @@ is_properly_populated_mariobox <- function(path) { - # All files excepts *.Rproj which changes based on the project name expected_files <- c( "DESCRIPTION", @@ -8,9 +7,10 @@ is_properly_populated_mariobox <- function(path) { "LICENSE.md", "dev/run_dev.R", "inst/mariobox.yml", + "man/get_health.Rd", "man/run_api.Rd", "NAMESPACE", - "R/fct_health.R", + "R/get_health.R", "R/run_plumber.R", "tests/testthat.R", "tests/testthat/test-health.R", @@ -26,6 +26,8 @@ is_properly_populated_mariobox <- function(path) { actual_files <- list.files(path, recursive = TRUE) + # browser() + # waldo::compare(sort(expected_files), sort(actual_files)) identical(sort(expected_files), sort(actual_files)) } @@ -47,12 +49,12 @@ path_pkg <- create_mariobox( open = FALSE ) -usethis::with_project(dummy_mariobox_path, { - test_that("create_mariobox() works", { +test_that("create_mariobox() works", { + usethis::with_project(dummy_mariobox_path, { usethis::use_mit_license(copyright_holder = "Babar") check_output <- rcmdcheck::rcmdcheck( - # path = dummy_mariobox_path, + path = dummy_mariobox_path, quiet = TRUE, args = c("--no-manual") ) diff --git a/tests/testthat/test-manage_endpoints.R b/tests/testthat/test-manage_endpoints.R index 788776c..1d363b8 100644 --- a/tests/testthat/test-manage_endpoints.R +++ b/tests/testthat/test-manage_endpoints.R @@ -9,27 +9,28 @@ test_that("Managing endpoints", { ) withr::with_dir(dummy_mariobox_path, { - # Adding endpoint mariobox_yaml_path <- "inst/mariobox.yml" endpoint_name <- "michel" - r_file <- sprintf("R/fct_%s.R", endpoint_name) - test_file <- sprintf("tests/testthat/test-fct_%s.R", endpoint_name) - endpoint_fct_code_str <- sprintf("%s <- function(req){ return(\"ok\")}", endpoint_name) + r_file <- sprintf("R/get_%s.R", endpoint_name) + test_file <- sprintf("tests/testthat/test-get_%s.R", endpoint_name) default_yaml <- list( + metadata = list( + title = "mariobox API" + ), handles = list( - health = list( + health_get = list( methods = "GET", path = "/health", - handler = "health" + handler = "get_health" ) ) ) expected_yaml <- default_yaml - expected_yaml[["handles"]][[endpoint_name]] <- list( + expected_yaml[["handles"]][[paste0(endpoint_name, "_get")]] <- list( methods = "GET", path = paste0("/", endpoint_name), - handler = endpoint_name + handler = paste0("get_", endpoint_name) ) add_endpoint( @@ -39,16 +40,29 @@ test_that("Managing endpoints", { expect_true(file.exists(r_file)) expect_true(file.exists(test_file)) + r_file_text <- readLines(r_file) + # The end point function exists expect_equal( - paste0(readLines(r_file), collapse = ""), - endpoint_fct_code_str + sum( + grepl("^get_michel <- function", r_file_text) + ), + 1 + ) + # The business logic function exists + expect_equal( + sum( + grepl("^get_michel_f <- function", r_file_text) + ), + 1 ) expect_equal( paste0(readLines(test_file), collapse = ""), "test_that(\"multiplication works\", { expect_equal(2 * 2, 4)})" ) + + actual_yaml <- yaml::read_yaml(mariobox_yaml_path) expect_equal( - yaml::read_yaml(mariobox_yaml_path), + actual_yaml, expected_yaml ) @@ -58,13 +72,24 @@ test_that("Managing endpoints", { name = endpoint_name, open = FALSE ), - regexp = sprintf("Endpoint '%s' already created", endpoint_name) + regexp = sprintf("Endpoint '%s' already exists", endpoint_name) ) expect_true(file.exists(r_file)) expect_true(file.exists(test_file)) + r_file_text <- readLines(r_file) + # The end point function exists expect_equal( - paste0(readLines(r_file), collapse = ""), - endpoint_fct_code_str + sum( + grepl("^get_michel <- function", r_file_text) + ), + 1 + ) + # The business logic function exists + expect_equal( + sum( + grepl("^get_michel_f <- function", r_file_text) + ), + 1 ) expect_equal( paste0(readLines(test_file), collapse = ""), @@ -77,7 +102,8 @@ test_that("Managing endpoints", { # Remove endpoint remove_endpoint( - name = endpoint_name + name = endpoint_name, + method = "GET" ) expect_false(file.exists(r_file)) expect_false(file.exists(test_file)) @@ -89,9 +115,10 @@ test_that("Managing endpoints", { # Idempotence: Remove endpoint expect_message( remove_endpoint( - name = endpoint_name + name = endpoint_name, + method = "GET" ), - regexp = sprintf("There is no endpoint '%s' to delete", endpoint_name) + regexp = sprintf("There is no endpoint '%s' with method 'GET' to delete", endpoint_name) ) expect_false(file.exists(r_file)) expect_false(file.exists(test_file))