From 5360375bfa2c2bcdbc1c6633461ea8dc4e1a0ee3 Mon Sep 17 00:00:00 2001 From: olivroy Date: Fri, 14 Jun 2024 09:09:00 -0400 Subject: [PATCH 1/7] Add `filter_detect()` --- NAMESPACE | 1 + R/dplyr-plus.R | 39 ++++++++++++++++++++++++++++++++ man/filter_detect.Rd | 38 +++++++++++++++++++++++++++++++ tests/testthat/test-dplyr-plus.R | 16 ++++++++++++- 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 man/filter_detect.Rd diff --git a/NAMESPACE b/NAMESPACE index 1455f4e..85964ff 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ export(dir_outline) export(distinct_identity) export(extract_cell_value) export(file_outline) +export(filter_detect) export(filter_identity) export(filter_if_any) export(filter_if_any_identity) diff --git a/R/dplyr-plus.R b/R/dplyr-plus.R index 6815951..59a6a9f 100644 --- a/R/dplyr-plus.R +++ b/R/dplyr-plus.R @@ -254,6 +254,45 @@ filter_if_any <- function(.data, ..., .by = NULL, .keep_new_var = FALSE) { i = "See {.help dplyr::filter} for more information on how it works." )) } + +#' Filter rows by pattern +#' +#' Shortcut for [dplyr::filter()] and [stringr::str_detect()] +#' +#' @inheritParams dplyr::mutate +#' @inheritParams dplyr::if_any +#' @inheritParams base::grepl +#' +#' @return A data frame with relocated columns at first +#' @export +#' +#' @examples +#' # don't specify column +#' dplyr::band_members |> +#' filter_detect("Beatles") +#' # specify columns +#' dplyr::band_members |> +#' filter_detect("Beatles", band) +filter_detect <- function(.data, pattern, .cols = dplyr::everything(), ignore.case = TRUE) { + rlang::check_required(pattern) + res <- dplyr::filter( + .data, + dplyr::if_any( + .cols = {{ .cols }}, + \(x) grepl(pattern, x, ignore.case = ignore.case) + ) + ) + n_matches <- purrr::map_int(res, \(x) sum(grepl(pattern, x, ignore.case = ignore.case))) + n_matches <- n_matches[n_matches > 0] + + if (length(n_matches) > 0L) { + n_matches <- sort(n_matches) + res <- res |> + dplyr::relocate(dplyr::all_of(n_matches)) + } + res +} + #' Elegant wrapper around filter and pull #' #' It can be very useful when trying to extract a value from somewhere, and you diff --git a/man/filter_detect.Rd b/man/filter_detect.Rd new file mode 100644 index 0000000..4cb19d8 --- /dev/null +++ b/man/filter_detect.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dplyr-plus.R +\name{filter_detect} +\alias{filter_detect} +\title{Filter rows by pattern} +\usage{ +filter_detect(.data, pattern, .cols = dplyr::everything(), ignore.case = TRUE) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{pattern}{character string containing a \link[base]{regular expression} + (or character string for \code{fixed = TRUE}) to be matched + in the given character vector. Coerced by + \code{\link[base]{as.character}} to a character string if possible. If a + character vector of length 2 or more is supplied, the first element + is used with a warning. Missing values are allowed except for + \code{regexpr}, \code{gregexpr} and \code{regexec}.} + +\item{.cols}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Columns to transform. +You can't select grouping columns because they are already automatically +handled by the verb (i.e. \code{\link[dplyr:summarise]{summarise()}} or \code{\link[dplyr:mutate]{mutate()}}).} + +\item{ignore.case}{if \code{FALSE}, the pattern matching is \emph{case + sensitive} and if \code{TRUE}, case is ignored during matching.} +} +\value{ +A data frame with relocated columns at first +} +\description{ +Shortcut for \code{\link[dplyr:filter]{dplyr::filter()}} and \code{\link[stringr:str_detect]{stringr::str_detect()}} +} +\examples{ +dplyr::band_members |> + filter_detect("Beatles") +} diff --git a/tests/testthat/test-dplyr-plus.R b/tests/testthat/test-dplyr-plus.R index 9ef332e..2d8ab1c 100644 --- a/tests/testthat/test-dplyr-plus.R +++ b/tests/testthat/test-dplyr-plus.R @@ -28,7 +28,7 @@ test_that("slice_group_sample() works as expected", { ) }) -test_that("`filter_if_any()` works as expected", { +test_that("filter_if_any() works as expected", { expect_equal( dplyr::starwars |> dplyr::mutate(v1 = birth_year > 10, .by = gender) |> @@ -42,6 +42,20 @@ test_that("`filter_if_any()` works as expected", { ) }) +test_that("filter_detect() works as expected", { + expect_gt( + dplyr::band_members |> + filter_detect("Beatles") |> + nrow(), + 0 + ) + expect_equal( + mtcars |> + filter_detect("Beatles") |> + nrow(), + 0 + ) +}) test_that("filter_if_any() errors correctly when using `by` instead of `.by`", { skip("Not ready") From a3e88c3cb141cca26190bfff2f1dd44121af9edc Mon Sep 17 00:00:00 2001 From: olivroy <52606734+olivroy@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:21:25 -0400 Subject: [PATCH 2/7] Update _pkgdown.yml ci skip --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 67a1cd9..d861e63 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -41,6 +41,7 @@ reference: - count_pct - case_if_any - filter_if_any + - filter_detect - extract_cell_value - slice_group_sample - na_if2 From 8fe0dd9d629ff99c027f608176e20c5de5c44981 Mon Sep 17 00:00:00 2001 From: olivroy Date: Thu, 20 Jun 2024 19:05:49 -0400 Subject: [PATCH 3/7] Fixup `filter_detect()` --- R/dplyr-plus.R | 2 +- TODO.R | 1 + tests/testthat/test-dplyr-plus.R | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/R/dplyr-plus.R b/R/dplyr-plus.R index 59a6a9f..51bd667 100644 --- a/R/dplyr-plus.R +++ b/R/dplyr-plus.R @@ -286,7 +286,7 @@ filter_detect <- function(.data, pattern, .cols = dplyr::everything(), ignore.ca n_matches <- n_matches[n_matches > 0] if (length(n_matches) > 0L) { - n_matches <- sort(n_matches) + n_matches <- sort(names(n_matches)) res <- res |> dplyr::relocate(dplyr::all_of(n_matches)) } diff --git a/TODO.R b/TODO.R index a36306f..41e591d 100644 --- a/TODO.R +++ b/TODO.R @@ -26,3 +26,4 @@ # TODO exclude _files from `proj_list()` # TODO rename_files should be less noisy about project name file # TODO add_to_tricks(). when detecting TRICK like complete todo, but not remove line. requires a scheme. moves the item to tricks.md at the correct place. (copy to clipboard is probably enough) +# TODO do the same as readme : don't show x.Rmd and x.md in outline (full path checking required) diff --git a/tests/testthat/test-dplyr-plus.R b/tests/testthat/test-dplyr-plus.R index 2d8ab1c..2c4f7f1 100644 --- a/tests/testthat/test-dplyr-plus.R +++ b/tests/testthat/test-dplyr-plus.R @@ -55,6 +55,12 @@ test_that("filter_detect() works as expected", { nrow(), 0 ) + # Reorder based on number of matches. + expect_named( + mtcars |> + filter_detect("Beatles"), + c("band", "name") + ) }) test_that("filter_if_any() errors correctly when using `by` instead of `.by`", { From 5d7fcf5d30fe1441eeb089f2d31f8e7647289868 Mon Sep 17 00:00:00 2001 From: olivroy Date: Wed, 26 Jun 2024 08:43:44 -0400 Subject: [PATCH 4/7] Look in .Rbuildignore when renaming files. Will still need to escape things to work properly --- NEWS.md | 1 + R/files-conflicts.R | 7 ++++--- R/rename.R | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9b68038..70ce068 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,6 +19,7 @@ that will passed on to `proj_list()` ## Fixes +* `rename_files2()` now looks in `.Rbuildignore` to see if some files should be replaced. * Improved regex in `link_gh_issue()`. * `file_outline()` now recognize `describe()` test calls. diff --git a/R/files-conflicts.R b/R/files-conflicts.R index c3175d4..c56d826 100644 --- a/R/files-conflicts.R +++ b/R/files-conflicts.R @@ -20,9 +20,10 @@ check_referenced_files <- function(path = ".", quiet = FALSE) { # TODO insert in either proj_outline, or rename_file if (path == "." || fs::is_dir(path)) { - path <- fs::dir_ls(path = path, recurse = TRUE, regexp = "\\.(R|md|ml)$") - path <- fs::path_filter(path = path, regexp = "_files|tests/testthat|_book|_freeze", invert = TRUE) # need to do this in 2 places - } else if (fs::path_ext(path) %in% c("R", "yml", "yaml", "Rmd", "md", "qmd", "Rmarkdown")) { + # FIXME in Rbuilignore, change `^_pkgdown\.yml$` to `_pkgdown.yml` to make sure it works + path <- fs::dir_ls(path = path, recurse = TRUE, regexp = "\\.(R|md|ml|builignore)$", all = TRUE, type = "file") + path <- fs::path_filter(path = path, regexp = "_files|tests/testthat|_book|_freeze|.github", invert = TRUE) # need to do this in 2 places + } else if (fs::path_ext(path) %in% c("R", "yml", "yaml", "Rmd", "md", "qmd", "Rmarkdown", "gitignore", "Rbuildignore")) { path <- path } else { cli::cli_abort("Wrong specification.") diff --git a/R/rename.R b/R/rename.R index 1352692..baffe7d 100644 --- a/R/rename.R +++ b/R/rename.R @@ -92,6 +92,7 @@ rename_files2 <- function(old, related_files <- fs::dir_ls(regexp = paste0(basename_remove_ext(old), "\\."), recurse = TRUE) related_files <- fs::path_filter(related_files, "_snaps/|_book/|_files|_freeze|renv/", invert = TRUE) related_files <- setdiff(related_files, old) + # remove project name from conflicts. related_files <- stringr::str_subset(related_files, proj_name, negate = TRUE) if (length(related_files) > 0) { @@ -132,8 +133,8 @@ rename_files2 <- function(old, regex_friendly <- paste0("to {.val ", regex_friendly, "}") # avoid searching in generated files and tests/testthat files - files_to_look_into <- fs::dir_ls(regexp = "ya?ml$|md$|R$", type = "file", recurse = TRUE) - files_to_look_into <- fs::path_filter(files_to_look_into, regexp = "_files|tests/testthat|_book/|_freeze/|renv/", invert = TRUE) # need to do elsewhere too + files_to_look_into <- fs::dir_ls(regexp = "ya?ml$|md$|R$|gitignore|Rbuildignore", type = "file", recurse = TRUE, all = TRUE) + files_to_look_into <- fs::path_filter(files_to_look_into, regexp = "_files|tests/testthat|_book/|_freeze/|renv/|.github/|.git/", invert = TRUE) # need to do elsewhere too n_file_names_conflicts <- solve_file_name_conflict( files = files_to_look_into, regex = regexp_to_search_for_in_files, From d11e7d6534f0626abffcdb5ad86500dd9832a4ce Mon Sep 17 00:00:00 2001 From: olivroy Date: Wed, 26 Jun 2024 08:44:15 -0400 Subject: [PATCH 5/7] Strip index.html in browse_pkg --- R/browse-pkg.R | 2 ++ tests/testthat/_snaps/escape-inline-markup.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/R/browse-pkg.R b/R/browse-pkg.R index bfcbf00..454e8ac 100644 --- a/R/browse-pkg.R +++ b/R/browse-pkg.R @@ -58,6 +58,8 @@ browse_pkg <- function(package = NULL, if (length(pkgdown) > 1) { pkgdown <- stringr::str_subset(pkgdown, package) } + # Remove trailing index.html from site + pkgdown <- sub("index.html$", "", pkgdown) } cran_home <- suppressMessages(usethis::browse_cran(package)) diff --git a/tests/testthat/_snaps/escape-inline-markup.md b/tests/testthat/_snaps/escape-inline-markup.md index 7ab50f9..e9a30eb 100644 --- a/tests/testthat/_snaps/escape-inline-markup.md +++ b/tests/testthat/_snaps/escape-inline-markup.md @@ -1,4 +1,4 @@ -# escape_markup() works +# escape_markup() works with { Code escape_markup("i{gt_var} in {{gt_var}} in gt_var in {.file {gt_var}}.") From f2bda349b6f40a91c8fc81864130184aff0a736e Mon Sep 17 00:00:00 2001 From: olivroy Date: Wed, 26 Jun 2024 08:45:39 -0400 Subject: [PATCH 6/7] Fix `proj_file()` to only have files, not directory to make sure no errors happen in `file_outline()` --- R/outline.R | 4 +++- R/proj-list.R | 2 +- TODO.R | 2 -- tests/testthat/test-dplyr-plus.R | 13 +++++++------ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/R/outline.R b/R/outline.R index 6df596d..21058b1 100644 --- a/R/outline.R +++ b/R/outline.R @@ -154,7 +154,7 @@ file_outline <- function(path = active_rs_doc(), "i" = "Run {.run [{.fn proj_file}](reuseme::proj_file(\"{pattern}\"))} to search in file names too." ) } else { - msg <- "Empty outline." + msg <- c("{.path {path}}","Empty outline.") } cli::cli_inform(msg) return(invisible()) @@ -249,9 +249,11 @@ dir_outline <- function(path = ".", pattern = NULL, dir_tree = FALSE, alpha = FA file_list_to_outline <- exclude_example_files(file_list_to_outline) } + # TODO expand this to apply to most generated files if (any(grepl("README.Rmd", file_list_to_outline))) { file_list_to_outline <- stringr::str_subset(file_list_to_outline, "README.md", negate = TRUE) } + if (dir_tree) { cli::cli_h2("Here are the non-R files of {.file {path}}") regexp_exclude <- paste( diff --git a/R/proj-list.R b/R/proj-list.R index ebff806..1ee5303 100644 --- a/R/proj-list.R +++ b/R/proj-list.R @@ -71,7 +71,7 @@ proj_file <- function(file = NULL, path = active_rs_proj(), pattern = NULL) { } file_exts <- c("R", "qmd", "Rmd", "md", "Rmarkdown") file_exts_regex <- paste0("*.", file_exts, "$", collapse = "|") - possible_files <- fs::dir_ls(proj_path, regexp = file_exts_regex, recurse = TRUE) + possible_files <- fs::dir_ls(proj_path, regexp = file_exts_regex, recurse = TRUE, type = "file") possible_files <- fs::path_filter(possible_files, regexp = file) if (length(possible_files) > 1L) { # exclude these files if multiple matches diff --git a/TODO.R b/TODO.R index 41e591d..34004a7 100644 --- a/TODO.R +++ b/TODO.R @@ -20,10 +20,8 @@ # TODO [outline] todos in qmd file inside html comment # TODO reframe more than one issue. nw drive # TODO [delete] generated files -# TODO [proj_file] to accesss data (return the path in this case?) # TODO [check_referenced_files] doesn't check for {.file R/file.R} # TODO browse_pkg should open by default if no vignettes are found, because there is not much to do in the R-session. # TODO exclude _files from `proj_list()` # TODO rename_files should be less noisy about project name file # TODO add_to_tricks(). when detecting TRICK like complete todo, but not remove line. requires a scheme. moves the item to tricks.md at the correct place. (copy to clipboard is probably enough) -# TODO do the same as readme : don't show x.Rmd and x.md in outline (full path checking required) diff --git a/tests/testthat/test-dplyr-plus.R b/tests/testthat/test-dplyr-plus.R index 2c4f7f1..06206a1 100644 --- a/tests/testthat/test-dplyr-plus.R +++ b/tests/testthat/test-dplyr-plus.R @@ -49,18 +49,19 @@ test_that("filter_detect() works as expected", { nrow(), 0 ) + # Reorder based on number of matches. + expect_named( + dplyr::band_members |> + filter_detect("Beatles"), + c("band", "name") + ) + # no match expect_equal( mtcars |> filter_detect("Beatles") |> nrow(), 0 ) - # Reorder based on number of matches. - expect_named( - mtcars |> - filter_detect("Beatles"), - c("band", "name") - ) }) test_that("filter_if_any() errors correctly when using `by` instead of `.by`", { From c842edddd49d3b97bbcedfd160a831fdb3aef66b Mon Sep 17 00:00:00 2001 From: olivroy Date: Wed, 26 Jun 2024 08:46:02 -0400 Subject: [PATCH 7/7] Increment version number to 0.0.2.9005 --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4a89f78..e1a4ff5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: reuseme Title: Collections of Utility Functions to Work Across Projects -Version: 0.0.2.9004 +Version: 0.0.2.9005 Authors@R: person("Olivier", "Roy", , "olivierroy71@hotmail.com", role = c("aut", "cre")) Description: Allows you to browse current projects, rename files safely,