diff --git a/DESCRIPTION b/DESCRIPTION index 0911389..592f447 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -42,4 +42,4 @@ Suggests: Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.1.9000 diff --git a/NAMESPACE b/NAMESPACE index 175ae06..1455f4e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ S3method(print,outline_report) export(active_rs_doc) export(active_rs_doc_copy) export(active_rs_doc_delete) +export(active_rs_doc_nav) export(arrange_identity) export(browse_pkg) export(case_if_any) diff --git a/NEWS.md b/NEWS.md index 830a931..8b5b42d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,16 @@ * `proj_list()` / `proj_switch()` no longer opens a nested project if looking for `"pkgdown"`, `"testthat"`, etc. +* `active_rs_doc_nav()` is a new function to navigate to files pane location. + +`active_rs_doc_copy()` now accepts copying md and qmd files too and no longer allows renaming Rprofile. + +* `proj_file()` is better. + +* Local GitHub issues show better in outline. + +* `file_outline()` detects better plot titles and section titles. + # reuseme 0.0.2 * `complete_todo()` no longer deletes the full line. It only deletes what it says it deletes (#27). diff --git a/R/browse-pkg.R b/R/browse-pkg.R index da80eb0..bfcbf00 100644 --- a/R/browse-pkg.R +++ b/R/browse-pkg.R @@ -88,9 +88,9 @@ browse_pkg <- function(package = NULL, "reference" ) - pkgdown <- stringr::str_remove(pkgdown, "/$") + pkgdown <- sub("/$", "", pkgdown) pkgdown_tabs_url <- paste0(pkgdown, "/", pkgdown_tabs, "/") - if (stringr::str_detect(pkgdown, "r-lib.org|tidyverse.org|tidymodels.org") && !stringr::str_detect(pkgdown, "github.com")) { + if (grepl("r-lib.org|tidyverse.org|tidymodels.org", pkgdown) && !grepl("github.com", pkgdown, fixed = TRUE)) { # known packages with dev enabled. pkgdown_tabs_url[1] <- paste0(pkgdown, "/dev/news") } diff --git a/R/dplyr-plus.R b/R/dplyr-plus.R index 7b68c75..6815951 100644 --- a/R/dplyr-plus.R +++ b/R/dplyr-plus.R @@ -240,10 +240,10 @@ filter_if_any <- function(.data, ..., .by = NULL, .keep_new_var = FALSE) { if (all(purrr::map_lgl(variables[, 1:n_var], is.logical))) { res <- dplyr::filter(variables, dplyr::if_any(.cols = seq_len(n_var)), .by = {{ .by }}) - if (!.keep_new_var) { - res <- res[-seq_len(n_var)] - } else { + if (.keep_new_var) { cli::cli_warn("You have modified the original data") + } else { + res <- res[-seq_len(n_var)] } return(res) @@ -310,14 +310,13 @@ extract_cell_value <- function(data, var, filter, name = NULL, length = NULL, un if (unique) { res2 <- unique_named(res2) } - if (!is.null(length)) { + + if (!is.null(length) && !rlang::has_length(res2, length)) { # TODO use `check_length()` when implemented. r-lib/rlang#1618 - if (!rlang::has_length(res2, length)) { - cli::cli_abort(c( - "Expected an output of {length}", - "Got an output of {length(res2)}" - )) - } + cli::cli_abort(c( + "Expected an output of {length}", + "Got an output of {length(res2)}" + )) } res2 diff --git a/R/escape-inline-markup.R b/R/escape-inline-markup.R index f33e140..372e1e2 100644 --- a/R/escape-inline-markup.R +++ b/R/escape-inline-markup.R @@ -28,12 +28,12 @@ escape_markup <- function(x) { is_markup_okay <- is_bracket & stringr::str_detect(x, "\\{\\.[:alpha:]+[^\\{]") & !local_var_ref_in_markup & !is_markup_incorrect(x) if (all(is_markup_okay) && !any(local_var_ref_in_markup)) { - x[is_left_bracket & !is_bracket] <- stringr::str_replace_all(x[is_left_bracket & !is_bracket], "\\{", "{{") - x[is_right_bracket & !is_bracket] <- stringr::str_replace_all(x[is_right_bracket & !is_bracket], "\\}", "}}") + x[is_left_bracket & !is_bracket] <- gsub("{", "{{", x[is_left_bracket & !is_bracket], fixed = TRUE) + x[is_right_bracket & !is_bracket] <- gsub("}", "}}", x[is_right_bracket & !is_bracket], fixed = TRUE) return(x) } # replace fn arg {fn}(arg) -> fn({arg}) - valid_r_variable_regex <- "[:alpha:][[:alpha:]\\_\\d]+" + valid_r_variable_regex <- "\\.?[:alpha:][[:alpha:]\\_\\d]+" x <- stringr::str_replace_all( x, paste0("\\{(", valid_r_variable_regex, ")\\}\\("), @@ -59,16 +59,20 @@ escape_markup <- function(x) { # ) # replace variables + x <- replace_r_var(x) + x[is_left_bracket & !is_bracket] <- gsub("{", "{{", x[is_left_bracket & !is_bracket], fixed = TRUE) + x[is_right_bracket & !is_bracket] <- gsub("}", "}}", x[is_right_bracket & !is_bracket], fixed = TRUE) - x <- replace_r_var(x) + # whisker replacement {{{ vignette_title }}} in usethis by vignette_title + x <- stringr::str_replace_all(x, "\\{\\{?\\{?(\\s?[^\\}]+) \\}?\\}?\\}", "\\1") - x[is_left_bracket & !is_bracket] <- stringr::str_replace_all(x[is_left_bracket & !is_bracket], "\\{", "{{") - x[is_right_bracket & !is_bracket] <- stringr::str_replace_all(x[is_right_bracket & !is_bracket], "\\}", "}}") + # make `{` and `}` work + x <- stringr::str_replace_all(x, c("`\\{`" = "`{{`", "`\\}`" = "`}}`")) if (any(stringr::str_detect(x, "\\{{3,10}"))) { # more than 3 { - cli::cli_abort("internal errror. Did not transform string correctly.") + rlang::abort(c("internal error. Did not transform string correctly.", x)) } x } @@ -84,7 +88,7 @@ escape_markup <- function(x) { #' is_markup_incorrect("{.file {gt}}") is_markup_incorrect <- function(x) { # no match of single { or } - valid_r_variable_regex <- "[:alpha:][[:alpha:]\\_\\d]+" + valid_r_variable_regex <- "\\.?[:alpha:][[:alpha:]\\_\\d]+" stringr::str_detect(x, pattern = paste0("(? "{{gt_var}} in {{gt_var}} in gt_var in {.file {gt_var}}." +#' # last instance taken care of with escape_markup with a different strategy #' escape_markup("{gt_var} in {{gt_var}} in gt_var in {.file {gt_var}}.") #' #> "{{gt_var}} in {{gt_var}} in gt_var in {.file gt_var}." NULL diff --git a/R/files-conflicts.R b/R/files-conflicts.R index 511a1d9..c3175d4 100644 --- a/R/files-conflicts.R +++ b/R/files-conflicts.R @@ -118,7 +118,7 @@ solve_file_name_conflict <- function(files, regex, dir = ".", extra_msg = NULL, cli::cli_inform } # Remove duplicated Found x references - which_bullet_to_replace <- stringr::str_subset(extra_msg, "Found references to", negate = TRUE) + which_bullet_to_replace <- stringr::str_subset(extra_msg, stringr::fixed("Found references to"), negate = TRUE) # possibly just move up our # extra_msg[i] <- f_inform(c( @@ -152,8 +152,8 @@ get_referenced_files <- function(files) { stringr::str_subset(pattern = "file.[(exist)|(delete)]|glue\\:\\:glue|unlink", negate = TRUE) |> # don't detect where we test for existence of path or construct a path with glue stringr::str_subset(pattern = "[(regexp)|(pattern)]\\s\\=.*\".*[:alpha:]\"", negate = TRUE) |> # remove regexp = a.pdf format stringr::str_subset(pattern = "grepl?\\(|stringr|g?sub\\(", negate = TRUE) |> # avoid regexp - stringr::str_subset(pattern = "nocheck", negate = TRUE) |> # remove nocheck and unlink statements (refers to deleted files anywa) - stringr::str_subset("\"") |> + stringr::str_subset(pattern = stringr::fixed("nocheck"), negate = TRUE) |> # remove nocheck and unlink statements (refers to deleted files anywa) + stringr::str_subset(stringr::fixed("\"")) |> stringr::str_trim() |> stringr::str_extract_all("\"[^\"]+\"") |> unlist() |> @@ -163,7 +163,7 @@ get_referenced_files <- function(files) { stringr::str_subset(pattern = "tmp|temp", negate = TRUE) |> # remove common file names that are not very nice stringr::str_subset(pattern = "https?", negate = TRUE) |> # doesn't check for files read online. stringr::str_subset(pattern = "\\@.+\\.", negate = TRUE) |> # email addresses or containing @ - stringr::str_subset(pattern = "_fichiers/", negate = TRUE) |> # manually remove false positive + stringr::str_subset(pattern = stringr::fixed("_fichiers/"), negate = TRUE) |> # manually remove false positive stringr::str_subset(pattern = "\n", negate = TRUE) |> # remove things with line breaks stringr::str_subset(pattern = "^\\.[:alpha:]{1,4}$", negate = TRUE) |> # remove reference to only file extensions stringr::str_subset(pattern = "\\.\\d+$", negate = TRUE) |> # remove 0.000 type diff --git a/R/markup.R b/R/markup.R index 1a4f90d..464cc7a 100644 --- a/R/markup.R +++ b/R/markup.R @@ -9,14 +9,14 @@ #' #' Afterwards, we use [markup_href()] to create a cli link #' @param x A string, usually lines of files that contains issue numbers. -#' +#' @param home_repo Optional, but if supplied, will be stripped. #' @return A markdown link linked issue to GitHub issue #' @export #' @keywords internal #' @family inline markup internal helpers #' @examples #' link_gh_issue(c("We really need rstudio/gt#1469 to be fixed.")) -link_gh_issue <- function(x) { +link_gh_issue <- function(x, home_repo = NULL) { # Return early if no issue pattern is detected. regex_gh_issue <- common_regex("gh_issue") @@ -37,11 +37,73 @@ link_gh_issue <- function(x) { regex_gh_issue, paste0("[\\1#\\2](https://github.com/\\1/issues/\\2)") ) + if (!is.null(home_repo)) { + x_changed <- gsub( + paste0(home_repo, "#"), + "#", + x_changed + ) + } x[has_gh_issue] <- x_changed x } +# transforms (#xx) to (org/repo#xx) +link_local_gh_issue <- function(x, repo_home) { + gsub( + # max 99999 issues. + pattern = "\\((#\\d{1,5})\\)", + paste0("(", repo_home, "\\1)"), + x + ) +} +find_pkg_org_repo <- function(dir_common = NULL, file = NULL) { + rlang::local_interactive(FALSE) + withr::local_options("usethis.quiet" = TRUE) + if (!is.null(dir_common)) { + pkg_path <- tryCatch( + rprojroot::find_package_root_file(path = dir_common), + error = function(e) { + # cli::cli_inform("Could not detect path.") + NULL + } + ) + if (is.null(pkg_path)) { + return(NULL) + } + gh_url <- tryCatch( + usethis::browse_github(basename(pkg_path)), + error = function(e) { + # TODO possibly look into checking desc::desc_get("BugReports", "~/path/to/DESCRIPTION") + # cli::cli_abort("didn't find a way to do what is required.", parent = e) + NULL + } + ) + if (is.null(gh_url)) { + return(NULL) + } + org_repo_found <- sub(".+github.com/|.+gitlab.com/", "", gh_url) + return(org_repo_found) + } + + if (!is.null(file)) { + pkg_path <- withCallingHandlers( + rprojroot::find_package_root_file(path = file), + error = function(e) { + # cli::cli_inform("Could not detect path.") + NULL + } + ) + gh_url <- usethis::browse_github(basename(pkg_path)) + org_repo_found <- sub(".+github.com/|.+gitlab.com/", "", gh_url) + } else { + org_repo_found <- NULL + } + if (is.null(org_repo) && is.null(org_repo_found)) { + cli::cli_abort("No way to discover URL.") + } +} #' Create a cli href with a markdown link #' #' Transforms `[text](url)` -> `{.href [text](url)}` diff --git a/R/open.R b/R/open.R index 67d3b42..71f098d 100644 --- a/R/open.R +++ b/R/open.R @@ -39,10 +39,10 @@ open_rs_doc <- function(path, line = -1L, col = -1L, move_cursor = TRUE) { #' @name open_rs_doc #' @export active_rs_doc <- function() { - if (!interactive() && !rstudioapi::isAvailable()) { + if (!interactive() && !is_rstudio()) { return("Non-existing doc") } - if (!rstudioapi::isAvailable()) { + if (!is_rstudio()) { cli::cli_abort("Not in RStudio.") } unsaved_doc <- tryCatch(rstudioapi::documentPath(), error = function(e) TRUE) @@ -79,20 +79,21 @@ active_rs_doc_copy <- function(new = NULL, ..., old = NULL) { cli::cli_abort("Unsaved document, focus on the saved doc you want to save.") } - if (!fs::path_ext(old) %in% c("R", "qmd", "Rmd")) { - cli::cli_abort("Only R docs for now") + if (!fs::path_ext(old) %in% c("md", "R", "qmd", "Rmd")) { + cli::cli_abort("Only R and md docs for now") } old_path_file <- fs::path_ext_remove(fs::path_file(old)) - if (stringr::str_detect(old, "r-profile|Rprofile")) { + + if (grepl("r-profile|Rprofile", old)) { cli::cli_abort("Attempting to copy Rprofile (focus on the document you want)") } if (is.null(new)) { new_name <- paste0(old_path_file, "-new") } else { - new_name <- stringr::str_remove(new, "\\.R|\\.qmd|\\.Rmd$") + new_name <- sub("\\.R|\\.[Rq]?md$", "", new) } # Hack to ensure file/file.R will be correctly renamed. - new_path <- stringr::str_replace(old, paste0(old_path_file, "\\."), paste0(new_name, ".")) + new_path <- sub(paste0(old_path_file, "\\."), paste0(new_name, "."), old) copied <- file.copy(old, new_path, overwrite = FALSE) if (copied) { @@ -124,7 +125,7 @@ active_rs_doc_copy <- function(new = NULL, ..., old = NULL) { #' @examplesIf FALSE #' active_rs_doc_delete() active_rs_doc_delete <- function() { - if (!rlang::is_interactive() || !rstudioapi::isAvailable()) { + if (!rlang::is_interactive() || !is_rstudio()) { cli::cli_abort(c("Can't delete files in non-interactive sessions.")) } doc <- active_rs_doc() @@ -140,7 +141,7 @@ active_rs_doc_delete <- function() { if (fs::is_dir(elems$full_path)) { cli::cli_abort("Must be a file", .internal = TRUE) } - if (interactive() && rstudioapi::isAvailable()) { + if (interactive() && is_rstudio()) { rstudioapi::documentSave() } cli::cli_inform(c( @@ -211,7 +212,7 @@ active_rs_doc_delete <- function() { parent_dir <- fs::path_file(fs::path_dir(elems$full_path)) if (grepl("^temp", fs::path_file(elems$rel_path)) || - (!parent_dir %in% c("tests", "testthat") && grepl("^test-", fs::path_file(elems$rel_path)))) { + (!parent_dir %in% c("tests", "testthat") && grepl("^test-", fs::path_file(elems$rel_path)))) { reasons_deleting <- c(reasons_deleting, "it has the temp- prefix.") will_delete <- append(will_delete, TRUE) } @@ -256,7 +257,7 @@ active_rs_doc_delete <- function() { cli::cli_inform(c( "v" = "Deleted the active document {.val {elems$rel_path}} because {reasons_deleting}.", # FIXME (upstream) the color div doesn't go all the way r-lib/cli#694 - "i" = paste(cli::col_grey("The deleted file"), "{.path {elems$full_path}}", cli::col_grey("contents are returned invisibly in case you need them.")) + "i" = paste(cli::col_grey("The deleted file"), "{.path {elems$full_path}}", cli::col_grey("contents are returned invisibly in case you need them.")) )) contents <- readLines(elems$full_path, encoding = "UTF-8") fs::file_delete(elems$full_path) @@ -361,3 +362,36 @@ normalize_proj_and_path <- function(path, call = caller_env()) { full_path = full_path ) } + +#' Open Files Pane at current document location +#' +#' Easily navigate to active file document. +#' +#' Wrapper around [executeCommand("activateFiles")][rstudioapi::executeCommand()] + +#' [rstudioapi::filesPaneNavigate()] + [rstudioapi::getActiveDocumentContext()] +#' +#' @param path A path to file to navigate to (default active document). +#' +#' @returns NULL, called for its side effects. +#' @export +active_rs_doc_nav <- function(path = active_rs_doc()) { + if (!is_rstudio() || !interactive()) { + cli::cli_abort("Must use in RStudio interactive sessions.") + } + if (is.null(path)) { + cli::cli_abort("Can't navigate to an unsaved file!") + } + if (fs::is_file(path)) { + dir <- fs::path_dir(path) + } else if (fs::is_dir(path)) { + dir <- path + } else { + cli::cli_abort("{.arg path} must be an existing file or directory.") + } + rstudioapi::executeCommand("activateFiles") + rstudioapi::filesPaneNavigate(dir) + cli::cli_inform(c( + "v" = "Navigated to {.path {dir}} in RStudio Files Pane." + )) + invisible() +} diff --git a/R/outline-criteria.R b/R/outline-criteria.R index f7322ab..961cc60 100644 --- a/R/outline-criteria.R +++ b/R/outline-criteria.R @@ -33,7 +33,7 @@ o_is_roxygen_comment <- function(x, file_ext = NULL) { } o_is_todo_fixme <- function(x) { - has_todo <- stringr::str_detect(x, "(?|\\(\\.*\\)"), + is_section_title_source = is_section_title & + stringr::str_detect(content, "[-\\=]{3,}|^\\#'") & + stringr::str_detect(content, "[:alpha:]"), is_todo_fixme = print_todo & o_is_todo_fixme(content) & !o_is_roxygen_comment(content, file_ext) & !is_snap_file, n_leading_hash = nchar(stringr::str_extract(content, "\\#+")), n_leading_hash = dplyr::coalesce(n_leading_hash, 0), + # Make sure everything is second level in revdep/. + n_leading_hash = n_leading_hash + grepl("revdep/", file, fixed = TRUE), is_second_level_heading_or_more = (is_section_title_source | is_section_title) & n_leading_hash > 1, is_cross_ref = stringr::str_detect(content, "docs_links?\\(") & !stringr::str_detect(content, "@param|\\{\\."), - is_function_def = grepl("<- function(", content, fixed = TRUE) & !stringr::str_starts(content, "\\s*#") + is_function_def = grepl("<- function(", content, fixed = TRUE) & !stringr::str_starts(content, "\\s*#"), + is_tab_or_plot_title = o_is_tab_plot_title(content) & !is_section_title & !is_function_def, + is_a_comment_or_code = stringr::str_detect(content, "!=|\\|\\>|\\(\\.*\\)"), ) x <- dplyr::mutate( x, - before_and_after_empty = line == 1 | !nzchar(dplyr::lead(content, default = "")) & !nzchar(dplyr::lag(content)), - .by = file + before_and_after_empty = + line == 1 | !nzchar(dplyr::lead(content, default = "")) & !nzchar(dplyr::lag(content)), + .by = "file" ) x } diff --git a/R/outline.R b/R/outline.R index 599c6af..fc652c5 100644 --- a/R/outline.R +++ b/R/outline.R @@ -90,7 +90,7 @@ file_outline <- function(pattern = NULL, recent_only = FALSE) { # To contribute to this function, take a look at .github/CONTRIBUTING - if (length(path) == 1L && interactive() && rstudioapi::isAvailable()) { + if (length(path) == 1L && rlang::is_interactive() && is_rstudio()) { is_active_doc <- identical(path, active_rs_doc()) } else { is_active_doc <- FALSE @@ -134,8 +134,8 @@ file_outline <- function(pattern = NULL, file_content <- dplyr::bind_rows(file_content, .id = "file") } - suppressMessages( - in_active_project <- tryCatch( + in_active_project <- suppressMessages( + tryCatch( identical(suppressWarnings(proj_get2()), dir_common), error = function(e) FALSE ) @@ -197,7 +197,7 @@ file_outline <- function(pattern = NULL, } # File outline =================== # strip outline element .data$outline = `# Section 1` becomes `Section 1` - file_sections1 <- display_outline_element(file_sections0) + file_sections1 <- display_outline_element(file_sections0, dir_common) # Create hyperlink in console file_sections <- construct_outline_link(file_sections1, is_saved_doc, dir_common, pattern) @@ -215,7 +215,7 @@ file_outline <- function(pattern = NULL, ) file_sections$recent_only <- recent_only - if (any(duplicated(file_sections$outline_el))) { + if (anyDuplicated(file_sections$outline_el) > 0L) { file_sections <- scrub_duplicate_outline(file_sections) } file_sections <- dplyr::relocate( @@ -251,14 +251,14 @@ proj_outline <- function(pattern = NULL, proj = proj_get2(), work_only = TRUE, d )) } - if (!fs::dir_exists(proj)) { # when referring to a project by name. - proj_dir <- proj_list(proj) - } else { + if (fs::dir_exists(proj)) { if (!is_active_proj) { cli::cli_warn("Use {.fn dir_outline} for that.") } proj_dir <- proj + } else { # when referring to a project by name. + proj_dir <- proj_list(proj) } if (!rlang::has_length(proj_dir, 1)) { @@ -310,11 +310,7 @@ dir_outline <- function(pattern = NULL, path = ".", work_only = TRUE, dir_tree = if (recurse && !identical(Sys.getenv("TESTTHAT"), "true")) { # Remove examples from outline and test example files to avoid clutter # examples don't help understand a project. - file_list_to_outline <- fs::path_filter( - file_list_to_outline, - regexp = "testthat/_ref/|example-file", - invert = TRUE - ) + file_list_to_outline <- exclude_example_files(file_list_to_outline) } if (any(grepl("README.Rmd", file_list_to_outline))) { @@ -325,7 +321,7 @@ dir_outline <- function(pattern = NULL, path = ".", work_only = TRUE, dir_tree = fs::dir_tree( path = dir, - regexp = "R/.+|qmd|Rmd|_files|~\\$|*.Rd|_snaps|testthat.R|Rmarkdown|docs/", + regexp = "R/.+|qmd|Rmd|_files|~\\$|*.Rd|_snaps|tests/testthat.R|Rmarkdown|docs/", recurse = recurse, invert = TRUE ) @@ -333,6 +329,28 @@ dir_outline <- function(pattern = NULL, path = ".", work_only = TRUE, dir_tree = file_outline(path = file_list_to_outline, pattern = pattern, work_only = work_only, dir_common = dir, alpha = alpha, recent_only = recent_only) } +exclude_example_files <- function(path) { + # styler tests examples may not work.. + + regexp_exclude <- paste( + "vignettes/test/", # test vignettes + "LICENSE.md", # avoid indexing this. + "tests/(performance-monitor|gt-examples/|testthat/scope-|testthat/assets|testthat/_outline|testthat/testTestWithFailure|testthat/testTest/|testthat/test-parallel/|testthat/test-list-reporter/)", # example files in usethis, pkgdown, reuseme, devtools, etc. + "inst/((rmarkdown/)?templates/|example-file/|examples/rmd/|tutorials/)", # license templates in usethis + "revdep/", # likely don't need to outline revdep/, use dir_outline() to find something in revdep/ + "themes/hugo-theme-console/", # protect blogdown + "vignettes/.+\\.R$", # generated files + "RcppExports.R", + "pkgdown/assets", + sep = "|" + ) + + fs::path_filter( + path, + regexp = regexp_exclude, + invert = TRUE + ) +} # Print method ------------------- #' @export @@ -419,7 +437,16 @@ print.outline_report <- function(x, ...) { base_name <- c(base_name, " ", title_el) } - cli::cli_h3(base_name) + # TRICK need tryCatch when doing something, withCallingHandlers when only rethrowing? + tryCatch( + cli::cli_h3(base_name), + error = function(e) { + # browser() + cli::cli_h3(escape_markup(base_name)) + # print(base_name) + # rlang::abort("Could not parse by cli", parent = e) + } + ) if (recent_only) { if (i %in% is_recently_modified) { @@ -465,7 +492,7 @@ keep_outline_element <- function(.data) { # What to keep in .R files (!is_md & is_section_title_source) | # What to keep anywhere - is_tab_or_plot_title | is_todo_fixme | is_test_name | is_cross_ref | is_function_def # | is_cli_info # TODO reanable cli info + is_tab_or_plot_title | is_todo_fixme | is_test_name | is_cross_ref | is_function_def # | is_cli_info # TODO reanable cli info ) dat$simplify_news <- NULL dat @@ -475,23 +502,28 @@ keep_outline_element <- function(.data) { # Includes removing headings comments # Remove title = # Removing quotes, etc. -display_outline_element <- function(.data) { +display_outline_element <- function(.data, dir_common) { x <- .data - x$outline_el <- purrr::map_chr(x$content, link_gh_issue) # to add link to GitHub. + org_repo <- find_pkg_org_repo(dir_common, unique(x$file)) + if (!is.null(org_repo)) { + x$outline_el <- link_local_gh_issue(x$content, org_repo) + } else { + x$outline_el <- x$content + } + x$outline_el <- purrr::map_chr(x$outline_el, \(x) link_gh_issue(x, org_repo)) # to add link to GitHub. x$outline_el <- purrr::map_chr(x$outline_el, markup_href) x <- dplyr::mutate( x, outline_el = dplyr::case_when( - is_todo_fixme ~ stringr::str_extract(outline_el, "(TODO.+)|(FIXME.+)|(WORK.+)"), - is_test_name ~ stringr::str_extract(outline_el, "test_that\\(['\"](.+)['\"]", group = 1), + is_todo_fixme ~ stringr::str_extract(outline_el, "(TODO.+)|(FIXME.+)|(WORK.+)|(BOOK.+)"), + is_test_name ~ stringr::str_extract(outline_el, "test_that\\(['\"](.+)['\"],\\s?\\{", group = 1), is_cli_info ~ stringr::str_extract(outline_el, "[\"'](.{5,})[\"']") |> stringr::str_remove_all("\""), - is_tab_or_plot_title ~ stringr::str_extract(outline_el, "title = [\"']([^\"]{5,})[\"']", group = 1), - is_chunk_cap_next & !is_chunk_cap ~ stringr::str_remove_all(outline_el, "\\s?\\#\\|\\s+"), + is_tab_or_plot_title ~ stringr::str_extract(outline_el, "title =[^\"']*[\"']([^\"]{5,})[\"']", group = 1), is_chunk_cap_next & !is_chunk_cap ~ stringr::str_remove_all(outline_el, "\\s?\\#\\|\\s+"), is_chunk_cap ~ stringr::str_remove_all(stringr::str_extract(outline_el, "(cap|title)\\:\\s*(.+)", group = 2), "\"|'"), is_cross_ref ~ stringr::str_remove_all(outline_el, "^(i.stat\\:\\:)?.cdocs_lin.s\\(|[\"']\\)$|\""), is_doc_title ~ stringr::str_remove_all(outline_el, "subtitle\\:\\s?|title\\:\\s?|\"|\\#\\|\\s?"), is_section_title & !is_md ~ stringr::str_remove(outline_el, "^\\s{0,4}\\#+\\s+|^\\#'\\s\\#+\\s+"), # Keep inline markup - is_section_title & is_md ~ stringr::str_remove_all(outline_el, "^\\#+\\s+|\\{.+\\}"), # strip cross-refs. + is_section_title & is_md ~ stringr::str_remove_all(outline_el, "^\\#+\\s+|\\{.+\\}|<(a href|img src).+$"), # strip cross-refs. is_function_def ~ stringr::str_extract(outline_el, "(.+)\\<-", group = 1) |> stringr::str_trim(), .default = stringr::str_remove_all(outline_el, "^\\s*\\#+\\|?\\s?(label:\\s)?|\\s?[-\\=]{4,}") ), @@ -570,13 +602,13 @@ define_important_element <- function(.data) { } construct_outline_link <- function(.data, is_saved_doc, dir_common, pattern) { - rs_avail_file_link <- rstudioapi::isAvailable("2023.09.0.375") # better handling after + rs_avail_file_link <- is_rstudio("2023.09.0.375") # better handling after .data <- define_important_element(.data) if (is.null(dir_common) || !nzchar(dir_common)) { dir_common <- "Don't remove anything if not null" } - .data$rs_version <- ifelse(!rstudioapi::isAvailable("2023.12.0.274") && rstudioapi::isAvailable(), ".", "") + .data$rs_version <- ifelse(!is_rstudio("2023.12.0.274") && is_rstudio(), ".", "") .data$has_inline_markup <- dplyr::coalesce(stringr::str_detect(.data$outline_el, "\\{|\\}"), FALSE) .data$is_saved_doc <- is_saved_doc .data <- dplyr::mutate( @@ -625,20 +657,17 @@ construct_outline_link <- function(.data, is_saved_doc, dir_common, pattern) { outline_el2 = dplyr::coalesce(outline_el2, outline_el) ) - .data <- dplyr::mutate(.data, - link = paste0(outline_el2, " {.path ", file, ":", line, "}"), - # rstudioapi::documentOpen works in the visual mode!! but not fully. - file_path = .data$file, - is_saved_doc = .env$is_saved_doc, - - # May have caused CI failure - text_in_link = stringr::str_remove(file_path, as.character(.env$dir_common)) |> stringr::str_remove("^/"), - # decide which is important - style_fun = dplyr::case_match(importance, - "not_important" ~ "cli::style_italic('i')", # cli::style_inverse for bullets - "important" ~ "cli::style_inverse('i')", - .default = NA - ) + .data$link <- paste0(.data$outline_el2, " {.path ", .data$file, ":", .data$line, "}") + # rstudioapi::documentOpen works in the visual mode!! but not fully. + .data$file_path <- .data$file + .data$is_saved_doc <- is_saved_doc + # May have caused CI failure + .data$text_in_link <- sub(as.character(dir_common), "", .data$file_path) + .data$text_in_link <- sub("^/", "", .data$text_in_link) + .data$style_fun <- dplyr::case_match(.data$importance, + "not_important" ~ "cli::style_italic('i')", # cli::style_inverse for bullets + "important" ~ "cli::style_inverse('i')", + .default = NA_character_ ) if (anyNA(.data$style_fun)) { @@ -664,7 +693,17 @@ construct_outline_link <- function(.data, is_saved_doc, dir_common, pattern) { rs_version = NULL, outline_el2 = NULL, condition_to_truncate = NULL, - condition_to_truncate2 = NULL + condition_to_truncate2 = NULL, + style_fun = NULL, + is_saved_doc = NULL, + is_roxygen_comment = NULL, + is_news = NULL, + # I may put it pack + importance = NULL, + # may be useful for debugging + before_and_after_empty = NULL, + # may be useful for debugging + has_inline_markup = NULL ) |> dplyr::filter(is.na(outline_el) | grepl(pattern, outline_el, ignore.case = TRUE)) } diff --git a/R/proj-list.R b/R/proj-list.R index ab2a6bb..acf3168 100644 --- a/R/proj-list.R +++ b/R/proj-list.R @@ -58,10 +58,16 @@ proj_file <- function(file = NULL, proj = NULL, pattern = NULL) { if (fs::is_file(file)) { file_outline(path = file) open_rs_doc(file) + return(invisible(file)) } proj <- proj %||% proj_get2() proj_path <- proj_list(proj) - + file_path <- fs::path(proj_path, file) + if (fs::is_file(file_path)) { + file_outline(path = file_path) + open_rs_doc(file_path) + return(invisible(file_path)) + } 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) diff --git a/R/rename.R b/R/rename.R index 3f82e8f..1352692 100644 --- a/R/rename.R +++ b/R/rename.R @@ -71,7 +71,7 @@ rename_files2 <- function(old, # renaming should only happen in tests or interactive sessions if (action == "rename" && !(rlang::is_interactive() || identical(Sys.getenv("TESTTHAT"), "true"))) { - cli::cli_abort(c("Should only rename files in interactive sessions (or in tests)")) + cli::cli_abort("Should only rename files in interactive sessions.") } is_git <- !isFALSE(tryCatch(rprojroot::find_root_file(criterion = rprojroot::criteria$is_vcs_root), error = function(e) FALSE)) @@ -95,7 +95,7 @@ rename_files2 <- function(old, # remove project name from conflicts. related_files <- stringr::str_subset(related_files, proj_name, negate = TRUE) if (length(related_files) > 0) { - # maybe would need to normalize path. + # TODO verify if path should be normalized. cli::cli_warn(c( "Other files have a similar pattern", "See {.file {related_files}}", @@ -124,7 +124,7 @@ rename_files2 <- function(old, if (renaming_strategy == "object_names") { # Create regex = replace kebab-case by snake_case to verify object names # Then the regex is the union of these. - regex_friendly <- c(basename_remove_ext(old), stringr::str_replace_all(basename_remove_ext(old), "-", "_")) + regex_friendly <- c(basename_remove_ext(old), gsub("-", "_", basename_remove_ext(old), fixed = TRUE)) regex_friendly <- paste0(unique(regex_friendly), collapse = "|") } else { regex_friendly <- ifelse(renaming_strategy %in% c("object_names"), basename_remove_ext(old), old) @@ -216,7 +216,7 @@ compute_conflicts_regex <- function(file, renaming_strategy) { file_name_base <- basename_remove_ext(file) - object_snake_from_file_kebab <- stringr::str_replace_all(file_name_base, "-", "_") + object_snake_from_file_kebab <- gsub("-", "_", file_name_base, fixed = TRUE) if (renaming_strategy == "object_names") { # dat/file-name.csv|file_name diff --git a/R/screenshot.R b/R/screenshot.R index c4c8dbc..71e3106 100644 --- a/R/screenshot.R +++ b/R/screenshot.R @@ -1,198 +1,198 @@ -#' Save the current image in clipboard to png in your active directory -#' -#' @description -#' The screenshot will be saved as `.png` to a directory following these rules -#' 1. In a regular RStudio project (or a Quarto book), it will be saved to a `images/` directory -#' 2. In a package project, it will be saved in a `man/figures` directory -#' 3. In a Quarto Blog project, it will save in the current post's folder. -#' 4. You can always override these defaults by setting `dir` -#' -#' After using the shortcut Win + Shift + S, you can call this function! -#' -#' @details -#' If no file name is supplied, a file named `image0*.png` will be created. -#' The function then prompts you to rename the file with a more expressive name. -#' It will continue the numbering if a file named image exists. -#' -#' Still have to validate if it works on macOS, as it is not clear whether the -#' image goes to the clipboard by default -#' -#' The maximum number of images in a folder is 99. (only padding 2), should be enough. -#' -#' You should not be able to overwrite a screenshot with a generic name, only a -#' named one as it is possible you may require to retake your screenshot. -#' @param file A file name, ideally `-` (kebab-case). (extension ignored) (optional, default is `image.png`) -#' @param proj A project name -#' @param dir A directory (optional), to override the directory rules mentioned in the description. inside `proj`. -#' @return The full image path, invisibly. -#' @export -#' @examples -#' if (FALSE) { -#' # Add an image to the clipboard -#' # Run the following -#' screenshot(file = "my-new-image") -#' } -#' -screenshot <- function(file = NULL, proj = proj_get(), dir = NULL) { - # https://z3tt.github.io/graphic-design-ggplot2/tips-to-improve-your-ggplot-workflow.html#save-ggplot-output-with-the-correct-dimensions - # Could wrap ggsave also - - if (!rlang::is_interactive()) { - cli::cli_warn("Remove {.fn reuseme::screenshot} from scripts. It is only meant to be used interactively.") - return(invisible()) - } - - - check_string(file, allow_null = TRUE) - is_active_proj <- identical(proj, proj_get2()) - - proj_path <- proj_list(proj) - - - if (!rstudioapi::isAvailable()) { - cli::cli_warn("This feature may not work as excepted outside RStudio.") - } - - # Making dir a full path if not in active project. - if (!is.null(dir)) { - dir_rel <- dir - if (!is_active_proj) { - dir <- fs::path(proj_path, dir_rel) - } - - if (!fs::is_dir(dir) || !fs::dir_exists(dir)) { - cli::cli_abort(c(x = "{.arg dir} must be {.code NULL} or a valid directory within {.arg proj}.")) - } - } - - img_dir <- if (!is.null(dir)) { - if (is_active_proj) { - dir - } else { - fs::path(proj_path, dir_rel) - } - } else if (is_pkg(proj_path)) { - "man/figures" - } else if (is_quarto_blog(proj_path)) { - if (is_active_proj) { - get_active_qmd_post(base_path = proj_path) - } else { - cli::cli_abort(c("You are trying to add a screenshot to a Quarto blog.", "Either open the RStudio project or supply {.arg dir} to write a screenshot in the directory.")) - } - } else if (is_active_proj) { - "images" - } else if (!is_active_proj && !is_pkg(proj_path)) { - fs::path(proj_path, "images") - } else { - cli::cli_abort("Not a supported case.") - } - - img_dir_rel <- fs::path_rel(img_dir, start = proj_path) - - - - if (is_quarto_blog(proj_path)) { - file_index_qmd <- fs::path(img_dir, "index.qmd") - if (!fs::file_exists(file_index_qmd)) { - cli::cli_abort("In a Quarto blog, {.arg dir} must be a relative path to a Quarto Post. i.e. index.qmd.") - } - } - - if (!fs::dir_exists(img_dir)) { - cli::cli_abort(c( - x = "The directory where we want to save the image, {img_dir} doesn't exist.", - i = "Run {.run fs::dir_create(\"{img_dir}\")} to create it.", - "Then, rerun {.fun reuseme::screenshot} to save the screenshot" - )) - } - - is_generic_file_name <- is.null(file) - file <- file %||% "image" - file <- fs::path_ext_remove(file) - - if (file == "image") { - files_named_image <- fs::dir_ls( - path = img_dir, - type = "file", - regexp = "image.+png", - recurse = FALSE - ) - - if (rlang::has_length(files_named_image)) { - increment_val <- stringr::str_extract(files_named_image, "image-(\\d{2})", group = 1) - increment <- max(as.numeric(increment_val)) - } else { - increment <- 0 - } - - file <- glue::glue("image-{stringr::str_pad(increment + 1, width = 2, pad = '0')}") - } - img_file <- fs::path(file, ext = "png") - - img_path <- fs::path(img_dir, img_file) - - if (fs::file_exists(img_path) && is_generic_file_name) { - cli::cli_abort("You cannot overrite a screenshot. Please change `file`") - } - - rlang::check_installed("magick") - screen_shot <- tryCatch( - magick::image_read("clipboard:"), - error = function(e) { - cli::cli_abort( - c("x" = "The clipboard must contain an image."), - parent = NA - ) - } - ) - - magick::image_write( - image = screen_shot, - path = img_path, - format = "png", - comment = "screenshot" - ) - - img_path_chr <- as.character(img_path) - img_path_rel_chr <- as.character(fs::path_rel(img_path, proj_path)) - img_file_chr <- as.character(img_file) - img_dir_rel_chr <- as.character(img_dir_rel) - img_dir_chr <- as.character(img_dir) - proj_chr <- as.character(proj) - change_project_command <- "[{proj_chr}](reuseme::proj_switch('{proj_chr}'))" - - bullets <- if (is_active_proj) { - if (length(fs::dir_ls(".", regexp = "qmd|Rmd")) > 0) { - "Use with Quarto, Rmd (source mode) with" - } - } else { - "Use with Quarto, Rmd (source mode) in {.run [{proj_chr}](reuseme::proj_switch('{proj_chr}'))}" - } - - if (is_quarto_blog(proj_path)) { - bullets <- c( - bullets, - # cli bug r-lib/cli#683 - '![]({fs::path_file(img_path_chr)}){{fig-alt="" width="70%"}}' - ) - } else { - bullets <- c( - bullets, - '![]({img_path_rel_chr}){{fig-alt="" width="70%"}}' - ) - } - - - - if (is_generic_file_name) { - bullets <- c( - bullets, - "i" = "Consider using a more precise name", - "reuseme::rename_files2('{img_path_chr}', '{img_dir_chr}/better-name.png', warn_conflicts = 'none')", - "i" = "See {.help reuseme::rename_files2} for details." - ) - } - - cli::cli_inform(bullets) - invisible(img_path_rel_chr) -} +#' Save the current image in clipboard to png in your active directory +#' +#' @description +#' The screenshot will be saved as `.png` to a directory following these rules +#' 1. In a regular RStudio project (or a Quarto book), it will be saved to a `images/` directory +#' 2. In a package project, it will be saved in a `man/figures` directory +#' 3. In a Quarto Blog project, it will save in the current post's folder. +#' 4. You can always override these defaults by setting `dir` +#' +#' After using the shortcut Win + Shift + S, you can call this function! +#' +#' @details +#' If no file name is supplied, a file named `image0*.png` will be created. +#' The function then prompts you to rename the file with a more expressive name. +#' It will continue the numbering if a file named image exists. +#' +#' Still have to validate if it works on macOS, as it is not clear whether the +#' image goes to the clipboard by default +#' +#' The maximum number of images in a folder is 99. (only padding 2), should be enough. +#' +#' You should not be able to overwrite a screenshot with a generic name, only a +#' named one as it is possible you may require to retake your screenshot. +#' @param file A file name, ideally `-` (kebab-case). (extension ignored) (optional, default is `image.png`) +#' @param proj A project name +#' @param dir A directory (optional), to override the directory rules mentioned in the description. inside `proj`. +#' @return The full image path, invisibly. +#' @export +#' @examples +#' if (FALSE) { +#' # Add an image to the clipboard +#' # Run the following +#' screenshot(file = "my-new-image") +#' } +#' +screenshot <- function(file = NULL, proj = proj_get(), dir = NULL) { + # https://z3tt.github.io/graphic-design-ggplot2/tips-to-improve-your-ggplot-workflow.html#save-ggplot-output-with-the-correct-dimensions + # Could wrap ggsave also + + if (!rlang::is_interactive()) { + cli::cli_warn("Remove {.fn reuseme::screenshot} from scripts. It is only meant to be used interactively.") + return(invisible()) + } + + + check_string(file, allow_null = TRUE) + is_active_proj <- identical(proj, proj_get2()) + + proj_path <- proj_list(proj) + + + if (!is_rstudio()) { + cli::cli_warn("This feature may not work as excepted outside RStudio.") + } + + # Making dir a full path if not in active project. + if (!is.null(dir)) { + dir_rel <- dir + if (!is_active_proj) { + dir <- fs::path(proj_path, dir_rel) + } + + if (!fs::is_dir(dir) || !fs::dir_exists(dir)) { + cli::cli_abort(c(x = "{.arg dir} must be {.code NULL} or a valid directory within {.arg proj}.")) + } + } + + img_dir <- if (!is.null(dir)) { + if (is_active_proj) { + dir + } else { + fs::path(proj_path, dir_rel) + } + } else if (is_pkg(proj_path)) { + "man/figures" + } else if (is_quarto_blog(proj_path)) { + if (is_active_proj) { + get_active_qmd_post(base_path = proj_path) + } else { + cli::cli_abort(c("You are trying to add a screenshot to a Quarto blog.", "Either open the RStudio project or supply {.arg dir} to write a screenshot in the directory.")) + } + } else if (is_active_proj) { + "images" + } else if (!is_active_proj && !is_pkg(proj_path)) { + fs::path(proj_path, "images") + } else { + cli::cli_abort("Not a supported case.") + } + + img_dir_rel <- fs::path_rel(img_dir, start = proj_path) + + + + if (is_quarto_blog(proj_path)) { + file_index_qmd <- fs::path(img_dir, "index.qmd") + if (!fs::file_exists(file_index_qmd)) { + cli::cli_abort("In a Quarto blog, {.arg dir} must be a relative path to a Quarto Post. i.e. index.qmd.") + } + } + + if (!fs::dir_exists(img_dir)) { + cli::cli_abort(c( + x = "The directory where we want to save the image, {img_dir} doesn't exist.", + i = "Run {.run fs::dir_create(\"{img_dir}\")} to create it.", + "Then, rerun {.fun reuseme::screenshot} to save the screenshot" + )) + } + + is_generic_file_name <- is.null(file) + file <- file %||% "image" + file <- fs::path_ext_remove(file) + + if (file == "image") { + files_named_image <- fs::dir_ls( + path = img_dir, + type = "file", + regexp = "image.+png", + recurse = FALSE + ) + + if (rlang::has_length(files_named_image)) { + increment_val <- stringr::str_extract(files_named_image, "image-(\\d{2})", group = 1) + increment <- max(as.numeric(increment_val)) + } else { + increment <- 0 + } + + file <- glue::glue("image-{stringr::str_pad(increment + 1, width = 2, pad = '0')}") + } + img_file <- fs::path(file, ext = "png") + + img_path <- fs::path(img_dir, img_file) + + if (fs::file_exists(img_path) && is_generic_file_name) { + cli::cli_abort("You cannot overrite a screenshot. Please change `file`") + } + + rlang::check_installed("magick") + screen_shot <- tryCatch( + magick::image_read("clipboard:"), + error = function(e) { + cli::cli_abort( + c("x" = "The clipboard must contain an image."), + parent = NA + ) + } + ) + + magick::image_write( + image = screen_shot, + path = img_path, + format = "png", + comment = "screenshot" + ) + + img_path_chr <- as.character(img_path) + img_path_rel_chr <- as.character(fs::path_rel(img_path, proj_path)) + img_file_chr <- as.character(img_file) + img_dir_rel_chr <- as.character(img_dir_rel) + img_dir_chr <- as.character(img_dir) + proj_chr <- as.character(proj) + change_project_command <- "[{proj_chr}](reuseme::proj_switch('{proj_chr}'))" + + bullets <- if (is_active_proj) { + if (length(fs::dir_ls(".", regexp = "qmd|Rmd")) > 0) { + "Use with Quarto, Rmd (source mode) with" + } + } else { + "Use with Quarto, Rmd (source mode) in {.run [{proj_chr}](reuseme::proj_switch('{proj_chr}'))}" + } + + if (is_quarto_blog(proj_path)) { + bullets <- c( + bullets, + # cli bug r-lib/cli#683 + '![]({fs::path_file(img_path_chr)}){{fig-alt="" width="70%"}}' + ) + } else { + bullets <- c( + bullets, + '![]({img_path_rel_chr}){{fig-alt="" width="70%"}}' + ) + } + + + + if (is_generic_file_name) { + bullets <- c( + bullets, + "i" = "Consider using a more precise name", + "reuseme::rename_files2('{img_path_chr}', '{img_dir_chr}/better-name.png', warn_conflicts = 'none')", + "i" = "See {.help reuseme::rename_files2} for details." + ) + } + + cli::cli_inform(bullets) + invisible(img_path_rel_chr) +} diff --git a/R/todo.R b/R/todo.R index 650d347..5d3cd99 100644 --- a/R/todo.R +++ b/R/todo.R @@ -63,7 +63,7 @@ use_todo <- function(todo, proj = proj_get2(), code = FALSE) { #' Remove a TODO/WORK/FIXME item from a file #' -#' Function meant to be wrapped as `{.run}` hyperlinks with [file_outline()]. +#' Function meant to be wrapped as `{.run }` hyperlinks with [file_outline()]. #' It basically removes a line from a file. #' #' @param line The line number (a single integer) @@ -82,7 +82,7 @@ complete_todo <- function(line, file, regexp, rm_line = NULL) { check_number_whole(line) line_original <- line # to defer warning. - if (interactive() && rstudioapi::isAvailable()) { + if (interactive() && is_rstudio()) { rstudioapi::documentSaveAll() } warn_change_of_line <- FALSE @@ -126,9 +126,9 @@ complete_todo <- function(line, file, regexp, rm_line = NULL) { if (warn_change_of_line) { cli::cli_warn(c( x = "Could not find {.arg regexp} as expected", - "Could not find {.val {regexp}} at line {line_original}.", - i = "Has the file content changed since you ran this code?", - # needs qty for cli pluralization, but no printing + "The expected pattern {.val {regexp}} was not found at line {line_original}.", + i = "Please verify if the file content has changed or if the pattern needs adjustment.", + # needs cli::qty for pluralization, but no printing "`regexp` was detected in {cli::qty(length(regexp_detection))} line{?s} {regexp_detection}." )) } @@ -209,7 +209,7 @@ compute_path_todo <- function(todo, proj) { if (!is.na(proj_name_in_todo)) { proj <- proj_name_in_todo regex_proj_in_todo <- paste0(proj_name_in_todo, "\\:\\:", "\\s?") - todo[1] <- stringr::str_remove(todo[1], regex_proj_in_todo) + todo[1] <- sub(regex_proj_in_todo, "", todo[1]) } is_active_proj <- tryCatch( @@ -247,11 +247,11 @@ compute_path_todo <- function(todo, proj) { # accepts a single line strip_todo_line <- function(x, only_rm_tag = FALSE) { check_string(x) - if (!stringr::str_detect(x, "TODO|WORK|FIXME")) { + if (!grepl("TODO|WORK|FIXME", x)) { cli::cli_abort("Could not detect a todo tag in x") } if (only_rm_tag) { - x_new <- stringr::str_remove(x, "\\s(TODO|WORK|FIXME)") + x_new <- sub("\\s(TODO|WORK|FIXME)", "", x) } else { x_new <- stringr::str_extract(x, "([^#]+)\\#+", group = 1) if (is.na(x_new)) { diff --git a/R/utils-proj.R b/R/utils-proj.R index b32b746..945f6d3 100644 --- a/R/utils-proj.R +++ b/R/utils-proj.R @@ -112,3 +112,8 @@ get_active_qmd_post <- function(base_path = proj_get(), error_call = caller_env( fs::path_dir(relative_path) } + +# to mock. +is_rstudio <- function(v = NULL) { + rstudioapi::isAvailable(version_needed = v) +} diff --git a/README.Rmd b/README.Rmd index bd1bcd0..baf1f4b 100644 --- a/README.Rmd +++ b/README.Rmd @@ -25,7 +25,7 @@ knitr::opts_chunk$set( The goal of reuseme is to provide utility functions for project management across RStudio projects. -Sometimes, you have to manage multiple things at once, but don't have the time to do edits. +Sometimes, managing multiple projects can be challenging. reuseme also aims to simplify project management on Windows. You may need to switch quickly to a project, add things or browse a certain file if you have some replications across projects. Sometimes, it is hard to do that. reuseme also aims to help me overcome things I don't like on Windows. @@ -49,9 +49,8 @@ reuseme is adapted for a standard workflow, recommended in (find resources) - Anyone working in RStudio (recent version for hyperlink support) - Work with RStudio projects - Your RStudio projects are organized in a centralized location on your computer -- Your RStudio projects are Version controlled with git (optional, but recommended to avoid surprises! No need to be hosted on repositories like GitLab or GitHub) -- You are working on Windows (macOS is supported, but some things were designed on Windows) -- You use machine and human readable paths (i.e. no spaces, special characters) (Tip: don't hesitate to rename your files, it can take away the pain in the long run! +- Your RStudio projects are Version controlled with git (optional, but recommended for avoiding surprises! No need to be hosted on repositories like GitLab or GitHub) +- You use machine and human-readable paths (i.e. no spaces, special characters) (Tip: don't hesitate to rename your files (`reuseme::rename_files2()`), your future self will thank you! To take advantage of reuseme, it is highly recommended to set the following option in your `.Rprofile` @@ -95,7 +94,7 @@ With reuseme, just use the project name! +================================================================================+==================================================================================+======================================================================================================================================+ | Switch to project "cool-project" | `proj_switch(proj = "cool-project")` | `proj_activate(path = "C:/users/long/path/to/cool-project")` | +--------------------------------------------------------------------------------+----------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ -| Write a TODO item in project "cooler-project", while working in "cool-project" | `use_todo(todo = "I need to do this ASAP as possible", proj = "cooler-project")` | `usethis::write_union(path = "C:/Users/I/do/not/want/to/type/cooler-project/TODO.R", lines = "I need to do this ASAP as possible.")` | +| Write a TODO item in project "cooler-project", while working in "cool-project" | `reuseme::use_todo(todo = "I need to do this ASAP as possible", proj = "cooler-project")` | `usethis::write_union(path = "C:/Users/I/do/not/want/to/type/cooler-project/TODO.R", lines = "I need to do this ASAP as possible.")` | +--------------------------------------------------------------------------------+----------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+ | Open pkgdown site link and see vignettes | 1. \`browse_pkg("usethis") | 1. `browse_package("usethis")` | | | 2. Click on the hyperlinks that correspond to your query | 2. Type the correct number that corresponds | @@ -128,6 +127,7 @@ Due to the growing number of criteria, regex, `file_outline()` is slowing down a ```{r} #| warning: false +#| message: false bench::mark( outline <- proj_outline() ) diff --git a/README.md b/README.md index da67599..fc63af3 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ coverage](https://codecov.io/gh/olivroy/reuseme/branch/main/graph/badge.svg)](ht The goal of reuseme is to provide utility functions for project -management across RStudio projects. Sometimes, you have to manage -multiple things at once, but don’t have the time to do edits. You may -need to switch quickly to a project, add things or browse a certain file -if you have some replications across projects. Sometimes, it is hard to -do that. reuseme also aims to help me overcome things I don’t like on -Windows. +management across RStudio projects. Sometimes, managing multiple +projects can be challenging. reuseme also aims to simplify project +management on Windows. You may need to switch quickly to a project, add +things or browse a certain file if you have some replications across +projects. Sometimes, it is hard to do that. reuseme also aims to help me +overcome things I don’t like on Windows. ## Installation @@ -45,13 +45,11 @@ resources) - Your RStudio projects are organized in a centralized location on your computer - Your RStudio projects are Version controlled with git (optional, but - recommended to avoid surprises! No need to be hosted on repositories - like GitLab or GitHub) -- You are working on Windows (macOS is supported, but some things were - designed on Windows) -- You use machine and human readable paths (i.e. no spaces, special - characters) (Tip: don’t hesitate to rename your files, it can take - away the pain in the long run! + recommended for avoiding surprises! No need to be hosted on + repositories like GitLab or GitHub) +- You use machine and human-readable paths (i.e. no spaces, special + characters) (Tip: don’t hesitate to rename your files + (`reuseme::rename_files2()`), your future self will thank you! To take advantage of reuseme, it is highly recommended to set the following option in your `.Rprofile` @@ -103,33 +101,36 @@ If you want to work across projects with [usethis](usethis.r-lib.org), you need to provide the full path to a project. With reuseme, just use the project name! - +
---++++ - - - - - - - - - - + + + + + + + - - + - + +
usethis vs reuseme
Workflowreusemeusethis
Switch to project “cool-project”proj_switch(proj = "cool-project")proj_activate(path = "C:/users/long/path/to/cool-project")

