library(testthat)
library(yaml)
library(glue)
# Load already included functions if relevant
pkgload::load_all(export_all = FALSE)

save_att_params() : save attachment parameter configuration as a yaml file

This function will save a list of attachment parameters to a yaml file. It is used to allow the function att_amend_desc() to be executed with previously saved parameters. By default, the save parameters are stored in a yaml file named dev/config_attachment.yaml at the root of the package.

#' save_att_params
#'
#' @param param_list list A named list of all parameters to save
#' @param path_to_yaml character The path to the yaml file
#' @param overwrite logical Whether to overwrite the yaml file if it already exists
#' 
#' @importFrom yaml write_yaml
#' @return character The path to the yaml file
#' @noRd
save_att_params <- function(
    param_list,
    path_to_yaml = "dev/config_attachment.yaml",
    overwrite = FALSE
    ) {

  # Check each name in list corresponds to a parameter name
  att_param_names <- names(formals(att_amend_desc))
  input_names <- names(param_list)
  all_inputs_are_params <- all(input_names %in% att_param_names)
  if (isFALSE(all_inputs_are_params)){
    bad_names <- input_names[!input_names %in% att_param_names]
    stop(paste0("Unexpected parameters to save : ", paste0(bad_names, collapse = " ; ")))
  }

  # Create dir if missing
  dir_yaml <- normalizePath(dirname(path_to_yaml), mustWork = FALSE)
  if (!dir.exists(dir_yaml)) {
    dir.create(dir_yaml)
    add_build_ignore(basename(dir_yaml))
    }

  # Write params to yaml
  yaml_exists <- file.exists(path_to_yaml)

  if (isTRUE(yaml_exists & !overwrite)) {
    stop("yaml file already exists and overwriting is not permitted")
  } else {
    write_yaml(
      x = param_list,
      file = path_to_yaml,
      indent.mapping.sequence = TRUE
    )
    message("Saving attachment parameters to yaml config file")
  }

  return(path_to_yaml)
}
# create a list of parameters and tmp file name
parameter_list <- list(
  pkg_ignore = c("remotes", "i"),
  extra.suggests = c("testthat", "rstudioapi")
)
yaml_path <- paste0(tempfile(pattern = "save_att"), ".yaml")

# save params
save_att_params(param_list = parameter_list,
                path_to_yaml = yaml_path)

yaml::read_yaml(yaml_path)
# rstudioapi::navigateToFile(yaml_path)

# clear created yaml file
unlink(yaml_path)
test_that("save_att_params works", {

  # Test error if incorrect list is provided
  expect_error(object = save_att_params(param_list =
                                          list(notanattparam = 3,
                                               pkg_ignore = c("remotes"),
                                               anotherbadparam = c("testthat")
                                               )
                                        ),
               regexp = "Unexpected parameters to save : notanattparam ; anotherbadparam")

  # Create tmp dir
  tmpdir <- tempfile(pattern = "att")
  dir.create(tmpdir)
  path_to_yaml <- file.path(tmpdir, "config_attachment.yaml")

  # Setup list of att_amend_desc() params
  param_list <- list(
    pkg_ignore = c("remotes", "i", "usethis", "rstudioapi", "renv",
                   "gitlab", "git", "local", "find.rscript", "bioc"),
    extra.suggests = c("testthat", "rstudioapi", "renv", "lifecycle"),
    dir.t = "",
    normalize = FALSE
    )

  # Run function to save params in yaml in tmp folder
  expect_message(
    yaml_config <- save_att_params(
      param_list = param_list,
      path_to_yaml = path_to_yaml
    ), regexp = "Saving attachment parameters to yaml config file"
  )

  # Test that yaml file is created
  expect_true(file.exists(yaml_config))

  # Test that reading yaml file restore the correct list of parameters
  config_data <- yaml::read_yaml(yaml_config)
  expect_equal(
    object = config_data,
    expected = param_list
    )

  # Test overwriting error
  expect_error(object = save_att_params(param_list = param_list,
                               path_to_yaml = path_to_yaml,
                               overwrite = FALSE),
               regexp = "yaml file already exists and overwriting is not permitted"
               )

  # Test that yaml file is updated after overwriting
  new_parameter_list <- list(
    pkg_ignore = c("remotes", "i", "usethis", "rstudioapi"),
    extra.suggests = c("testthat", "rstudioapi", "lifecycle", "git", "renv")
  )

  yaml_config <- save_att_params(
    param_list = new_parameter_list,
    path_to_yaml = path_to_yaml,
    overwrite = TRUE
  )

  config_data <- yaml::read_yaml(yaml_config)
  expect_equal(
    object = config_data,
    expected = new_parameter_list
    )

  # Test clearing yaml with no params
  empty_parameter_list <- list()

  yaml_config <- save_att_params(
    param_list = empty_parameter_list,
    path_to_yaml = path_to_yaml,
    overwrite = TRUE
  )

  config_data <- yaml::read_yaml(yaml_config)
  expect_equal(
    object = config_data,
    expected = empty_parameter_list
    )

  # clean
  unlink(tmpdir, recursive = TRUE)

})

