Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
249fd30
Refactor to offer a suggestion if there is a close match, don't print…
PietrH Jun 30, 2026
7ec0ef0
Add multi value support, only notify on actually missing values
PietrH Jun 30, 2026
99eb1a3
Adapt tests to not check on message.
PietrH Jun 30, 2026
237665a
This line is never run, sylistic fluff
PietrH Jun 30, 2026
408cb1c
Test for the suggestion
PietrH Jun 30, 2026
3faeacf
Merge branch '587-get_bibliography' into refactor-check_value
PietrH Jun 30, 2026
1bc3a8f
Add function family : otherwise pkgdown will complain!
PietrH Jun 30, 2026
48a1cd7
devtools::document()
PietrH Jun 30, 2026
55e9e0d
Hardcode the citation to avoid OS differences
PietrH Jul 2, 2026
c64ad11
declare utils
PietrH Jul 2, 2026
86a2fcf
Style
PietrH Jul 2, 2026
aad60e6
Multiple missing values are possible
PietrH Jul 2, 2026
ef663d4
Switch to absolute transformation distance over relative
PietrH Jul 2, 2026
3d992d6
Test for case where there are multiple typo's in a single entry
PietrH Jul 2, 2026
7b5327c
Use vector support so we can handle multiple value suggestions
PietrH Jul 2, 2026
c799441
Update sorting to work for multiple values of x: most likely candidat…
PietrH Jul 2, 2026
873f30e
Test on a vector for x
PietrH Jul 2, 2026
58f50b1
Adjust test so it doesn't hit suggestion threshold
PietrH Jul 2, 2026
f026e15
Add variable for magic number: maximum allowed levenshtein distance f…
PietrH Jul 2, 2026
1431445
Add more detailed documentation for more complex behaviour
PietrH Jul 2, 2026
ce6af95
Style
PietrH Jul 2, 2026
5d8f52a
Add reference to inspiration.
PietrH Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions R/get_bibliography.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#' first row contains the citation for the ETN platform, the second row the
#' citation for the etn R package, and subsequent rows contain citations for
#' the animal and acoustic projects present in the input data.frame.
#' @family access functions
#' @export
#'
#' @examplesIf interactive() && etn:::credentials_are_set()
Expand Down
109 changes: 79 additions & 30 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@