Workflow

+

Switch to project “cool-project”

reuseme

+

proj_switch(proj = "cool-project")

usethis

+

proj_activate(path = "C:/users/long/path/to/cool-project")

Write a TODO item in project “cooler-project”, while working in “cool-project”use_todo(todo = "I need to do this ASAP as possible", proj = "cooler-project")usethis::write_union(path = "C:/Users/I/do/not/want/to/type/cooler-project/TODO.R", lines = "I need to do this ASAP as possible.")reuseme::use_todo(todo = "I need to do this ASAP as possible", proj = "cooler-project") +| +usethis::write_union(path = "C:/Users/I/do/not/want/to/type/cooler-project/TODO.R", lines = "I need to do this ASAP as possible.")
Open pkgdown site link and see vignettes
  1. `browse_pkg(“usethis”)
  2. @@ -141,6 +142,7 @@ the project name!
  3. browseVignettes("usethis")
  4. Open it
@@ -165,7 +167,7 @@ bench::mark( #> # A tibble: 1 × 6 #> expression min median `itr/sec` mem_alloc `gc/sec` #> -#> 1 outline <- proj_outline() 559ms 559ms 1.79 18MB 3.58 +#> 1 outline <- proj_outline() 524ms 524ms 1.91 20.9MB 3.82 ```
@@ -183,42 +185,18 @@ outline #> `i` A great title #> `i` TODO improve this Viz!- `Done✔?` #> -#> ── `LICENSE.md` MIT License -#> -#> ── `playground/roxygen2-test.R` -#> `i` Section to extract -#> #> ── `R/dplyr-plus.R` dplyr extra -#> `i` in the presence of ties. -#> `i` Use with_ties = FALSE to return exactly n matches -#> `i` Use each = FALSE to have n divided in each place #> `i` FIXME Doesn't work, problem with symbols here- `Done✔?` -#> `i` with dplyr::filter -#> `i` extract the skin_color for C-3PO -#> `i` will return a named vector of mpg (as mtcars has rownames.) -#> `i` Extract hair color for all people #> `i` TODO use `check_length()` when implemented. r-lib/rlang#1618 ()- `Done✔?` #> `i` summarise with total -#> `i` works with `.by` -#> `i` works with `group_by()` -#> `i` NA all 2s -#> `i` You can actually use dplyr::na_if() in this case -#> `i` NA all 1 and 2 #> #> ── `R/eda-identity.R` dplyr/base identity helpers -------------------- -#> `i` Use cases / advantages -#> `i` Caution -#> `i` Workflow to explore mtcars #> `i` base identity functions #> `i` dplyr identity functions with small tweaks #> `i` dplyr identity without tweaks #> `i` dplyr extensions identity #> `i` helpers #> -#> ── `R/escape-inline-markup.R` -#> `i` example code -#> `i` last instance taken care of with escape_markup with a different strategy -#> #> ── `R/files-conflicts.R` #> `i` TODO insert in either proj_outline, or rename_file- `Done✔?` #> `i` TODO probably needs a `detect_genuine_path()`- `Done✔?` @@ -230,11 +208,6 @@ outline #> `i` Scalars #> `i` Vectors #> -#> ── `R/named.R` -#> `i` returns the same as base R for unnamed input -#> `i` returns all values -#> `i` TODO is usable with `extract_cell_value()` -#> #> ── `R/open.R` #> `i` FIXME why is this code like this?- `Done✔?` #> `i` TODO structure and summarise information.- `Done✔?` @@ -250,10 +223,6 @@ outline #> `i` it is 'R/outline.R' #> #> ── `R/outline.R` `proj_outline()` -#> `i` Remove todo items -#> `i` interact with data frame -#> `i` These all work on the active file / project or directory. -#> `i` Like proj_switch(), proj_outline() accepts a project #> `i` `file_outline()` #> `i` File outline #> `i` Print method @@ -265,13 +234,9 @@ outline #> `i` TODO maybe add a max?- `Done✔?` #> `i` TODO improve on this message- `Done✔?` #> -#> ── `R/proj-reuseme.R` -#> `i` Setup -#> `i` Capabilities. -#> -#> ── `R/rename-files.R` -#> `i` Use case +#> ── `R/rename.R` #> `i` After here, we start doing some renaming real situations +#> `i` TODO verify if path should be normalized.- `Done✔?` #> `i` Helpers #> `i` helpers for computing scope of renaming #> `i` TODO measure of string proximity- `Done✔?` @@ -279,7 +244,7 @@ outline #> `i` FIXME maybe not fail while testing- `Done✔?` #> `i` TODO Check that old file is more recent- `Done✔?` #> -#> ── `R/use-todo.R` +#> ── `R/todo.R` #> `i` TODO think about maybe using todo = clipr::read_clip()- `Done✔?` #> `i` TODO nice to have, but would need to extract duplicates- `Done✔?` #> `i` Helpers @@ -289,13 +254,7 @@ outline #> #> ── `R/utils.R` OS utils #> -#> ── `tests/testthat/_ref/many-titles.md` The title is the only outline element -#> `i` Another title -#> `i` Second level -#> `i` TODO this is an item- `Done✔?` -#> `i` Last title -#> -#> ── `tests/testthat/_ref/my-analysis.md` My doc title +#> ── `tests/testthat/_outline/my-analysis.md` My doc title #> `i` A section #> `i` Dashboard card #> `i` A code section @@ -304,18 +263,21 @@ outline #> `i` A long ggplot2 title #> `i` A code section #> -#> ── `tests/testthat/_ref/my-analysis.R` Analyse my streets +#> ── `tests/testthat/_outline/my-analysis.R` Analyse my {streets} #> `i` Read my streets () data #> `i` data wrangling #> `i` Write my streets #> `i` TODO Create a new version- `Done✔?` -#> `i` Roxygen section -#> `i` A real one -#> `i` A true one #> `i` 'R/my-file.R' #> `i` Section title #> -#> ── `tests/testthat/_ref/single-title.md` The title is the only outline element +#> ── `tests/testthat/_outline/title.md` The title is the only outline element +#> +#> ── `tests/testthat/_outline/titles.md` The title is the only outline element +#> `i` Another title +#> `i` Second level +#> `i` TODO this is an item- `Done✔?` +#> `i` Last title #> #> ── `tests/testthat/_snaps/case-if-any.md` #> `i` wrong cases error @@ -333,16 +295,16 @@ outline #> `i` alpha and work_only arguments work #> `i` pattern works as expected #> -#> ── `tests/testthat/_snaps/rename-files.md` +#> ── `tests/testthat/_snaps/rename.md` #> `i` Helper files returns the expected input #> -#> ── `tests/testthat/_snaps/use-todo.md` +#> ── `tests/testthat/_snaps/todo.md` #> `i` Marking a TODO item as done works #> #> ── `tests/testthat/test-case-if-any.R` #> `i` case_if_any() basic work #> `i` wrong cases error -#> `i` case_if_any() can use a newly created variable (#8) +#> `i` case_if_any() can use a newly created variable (#8 ()) #> #> ── `tests/testthat/test-dplyr-plus.R` #> `i` filter_if_any() errors correctly when using `by` instead of `.by` @@ -356,7 +318,10 @@ outline #> `i` Returns identity #> `i` Side effects are what's intended in interactive sessions #> -#> ── `tests/testthat/test-link-elements.R` +#> ── `tests/testthat/test-escape-inline-markup.R` +#> `i` TODO could probably be {. } works?- `Done✔?` +#> +#> ── `tests/testthat/test-markup.R` #> `i` link_gh_issue() + markup_href() work #> #> ── `tests/testthat/test-named.R` @@ -377,16 +342,16 @@ outline #> `i` file_outline() contains function calls #> `i` dir_outline() works with no error #> -#> ── `tests/testthat/test-rename-files.R` +#> ── `tests/testthat/test-rename.R` #> `i` Helper files returns the expected input #> `i` force and action are deprecated #> #> ── `tests/testthat/test-screenshot.R` #> `i` screenshot() does nothing in non-interactive sessions #> -#> ── `tests/testthat/test-use-todo.R` +#> ── `tests/testthat/test-todo.R` #> `i` Marking TODO as done detects tags -#> `i` todo items are correctly stripped +#> `i` TODO items are correctly stripped #> #> ── `tests/testthat/test-utils.R` #> `i` Windows is recognized correctly. @@ -416,7 +381,10 @@ outline #> `i` TODO [proj_file] to accesss data (return the path in this case?)- `Done✔?` #> `i` TODO [check_referenced_files] doesn't check for 'R/file.R'- `Done✔?` #> `i` TODO explain rationale behind `work_only`. Suggest to transform to TODO…- `Done✔?` -#> `i` TODO outline Show function call if exported + not internal + bonus if…- `Done✔?` +#> `i` TODO browse_pkg should open by default if no vignettes are found, becau…- `Done✔?` +#> `i` TODO exclude _files from `proj_list()`- `Done✔?` +#> `i` TODO rename_files should be less noisy about project name file- `Done✔?` +#> `i` TODO add_to_tricks(). when detecting TRICK like complete todo, but not …- `Done✔?` #> #> ── `NEWS.md` #> `i` reuseme (development version) diff --git a/TODO.R b/TODO.R index e3ffe13..96d780a 100644 --- a/TODO.R +++ b/TODO.R @@ -25,22 +25,5 @@ # TODO explain rationale behind `work_only`. Suggest to transform to TODO if this item is no longer critical. `work_only` goal is to show you exactly where you need to do work # 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 escape_markup doesn't work with complex operation {{x^2}} for example. Maybe if detecting something complex, use cli_escape function. escape-complex-markyp branch created to try to address this. -# TODO [outline] avoid evaluating in current env. -# TODO wrap regexps in functions -# TODO items should not truncate leading code when marking as complte trunc-todo-code -# TODO [outline] remove examples from outline. Sometimes commented code is caught. -# TODO [outline] roxygen comments processing should be left to {.fn roxygen2::parse_file} -# TODO [outline] show key like {.fn pak::pkg_deps_tree} does. -# TODO [outline] roxygen function title -# TODO [outline] remove ggtext markup from plot title. -# FIXME [outline] comments are now interpreted as section -# 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 explain rationale behind `work_only`. Suggest to transform to TODO if this item is no longer critical. `work_only` goal is to show you exactly where you need to do work -# TODO [outline] Show function call if exported + not internal + bonus if has family tagf # 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) diff --git a/man/active_rs_doc_nav.Rd b/man/active_rs_doc_nav.Rd new file mode 100644 index 0000000..21d6159 --- /dev/null +++ b/man/active_rs_doc_nav.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/open.R +\name{active_rs_doc_nav} +\alias{active_rs_doc_nav} +\title{Open Files Pane at current document location} +\usage{ +active_rs_doc_nav(path = active_rs_doc()) +} +\arguments{ +\item{path}{A path to file to navigate to (default active document).} +} +\value{ +NULL, called for its side effects. +} +\description{ +Easily navigate to active file document. +} +\details{ +Wrapper around \link[rstudioapi:executeCommand]{executeCommand("activateFiles")} + +\code{\link[rstudioapi:filesPaneNavigate]{rstudioapi::filesPaneNavigate()}} + \code{\link[rstudioapi:rstudio-editors]{rstudioapi::getActiveDocumentContext()}} +} diff --git a/man/complete_todo.Rd b/man/complete_todo.Rd index 00b7ad8..a00beef 100644 --- a/man/complete_todo.Rd +++ b/man/complete_todo.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use-todo.R +% Please edit documentation in R/todo.R \name{complete_todo} \alias{complete_todo} \title{Remove a TODO/WORK/FIXME item from a file} @@ -22,7 +22,7 @@ Writes a file with corrections, and returns the new line content invisibly. } \description{ -Function meant to be wrapped as \code{{.run}} hyperlinks with \code{\link[=file_outline]{file_outline()}}. +Function meant to be wrapped as \code{{.run }} hyperlinks with \code{\link[=file_outline]{file_outline()}}. It basically removes a line from a file. } \keyword{internal} diff --git a/man/link_gh_issue.Rd b/man/link_gh_issue.Rd index 5f43ef4..df683f6 100644 --- a/man/link_gh_issue.Rd +++ b/man/link_gh_issue.Rd @@ -1,13 +1,15 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/link-elements.R +% Please edit documentation in R/markup.R \name{link_gh_issue} \alias{link_gh_issue} \title{Create a markdown link to a GitHub issue} \usage{ -link_gh_issue(x) +link_gh_issue(x, home_repo = NULL) } \arguments{ \item{x}{A string, usually lines of files that contains issue numbers.} + +\item{home_repo}{Optional, but if supplied, will be stripped.} } \value{ A markdown link linked issue to GitHub issue diff --git a/man/markup_href.Rd b/man/markup_href.Rd index 6204d72..3380241 100644 --- a/man/markup_href.Rd +++ b/man/markup_href.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/link-elements.R +% Please edit documentation in R/markup.R \name{markup_href} \alias{markup_href} \title{Create a cli href with a markdown link} diff --git a/man/rename_files2.Rd b/man/rename_files2.Rd index 8b3d1b6..4d251e6 100644 --- a/man/rename_files2.Rd +++ b/man/rename_files2.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/rename-files.R +% Please edit documentation in R/rename.R \name{rename_files2} \alias{rename_files2} \title{Rename an output or a data file and watch for references} diff --git a/man/use_todo.Rd b/man/use_todo.Rd index ff42a5f..9da58c0 100644 --- a/man/use_todo.Rd +++ b/man/use_todo.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/use-todo.R +% Please edit documentation in R/todo.R \name{use_todo} \alias{use_todo} \title{Add a TODO list by project to a TODO.R file in the base directory} diff --git a/reuseme.Rproj b/reuseme.Rproj index d230585..59699e9 100644 --- a/reuseme.Rproj +++ b/reuseme.Rproj @@ -1,28 +1,28 @@ -Version: 1.0 - -RestoreWorkspace: No -SaveWorkspace: No -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -AutoAppendNewline: Yes -StripTrailingWhitespace: Yes -LineEndingConversion: Posix - -BuildType: Package -PackageUseDevtools: Yes -PackageInstallArgs: --no-multiarch --with-keep.source -PackageRoxygenize: rd,collate,namespace - -UseNativePipeOperator: Yes - -MarkdownWrap: Sentence - -SpellingDictionary: en_CA +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace + +UseNativePipeOperator: Yes + +MarkdownWrap: Sentence + +SpellingDictionary: en_CA diff --git a/tests/testthat/_ref/my-analysis.R b/tests/testthat/_outline/my-analysis.R similarity index 78% rename from tests/testthat/_ref/my-analysis.R rename to tests/testthat/_outline/my-analysis.R index 8e72086..291c6e9 100644 --- a/tests/testthat/_ref/my-analysis.R +++ b/tests/testthat/_outline/my-analysis.R @@ -1,6 +1,6 @@ -# Analyse my streets --------------- +# Analyse my {streets} --------------- ## Read my [streets](https://https://en.wikipedia.org/wiki/Street_art) data ------- -my_streets <- read.csv("data/my-streets.csv") +my_streets <- read.csv("data/my-streets.csv", silent = TRUE) new_dat <- my_streets |> dplyr::mutate( title = "data wrangling", # "problem" @@ -17,14 +17,8 @@ fs::path("data", "my-streets", ext = "csv") # system.file("file.R", package = "fs") # should not be listed. # TODO Create a new version -#' ## Roxygen section -#' \url{https://github.com} -#' -#' gt::tab_header(title = "A real one") |> -#' gt::tab_header(subtitle = "A true one") - # {.file R/my-file.R} --- - +tab_header(title = md("**A table title**")) cli::cli_ul("Refer to {.href [google](https://google.com)}") # ## a commented section title ----- diff --git a/tests/testthat/_ref/my-analysis.md b/tests/testthat/_outline/my-analysis.md similarity index 100% rename from tests/testthat/_ref/my-analysis.md rename to tests/testthat/_outline/my-analysis.md diff --git a/tests/testthat/_ref/single-title.md b/tests/testthat/_outline/title.md similarity index 100% rename from tests/testthat/_ref/single-title.md rename to tests/testthat/_outline/title.md diff --git a/tests/testthat/_ref/many-titles.md b/tests/testthat/_outline/titles.md similarity index 83% rename from tests/testthat/_ref/many-titles.md rename to tests/testthat/_outline/titles.md index 73eccae..954687d 100644 --- a/tests/testthat/_ref/many-titles.md +++ b/tests/testthat/_outline/titles.md @@ -8,3 +8,5 @@ output not a todo # Last title + +## `function_name()` title diff --git a/tests/testthat/_snaps/outline-criteria.md b/tests/testthat/_snaps/outline-criteria.md index 773e52b..a97cef0 100644 --- a/tests/testthat/_snaps/outline-criteria.md +++ b/tests/testthat/_snaps/outline-criteria.md @@ -6,9 +6,9 @@ o_is_cli_info o_is_commented_code o_is_generic_test - o_is_object_title o_is_roxygen_comment o_is_section_title + o_is_tab_plot_title o_is_test_that o_is_todo_fixme o_is_work_item diff --git a/tests/testthat/_snaps/outline.md b/tests/testthat/_snaps/outline.md index 4ff5dd4..512dff0 100644 --- a/tests/testthat/_snaps/outline.md +++ b/tests/testthat/_snaps/outline.md @@ -15,28 +15,25 @@ `i` Dashboard card Message - -- `my-analysis.R` Analyse my streets + -- `titles.md` The title is the only outline element + Output + `i` Another title + `i` Last title + `i` Second level + `i` TODO this is an item- `Donev?` + Message + + -- `my-analysis.R` Analyse my {streets} Output - `i` A real one - `i` A true one `i` TODO Create a new version- `Donev?` `i` Read my streets () data - `i` Roxygen section `i` Section title `i` Write my streets `i` data wrangling `i` 'R/my-file.R' Message - -- `many-titles.md` The title is the only outline element - Output - `i` Another title - `i` Last title - `i` Second level - `i` TODO this is an item- `Donev?` - Message - - -- `single-title.md` The title is the only outline element + -- `title.md` The title is the only outline element # alpha and work_only arguments work @@ -44,7 +41,7 @@ file_outline("street", my_test_file, alpha = TRUE, work_only = FALSE) Message - -- `ref/my-analysis.R` Analyse my streets + -- `outline/my-analysis.R` Analyse my {streets} Output `i` Read my streets () data `i` Write my streets diff --git a/tests/testthat/test-escape-inline-markup.R b/tests/testthat/test-escape-inline-markup.R index d5f24cb..98f64a4 100644 --- a/tests/testthat/test-escape-inline-markup.R +++ b/tests/testthat/test-escape-inline-markup.R @@ -3,7 +3,13 @@ test_that("escape_markup() works", { expect_equal(escape_markup(c("gt", "y {", "{gt}")), c("gt", "y {{", "{{gt}}")) expect_equal(escape_markup("{gt}"), "{{gt}}") expect_equal(escape_markup("{.file {here}}"), "{.file {.url here}}") + expect_equal(escape_markup("{"), "{{") + expect_equal(escape_markup("{.email}"), "{{.email}}") + expect_equal(escape_markup("This `}` and `{`"), "This `}}` and `{{`") + expect_equal(escape_markup("This `{` and `}`"), "This `{{` and `}}`") + # TODO could probably be {{. }} works? + expect_equal(escape_markup("{. } works"), ". works") input <- "multi problems {{gt}} to {gt} to {.file gt} to {.file {gt}}" exp_str <- "multi problems {{gt}} to {{gt}} to {.file gt} to {.file {.url gt}}" expect_equal(escape_markup(input), exp_str) diff --git a/tests/testthat/test-outline-criteria.R b/tests/testthat/test-outline-criteria.R index 98ef4a0..2bcfe88 100644 --- a/tests/testthat/test-outline-criteria.R +++ b/tests/testthat/test-outline-criteria.R @@ -25,21 +25,33 @@ test_that("o_is_work_item() works", { }) test_that("o_is_test_that() works", { - expect_true(o_is_test_that('test_that("Serious things are happening"')) + expect_true(o_is_test_that('test_that("Serious things are happening", {')) + expect_false(o_is_test_that('test_that("", {')) }) test_that("o_is_generic_test() works", { expect_true(o_is_generic_test('test_that("Serious things work properly"')) }) -test_that("o_is_object_title() works", { - expect_true(o_is_object_title("title = 'A great'")) +test_that("o_is_tab_plot_title() works", { + expect_true(o_is_tab_plot_title("title = 'A great'")) + expect_false(o_is_tab_plot_title("tab_header()")) + expect_false(o_is_tab_plot_title("```{r tab_header}")) + expect_false(o_is_tab_plot_title("fwd_title = 'Family'")) + expect_false(o_is_tab_plot_title("guide_legend(title = 'Family'")) + expect_false(o_is_tab_plot_title("title = ''")) + expect_false(o_is_tab_plot_title("title = '', symbol = 'x'")) + + expect_false(o_is_tab_plot_title('title = ".+", " +\\(",")')) + expect_false(o_is_tab_plot_title("dc:title = 'aaaaaaa'")) }) test_that("o_is_section_title() works", { expect_true(o_is_section_title("# Analysis of this")) expect_true(o_is_section_title(" # section 1 ----")) expect_false(o_is_section_title("# TidyTuesday")) + expect_false(o_is_section_title("Function ID:", roxy_section = TRUE)) + expect_false(o_is_section_title("#' @section Function ID:", roxy_section = TRUE)) }) test_that("o_is_commented_code() works", { diff --git a/tests/testthat/test-outline.R b/tests/testthat/test-outline.R index c09e0ae..0306d19 100644 --- a/tests/testthat/test-outline.R +++ b/tests/testthat/test-outline.R @@ -1,75 +1,75 @@ -test_that("file_outline() works", { - my_test_files <- test_path("_ref", c("my-analysis.R", "my-analysis.md", "single-title.md", "many-titles.md")) - rlang::local_interactive(TRUE) - expect_snapshot( - file_outline(path = my_test_files, alpha = TRUE), - transform = ~ sub("_?ref/", "", .x) - ) -}) - -test_that("alpha and work_only arguments work", { - my_test_file <- test_path("_ref/my-analysis.R") - rlang::local_interactive(TRUE) - # Somehow on r cmd check, strips _ref -> ref? - # it is just RStudio vs non-Rstudio - expect_snapshot( - error = FALSE, - file_outline("street", my_test_file, alpha = TRUE, work_only = FALSE), - transform = ~ sub("_", "", .x, fixed = TRUE) - ) -}) - -test_that("file_outline() is a data frame", { - file <- fs::path_package("reuseme", "example-file", "outline-script.R") - outline <- file_outline(path = file) - expect_s3_class(outline, c("outline_report", "tbl_df")) - expect_snapshot( - outline, - # simplify path display to avoid snapshot failures. - transform = ~ sub(" `[^`]+` ", " `outline-script.R` ", .x) - ) -}) - -test_that("pattern works as expected", { - # TODO change tests for data frame size when stable (efficiency). As still debugging, better to keep all snapshots. - # The idea is to show doc title + regex outline match when relevant - file <- fs::path_package("reuseme", "example-file", "outline-script.R") - expect_snapshot(file_outline(pattern = "not found", path = file)) - expect_snapshot( - { - file_outline("Viz", path = file) - }, - transform = ~ sub(" `[^`]+` ", " `outline-script.R` ", .x) - ) - # will work also if the regex is only in title - expect_snapshot( - { - file_outline("Example for", path = file) - }, - transform = ~ sub(" `[^`]+` ", " `outline-script.R` ", .x) - ) -}) - -test_that("file_outline() with only title doesn't error", { - expect_no_error({ - file <- file_outline(path = test_path("_ref", "single-title.md")) - }) - expect_equal(nrow(file), 1L) - expect_no_error({ - file <- file_outline(path = test_path("_ref", "many-titles.md")) - }) - # Number of items in many-titles.md - expect_equal(nrow(file), 5L) -}) - -test_that("file_outline() contains function calls", { - file <- fs::path_package("reuseme", "example-file", "outline-script.R") - outline <- file_outline(path = file) - expect_contains(outline$outline_el, c("f_example", "f2_example")) - # excludes commented things - expect_no_match(outline$outline_el, "f_commented_example") -}) - -test_that("dir_outline() works with no error", { - expect_no_error(dir_outline(pattern = ".+", path = test_path("_ref"))) -}) +test_that("file_outline() works", { + my_test_files <- test_path("_outline", c("my-analysis.R", "my-analysis.md", "title.md", "titles.md")) + rlang::local_interactive(TRUE) + expect_snapshot( + file_outline(path = my_test_files, alpha = TRUE), + transform = ~ sub("_?ref/", "", .x) + ) +}) + +test_that("alpha and work_only arguments work", { + my_test_file <- test_path("_outline/my-analysis.R") + rlang::local_interactive(TRUE) + # Somehow on r cmd check, strips _ref -> ref? + # it is just RStudio vs non-Rstudio + expect_snapshot( + error = FALSE, + file_outline("street", my_test_file, alpha = TRUE, work_only = FALSE), + transform = ~ sub("_", "", .x, fixed = TRUE) + ) +}) + +test_that("file_outline() is a data frame", { + file <- fs::path_package("reuseme", "example-file", "outline-script.R") + outline <- file_outline(path = file) + expect_s3_class(outline, c("outline_report", "tbl_df")) + expect_snapshot( + outline, + # simplify path display to avoid snapshot failures. + transform = ~ sub(" `[^`]+` ", " `outline-script.R` ", .x) + ) +}) + +test_that("pattern works as expected", { + # TODO change tests for data frame size when stable (efficiency). As still debugging, better to keep all snapshots. + # The idea is to show doc title + regex outline match when relevant + file <- fs::path_package("reuseme", "example-file", "outline-script.R") + expect_snapshot(file_outline(pattern = "not found", path = file)) + expect_snapshot( + { + file_outline("Viz", path = file) + }, + transform = ~ sub(" `[^`]+` ", " `outline-script.R` ", .x) + ) + # will work also if the regex is only in title + expect_snapshot( + { + file_outline("Example for", path = file) + }, + transform = ~ sub(" `[^`]+` ", " `outline-script.R` ", .x) + ) +}) + +test_that("file_outline() with only title doesn't error", { + expect_no_error({ + file <- file_outline(path = test_path("_outline", "title.md")) + }) + expect_equal(nrow(file), 1L) + expect_no_error({ + file <- file_outline(path = test_path("_outline", "titles.md")) + }) + # Number of items in titles.md + expect_equal(nrow(file), 5L) +}) + +test_that("file_outline() contains function calls", { + file <- fs::path_package("reuseme", "example-file", "outline-script.R") + outline <- file_outline(path = file) + expect_contains(outline$outline_el, c("f_example", "f2_example")) + # excludes commented things + expect_no_match(outline$outline_el, "f_commented_example") +}) + +test_that("dir_outline() works with no error", { + expect_no_error(dir_outline(pattern = ".+", path = test_path("_outline"))) +}) diff --git a/tests/testthat/test-rename.R b/tests/testthat/test-rename.R index f8bdabc..78826d1 100644 --- a/tests/testthat/test-rename.R +++ b/tests/testthat/test-rename.R @@ -1,5 +1,5 @@ describe("rename_files2()", { - og_file <- fs::path_real(test_path("_ref", "my-analysis.R")) + og_file <- fs::path_real(test_path("_outline", "my-analysis.R")) # temp dir + change working directory tmp_dir <- withr::local_tempdir() fs::dir_create( @@ -123,7 +123,7 @@ test_that("file testing are working as expected", { }) test_that("force and action are deprecated", { - file <- withr::local_tempfile(fileext = ".R", lines = c("# x1")) + file <- withr::local_tempfile(fileext = ".R", lines = "# x1") file2 <- withr::local_tempfile(fileext = ".R") unlink(file2) lifecycle::expect_deprecated( diff --git a/tests/testthat/test-todo.R b/tests/testthat/test-todo.R index 066d892..c25c0d3 100644 --- a/tests/testthat/test-todo.R +++ b/tests/testthat/test-todo.R @@ -17,7 +17,7 @@ test_that("Marking TODO as done detects tags", { ) }) -test_that("todo items are correctly stripped", { +test_that("TODO items are correctly stripped", { expect_equal( strip_todo_line("2^2 # TODO this"), "2^2"