load_att_params() : export att_amend_desc parameter config from yaml

This function will try to read saved config for the att_amend_desc() function. If the config is present, it will provide the list of saved parameters. Otherwise, it will raise an error. The loaded parameter config will be show to the user by a message.

#' load_att_params
#' 
#' @param path_to_yaml character The path to the yaml file
#' 
#' @importFrom yaml read_yaml
#' @importFrom glue glue
#' 
#' @return list A named list of att_amend_desc parameters
#' 
#' @noRd
load_att_params <- function(
    path_to_yaml = "dev/config_attachment.yaml",
    verbose = FALSE
    ){

  # check yaml file exist
  if (isFALSE(file.exists(path_to_yaml))){
    stop(glue("The att_amend_desc() config file {path_to_yaml} does not exist"))
  }

  # read yaml
  param_list <- read_yaml(file = path_to_yaml)

  # Check each name in list corresponds to a parameter name
  att_param_names <- names(formals(att_amend_desc))
  input_names <- names(param_list)
  all_inputs_are_params <- all(input_names %in% att_param_names)
  if (isFALSE(all_inputs_are_params)){
    bad_names <- input_names[!input_names %in% att_param_names]
    stop(paste0("Unexpected parameters in config : ", paste0(bad_names, collapse = " ; ")))
  }

  # Show parameters used from config
  if (isTRUE(verbose)) {
    message("att_amend_desc() parameter loaded are : \n",
            paste0(
              glue("{names(param_list)} = {param_list}"),
              collapse = "\n"
            )
    )
  }

  # return parameters
  return(param_list)

}
# create a list of parameters and tmp file name
parameter_list <- list(
  pkg_ignore = c("remotes", "i"),
  extra.suggests = c("testthat", "rstudioapi")
)
yaml_path <- paste0(tempfile(pattern = "save_att"), ".yaml")

# save params
save_att_params(param_list = parameter_list,
                path_to_yaml = yaml_path)

# read yaml file
config_params <- load_att_params(path_to_yaml = yaml_path)

# clear created yaml file
unlink(yaml_path)
test_that("load_att_params works", {

  # create a list of parameters and tmp file name
  parameter_list <- list(
    pkg_ignore = c("remotes", "i"),
    extra.suggests = c("testthat", "rstudioapi")
  )
  yaml_path <- paste0(tempfile(pattern = "save_att"), ".yaml")

  # test error for non-existing file
  expect_error(object = load_att_params(yaml_path),
              regexp = glue::glue("The .* does not exist"))


  # save params
  save_att_params(param_list = parameter_list,
                  path_to_yaml = yaml_path)

  # test correct list is returned
  result <- load_att_params(yaml_path)
  expect_equal(
    object = result,
    expected = parameter_list
    )

  # test message is returned
  expect_message(object = load_att_params(yaml_path, verbose = TRUE),
                 regexp = "att_amend_desc\\(\\) parameter loaded are : \npkg_ignore = c\\(\"remotes\", \"i\"\\)\nextra.suggests = c\\(\"testthat\", \"rstudioapi\"\\)")

  # add wrong param to yaml
  write(x = "randomparam:\n- randomvalue\n",
        file = yaml_path,
        append=TRUE
        )

  # test error for incorrect param names
  expect_error(object = load_att_params(yaml_path),
               regexp = "Unexpected parameters in config : randomparam")

  # clear created file
  unlink(yaml_path)

})