#' Check input value against valid values
#'
#' This function checks if the input value(s) `x` are present in the valid
#' values `y`. If any value in `x` is not found in `y`, an error is thrown. If
#' `lowercase` is set to `TRUE`, the case of the values will be ignored during
#' the comparison, and the returned values will be converted to lowercase.
#'
#' A suggestion will be offered when a value is not found, based on the
#' Levenshtein distance to the valid values.
#'
#' This function was inspired by `rlang:::stop_arg_match()`.
#'
#' @param x Value(s) to test.
#' `NULL` values will automatically pass.
#' @param y Value(s) to test against.
#' @param name Name of the parameter.
#' @param lowercase If `TRUE`, the case of `x` and `y` values will ignored and
#' `x` values will be returned lowercase.
#' @param max_dist Maximum Levenshtein distance to consider a value a typo.
#' This variable is used to offer suggestions when a typo is suspected.
#' @returns Error or (lowercase) `x` values.
#' @family helper functions
#' @noRd
check_value <- function(x, y, name = "value", lowercase = FALSE) {
check_value <- function(x, y, name = "value", lowercase = FALSE, max_dist = 3) {
# Remove NA from valid values
y <- y[!is.na(y)]

Expand All @@ -20,18 +32,54 @@ check_value <- function(x, y, name = "value", lowercase = FALSE) {
x <- tolower(x)
y <- tolower(y)
}

# Check value(s) against valid values
assertthat::assert_that(
all(x %in% y), # Returns TRUE for x = NULL
msg = glue::glue(
"Can't find {name} `{x}` in: {y}",
x = glue::glue_collapse(x, sep = "`, `", last = "` and/or `"),
y = glue::glue_collapse(y, sep = ", ", width = 300)
if (all(x %in% y)) {
return(x)
}
missing_values <- x[!x %in% y]
# If the value is not found, check if it's a typo by calculating the
# Levenshtein distance.
distances <- utils::adist(missing_values, y) |>
# Transpose the matrix so we can get the minimum distance per candidate
t() |>
as.data.frame() |>
purrr::set_names(missing_values)
# If any candidate string is less 3 transformations away from the reference,
# we can assume it's a typo and suggest it to the user.
candidates_col <- cli::cli_vec(y, list("vec-trunc" = 5))
if (any(purrr::map_lgl(distances, \(dist_for_value) {
any(dist_for_value <= max_dist)
}))) {
# We need to repeat the candidates so we can get the minimum distance
# for each missing value
closest_match <-
rep(y, length(missing_values))[purrr::map_int(distances, which.min)]
# If there are many candidates, truncate the list to 5 items for the error
# message.
Comment thread
PietrH marked this conversation as resolved.
cli::cli_abort(
"Can't find {.var {name}}: {.val {missing_values}} in: {.or {.str {candidates_col}}}.
Did you mean {.strong {.val {closest_match}}}?",
class = "etn_value_not_found_suggest",
call = rlang::caller_env()
)
)

return(x)
} else {
# Sort the references so the closest matches are mentioned. Only show 5
# members.
candidates_col <- purrr::map(distances, \(dist_for_value){
y[order(as.vector(dist_for_value))]
}) |>
# Convert into a vector and truncate for the error message
purrr::reduce(rbind) |>
c() |>
# Don't repeat yourself
unique() |>
cli::cli_vec(list("vec-trunc" = 5))
cli::cli_abort(
"Can't find {.var {name}}: {.val {missing_values}} in: {.or {.str {candidates_col}}}.",
class = "etn_value_not_found",
call = rlang::caller_env()
)
}
}

#' Get credentials from environment variables, or set them manually
Expand Down Expand Up @@ -183,7 +231,8 @@ localdb_is_available <- function() {
select_protocol <- function() {
# ALlow overwriting of protocol logic by environmental variable
user_selected_protocol <- Sys.getenv("ETN_PROTOCOL",
unset = "no_protocol_set")
unset = "no_protocol_set"
)
if (user_selected_protocol != "no_protocol_set") {
return(user_selected_protocol)
}
Expand Down Expand Up @@ -239,8 +288,8 @@ remove_html_tags <- function(x) {
#' environment variables.
#' @family helper functions
#' @noRd
credentials_are_set <- function(){
nzchar(Sys.getenv("ETN_USER")) && nzchar(Sys.getenv("ETN_PWD"))
credentials_are_set <- function() {
nzchar(Sys.getenv("ETN_USER")) && nzchar(Sys.getenv("ETN_PWD"))
}

#' Get the citation for the etn package as plain text
Expand All @@ -253,19 +302,19 @@ credentials_are_set <- function(){
#'
#' @family helper functions
#' @noRd
etn_citation <- function(){
utils::citation("etn") |>
format(style = "text") |>
# Remove linebreaks that may have been introduced based on console width
stringr::str_squish() |>
# Remove markup: emphasis
stringr::str_remove_all(stringr::fixed("_")) |>
# Remove markup: links
stringr::str_remove_all("[<>]") |>
# Only mention doi once, so remove the doi: string
stringr::str_remove("doi:.*?(?= )") |>
# Finally, get rid of any superflous whitespace
stringr::str_squish()
etn_citation <- function() {
authors <-
"Huybrechts P, Desmet P, Govaert S, Oldoni D, Van Hoey S"
title <-
"etn: Access Data from the European Tracking Network"

doi_url <- "https://doi.org/10.5281/zenodo.15235747"

version_str <- paste("R package version", utils::packageVersion("etn"))

docs_url <- "https://inbo.github.io/etn/"

sprintf("%s. %s. %s, %s, %s.", authors, title, doi_url, version_str, docs_url)
}

# WRAPPER FUNCTIONS ----
Expand Down Expand Up @@ -318,13 +367,13 @@ NULL
# Checking this on every call would slow down the package.
get_etnservice_version <<-
memoise::memoise(get_etnservice_version,
cache = cachem::cache_mem(max_age = 60 * 15)
cache = cachem::cache_mem(max_age = 60 * 15)
)
# Memoisation: only validate the login credentials every 15 minutes.
validate_login <<-
memoise::memoise(validate_login,
cache = cachem::cache_mem(max_age = 60 * 15)
)
cache = cachem::cache_mem(max_age = 60 * 15)
)
}

#' Expand columns
Expand Down
1 change: 1 addition & 0 deletions man/get_acoustic_deployments.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_acoustic_detections.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_acoustic_projects.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_acoustic_receivers.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_animal_projects.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_animals.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions man/get_bibliography.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_cpod_projects.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_package.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/get_tags.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/testthat/_snaps/get_package/bibliography.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
item,type,citation
ETN,data platform,"Reubens, J., Aarestrup, K., Abecasis, D. et al. The European tracking network through time: united efforts to advance aquatic conservation in Europe. Anim Biotelemetry (2026). https://doi.org/10.1186/s40317-026-00475-z"
etn,R package,"Huybrechts P, Desmet P, Govaert S, Oldoni D, Van Hoey S (2026). etn: Access Data from the European Tracking Network. https://doi.org/10.5281/zenodo.15235747, R package version 3.0.0.9000, https://inbo.github.io/etn/."
etn,R package,"Huybrechts P, Desmet P, Govaert S, Oldoni D, Van Hoey S. etn: Access Data from the European Tracking Network. https://doi.org/10.5281/zenodo.15235747, R package version 3.0.0.9000, https://inbo.github.io/etn/."
2014_demer,animal project,"Pauwels, I.; Baeyens, R.; De Maerteleire, N.; Desmet, P.; Gelaude, E.; Milotic, T.; Pieters, S.; Reyserhove, L.; Robberechts, K.; Verhelst, P.; Coeck, J.; (2020): 2014_DEMER - Acoustic telemetry data for four fish species in the Demer river (Belgium). Marine Data Archive. https://doi.org/10.14284/432"
albert,acoustic project,
demer,acoustic project,
Expand Down
37 changes: 26 additions & 11 deletions tests/testthat/test-check_value.R
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
test_that("check_value() returns error for incorrect values", {
expect_error(
check_value("invalid", c("a", "b")),
"Can't find value `invalid` in: a, b",
fixed = TRUE
class = "etn_value_not_found"
)
expect_error(
check_value(c("a", "invalid"), c("a", "b")),
"Can't find value `a` and/or `invalid` in: a, b",
fixed = TRUE
class = "etn_value_not_found"
)
expect_error(
check_value("invalid", c("a", "b"), name = "param_name"),
"Can't find param_name `invalid` in: a, b",
fixed = TRUE
class = "etn_value_not_found"
)
expect_error(
check_value(c("missing", "misval"), c("a", "b", "c")),
class = "etn_value_not_found"
)
})

Expand All @@ -29,9 +30,8 @@ test_that("check_value() returns x for correct values", {

test_that("check_value() can ignore case", {
expect_error(
check_value("A", c("a", "B")),
"Can't find value `A` in: a, B",
fixed = TRUE
check_value("AAAA", c("aaaa", "BBBB")),
class = "etn_value_not_found"
)
expect_identical(
check_value("A", c("a", "B"), lowercase = TRUE),
Expand All @@ -51,7 +51,22 @@ test_that("check_value() can handle NA in reference", {

expect_error(
check_value("invalid", c("A", NA, "C")),
"Can't find value `invalid` in: A, C",
fixed = TRUE
class = "etn_value_not_found"
)
})

test_that("check_value() can offer a suggestion for typos", {
expect_error(
check_value("seeschelde", c("demer", "dijle", "zeeschelde")),
class = "etn_value_not_found_suggest"
)
})

test_that("check_value() offers suggestions for multiple typos", {
expect_error(
check_value(x = c("2000_Loire", "seeschelde"),
y = c("2011_Loire", "zeeschelde")
),
class = "etn_value_not_found_suggest"
)
})
Loading