att_amend_desc() use saved config

#' Compare input from the user to config file or default inputs
#'
#' @param local_att_params List of parameters called by the user
#' @inheritParams att_amend_desc
#'
#' @noRd

compare_inputs_load_or_save <- function(path.c, local_att_params, use.config, update.config) {

    if (isTRUE(use.config) & file.exists(path.c)) {
      # reassign input value to saved parameters
      saved_att_params <- load_att_params(path_to_yaml = path.c)
      # Check if different from config and as defaults
      diff_config <- lapply(names(local_att_params), function(x) {setdiff(local_att_params[x], saved_att_params[x])})
      diff_default <- lapply(names(local_att_params), function(x) {setdiff(local_att_params[x], formals(att_amend_desc)[x])})

      if (any(lengths(diff_config) != 0) & any(lengths(diff_default) != 0)) {
        stop(c("Params in your `att_amend_desc()` and the one in the config file are different. ",
             "Please choose among `update.config = TRUE` or `use.config = FALSE`"))
      }

      params_to_load <- saved_att_params
      # for (param_name in names(saved_att_params)){
      #   assign(param_name, saved_att_params[[param_name]])
      # }
      # message(c("Documentation parameters were restored from attachment config file.\n"))
    } else if (isTRUE(update.config) | !file.exists(path.c)) {
      # save current parameters to yaml config - overwrite if already exist
      save_att_params(
        param_list = local_att_params,
        path_to_yaml = path.c,
        overwrite = TRUE)

      params_to_load <- NULL
    } else {
      message("attachment config file was not updated. Parameters used this time won't be stored.")

      params_to_load <- local_att_params
    }

    return(params_to_load)
  }
# att_amend_desc use saved config ----
test_that("att_amend_desc can create, use and update config file", {
  # Copy package in a temporary directory
  tmpdir <- tempfile("dummyamend")
  dir.create(tmpdir)
  file.copy(system.file("dummypackage",package = "attachment"), tmpdir, recursive = TRUE)
  dummypackage <- file.path(tmpdir, "dummypackage")

  expect_false(dir.exists(file.path(dummypackage, "dev")))

  withr::with_dir(dummypackage, {
    # create a first config file
    att_amend_desc(
      # path = dummypackage, # default to "."
      # update.config = FALSE, # default
      # path.c = "dev/config_attachment.yaml" # default
    )
  })

  # Create if not exists
  expect_true(dir.exists(file.path(dummypackage, "dev")))
  expect_true(file.exists(file.path(dummypackage, "dev", "config_attachment.yaml")))
  # with default params

  yaml_config <- readLines(file.path(dummypackage, "dev", "config_attachment.yaml"))
  expect_equal(object = yaml_config,
               expected = c(
                 #paste0("path: ", dummypackage), # no path stored
                 "path.n: NAMESPACE", "path.d: DESCRIPTION", "dir.r: R", "dir.v: vignettes",
                 "dir.t: tests", "extra.suggests: ~", "pkg_ignore: ~", "document: yes",
                 "normalize: yes", "inside_rmd: no", "must.exist: yes", "check_if_suggests_is_installed: yes"
               ))

  # Do not overwrite if not update.config and not default params
  withr::with_dir(dummypackage, {
    expect_error(
      att_amend_desc(
        # path = dummypackage, # default to "."
        extra.suggests = c("ggplot2"),
        document = FALSE,
        check_if_suggests_is_installed = FALSE,
        update.config = FALSE, # default
        # path.c = file.path(dummypackage, "dev", "config_attachment.yaml") " default
      ), 
      regexp = "Params in your `att_amend_desc\\(\\)` and the one in the config file are different. Please choose among `update.config = TRUE` or `use.config = FALSE`"
    )
  })

    # Do not overwrite if not update.config
  withr::with_dir(dummypackage, {
    expect_message(
      att_amend_desc(
        # path = dummypackage, # default to "."
        extra.suggests = c("ggplot2"),
        document = FALSE,
        check_if_suggests_is_installed = FALSE,
        use.config = FALSE,
        update.config = FALSE
        # path.c = file.path(dummypackage, "dev", "config_attachment.yaml") " default
      ), 
      regexp = "Parameters used this time won't be stored"
    )
  })
  # config not changed, parameters not used
  yaml_config <- readLines(file.path(dummypackage, "dev", "config_attachment.yaml"))
  expect_equal(object = yaml_config,
               expected = c(
                 #paste0("path: ", dummypackage), # no path stored
                 "path.n: NAMESPACE", "path.d: DESCRIPTION", "dir.r: R", "dir.v: vignettes",
                 "dir.t: tests", "extra.suggests: ~", "pkg_ignore: ~", "document: yes",
                 "normalize: yes", "inside_rmd: no", "must.exist: yes", "check_if_suggests_is_installed: yes"
               ))

  # overwrite config file with new non-default parameters
  withr::with_dir(dummypackage, {
    expect_message(
      att_amend_desc(
        # path = dummypackage, # default to "."
        extra.suggests = c("ggplot2"),
        document = FALSE,
        check_if_suggests_is_installed = FALSE,
        update.config = TRUE,
        # path.c = file.path(dummypackage, "dev", "config_attachment.yaml") " default
      ),
      regexp = "'update.config' was set to TRUE, hence, 'use.config' was forced to FALSE"
    )
  })
  yaml_config <- readLines(file.path(dummypackage, "dev", "config_attachment.yaml"))
  expect_equal(object = yaml_config,
               expected = c(
                 "path.n: NAMESPACE", "path.d: DESCRIPTION", "dir.r: R", "dir.v: vignettes",
                 "dir.t: tests", "extra.suggests: ggplot2", "pkg_ignore: ~", "document: no",
                 "normalize: yes", "inside_rmd: no", "must.exist: yes", "check_if_suggests_is_installed: no"
               ))

  # remove non-default edits, without updating config
  expect_message(object = att_amend_desc(path = dummypackage, use.config = FALSE),
                 regexp = "1 package\\(s\\) removed: ggplot2.")

  # re-run with saved config
  expect_message(
    object = att_amend_desc(
      path = dummypackage,
      use.config = TRUE
    ),
    regexp = "1 package\\(s\\) added: ggplot2")
  desc_file <- readLines(file.path(dummypackage, "DESCRIPTION"))
  expect_equal(desc_file[grep("Suggests: ", desc_file) + 1], "    ggplot2,")

  # error when trying to use and update config at the same time
  expect_message(
    object =att_amend_desc(
      path = dummypackage,
      use.config = TRUE,
      update.config = TRUE),
    regexp = "'update.config' was set to TRUE, hence, 'use.config' was forced to FALSE"
  )

  # Clean after
  unlink(dummypackage, recursive = TRUE)
})
# Run but keep eval=FALSE to avoid infinite loop
# Execute in the console directly
fusen::inflate(flat_file = "dev/flat_save_att_params.Rmd",
               vignette_name = NA, # "Save attachment config",
               overwrite = TRUE,
               document = TRUE, # included parameters no
               check = FALSE)


ThinkR-open/attachment documentation built on June 15, 2025, 1:24 a.m.