diff --git a/DESCRIPTION b/DESCRIPTION index 38a2b55..da0c33d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: reuseme Title: Collections of Utility Functions to Work Across Projects -Version: 0.0.2.9007 +Version: 0.0.2.9008 Authors@R: person("Olivier", "Roy", , "olivierroy71@hotmail.com", role = c("aut", "cre")) Description: Allows you to browse current projects, rename files safely, diff --git a/R/files-conflicts.R b/R/files-conflicts.R index 9b3cbb1..d94b386 100644 --- a/R/files-conflicts.R +++ b/R/files-conflicts.R @@ -74,12 +74,12 @@ check_referenced_files <- function(path = ".", quiet = FALSE) { #' @param extra_msg Extra message to pass #' @param what Which file conflicts we talking about #' @param quiet A logical, informs where the occurrences are found. (Default, `FALSE`) -#' +#' @param new_file (Optional) new file name #' @return Mostly called for its side-effects, but will return the number of matches #' (0 if no referenced files are problematic) #' @export #' @keywords internal -solve_file_name_conflict <- function(files, regex, dir = ".", extra_msg = NULL, quiet = FALSE, what = NULL) { +solve_file_name_conflict <- function(files, regex, dir = ".", extra_msg = NULL, quiet = FALSE, what = NULL, new_file = NULL) { regex <- stringr::str_replace_all(regex, "\\\\|\\)|\\(|\\}\\{\\?|\\$|~", ".") # regex <- as.character(regex) if (dir != ".") { @@ -91,6 +91,11 @@ solve_file_name_conflict <- function(files, regex, dir = ".", extra_msg = NULL, purrr::map(\(x) tibble::enframe(x, name = "line_number", value = "content")) |> dplyr::bind_rows(.id = "file") + if (!is.null(new_file)) { + nchar_new_file <- nchar(new_file) + bullets_df$content <- gsub(new_file, paste0("x", nchar_new_file), bullets_df$content, fixed = TRUE) + } + bullets_df <- bullets_df[grepl(regex, bullets_df$content), ] if (nrow(bullets_df) == 0) { diff --git a/R/move.R b/R/move.R index df7affc..7286ad7 100644 --- a/R/move.R +++ b/R/move.R @@ -58,15 +58,15 @@ file_move_temp_auto <- function(destdir) { #' #' @returns The new full path name, invisibly, allowing you to call the functions another time. file_rename_auto <- function(new_name, old_file = .Last.value) { - if (!fs::is_file(old_name)) { - cli::cli_abort("incorrect context for .rename_temp.") + if (!fs::is_file(old_file)) { + cli::cli_abort("incorrect context for {.fn file_rename_auto}.") } # path_dir() behaves weirdly if not an fs path # fs::path_dir("~/") # fs::path_dir(fs::path("~)) # Workaround r-lib/fs#459 - old_path <- fs::path_real(old_name) - ext <- fs::path_ext(old_name) + old_path <- fs::path_real(old_file) + ext <- fs::path_ext(old_file) # TRICK {{wf}} path_ext_set replaces or appends extension new_name <- fs::path_ext_set(new_name, ext) new_path <- fs::path( @@ -75,7 +75,7 @@ file_rename_auto <- function(new_name, old_file = .Last.value) { ) fs::file_move(old_path, new_path) cli::cli_inform(c( - v = "Successfully moved {.file {old_name}} to {.val {new_name}}.", + v = "Successfully moved {.file {old_path}} to {.val {new_name}}.", "Open with {.run fs::file_show('{as.character(new_path)}')}", "For new name, call `reuseme::file_rename_auto('better-name-sans-ext')` immediately.", "For new dir, call `reuseme::file_move_auto('better-name-sans-ext')` immediately." diff --git a/R/outline-criteria.R b/R/outline-criteria.R index 32bedad..2bfc797 100644 --- a/R/outline-criteria.R +++ b/R/outline-criteria.R @@ -208,6 +208,7 @@ define_outline_criteria <- function(.data, print_todo) { 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_(save.+|links|add.+)?\\(.") & !stringr::str_detect(content, "@param|\\{\\.|str_"), + is_cross_ref = is_cross_ref & stringr::str_detect(content, "\\d"), 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, ) diff --git a/R/proj-list.R b/R/proj-list.R index fd8ffce..71b1a15 100644 --- a/R/proj-list.R +++ b/R/proj-list.R @@ -1,174 +1,175 @@ -#' Opens a RStudio project in a new session -#' -#' If not specified, will generate hyperlinks that call [usethis::proj_activate()]. -#' `proj_switch()` looks at `options(reuseme.reposdir)`. -#' -#' @param proj the name of a project located in the default locations or `NA` -#' @param new_session Should open in a new session? -#' -#' @returns Single logical value indicating if current session is modified. -#' @export -#' @seealso [usethis::proj_activate()] -#' @family project management helpers -proj_switch <- function(proj = NA, new_session = TRUE) { - # This is my default places (reuseme.reposdir possibly) - # See fs::path_home - project <- proj_list(proj) - - if (rlang::has_length(project, 1)) { - # Doing the switch - if (new_session) { - usethis::proj_activate(unname(project)) - } else { - # remove if https://github.com/r-lib/usethis/pull/1954 is implemented. - rstudioapi::openProject(path = unname(project), newSession = FALSE) - } - return(invisible(project)) - } - - # Displaying possibilities - # TODO maybe add a max? - bullets <- paste0("Open {.run [", names(project), "](reuseme::proj_switch('", names(project), "', new_session = ", new_session, "))}") - cli::cli_bullets(bullets) -} - - -#' Access the file outline within other project -#' -#' It can be used as [file_outline()] + `proj`. -#' -#' @param file A filename or regexp to a file inside `proj` -#' @param path a project path [proj_list()]. If `NULL`, -#' will return active project. -#' @param pattern A regular expression to look for -#' @return The file outline if multiple matches are found -#' @export -#' -#' @examples -#' try(proj_file("A non-existent file")) -#' @family project management helpers -proj_file <- function(file = NULL, path = active_rs_proj(), pattern = NULL) { - rlang::check_required(file) - check_proj(path, allow_null = TRUE) - # search will only be conducted with pattern - if (is.null(pattern) && is.null(file)) { - cli::cli_abort( - "One of {.arg pattern} or {.arg file} must exist." - ) - } - file <- file %||% "A non-existent rubbish file placeholder" - if (fs::is_file(file)) { - file_outline(file) - open_rs_doc(file) - return(invisible(file)) - } - proj_path <- proj_list(path) - 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, type = "file") - possible_files <- fs::path_filter(possible_files, regexp = file) - if (length(possible_files) > 1L) { - # exclude these files if multiple matches - possible_files <- fs::path_filter(possible_files, regexp = "_snaps|testthat/test-", invert = TRUE) - } - if (length(possible_files) == 0L) { - if (is.null(pattern)) { - cli::cli_abort("No match found for {.val {file}} in {.file {proj_path}}") - } else { - return(proj_outline( - path = proj_path, - pattern = pattern - )) - } - } - - if (length(possible_files) == 1L) { - return(file_outline(possible_files, pattern = pattern)) - } - - cli::cli_inform(c( # TODO improve on this message - "A couple files found. Access the desired place." - )) - file_outline(path = possible_files, pattern = pattern) -} - -#' Specify `proj` in functions -#' -#' @description -#' -#' Two main ways to specify proj: -#' * Set `options(reuseme.reposdir)` in `.Rprofile` that contains a character -#' vector of paths to your projects. (i.e. `~/rrr` that contains all your projects) -#' * Specify the full path to `proj`. (like you would for usethis function) -#' -#' @param proj A project path or name to match. If `NA`, returns all projects. -#' If `NULL`, returns the active project. -#' @param dirs The directories in which we want to list projects. -#' -#' @returns A named character vector with the project name as name, and path as value. -#' If `proj` is supplied -#' @export -#' @family project management helpers -proj_list <- function(proj = NA, dirs = getOption("reuseme.reposdir")) { - check_proj(proj, allow_null = TRUE, allow_na = TRUE) - proj <- proj %||% proj_get2() - if (!is.na(proj) && length(proj) == 1) { - # avoid creating a nested project. - # known direct - if (!proj %in% c("pkgdown", "testthat", "R", "man", "tests", "inst", "src", "vignettes")) { - if (fs::dir_exists(proj)) { - return( - rlang::set_names( - proj, - fs::path_file(proj) - ) - ) - } - } - } - - proj_location <- dirs %||% default_dirs() %||% getOption("usethis.destdir") - directories <- fs::dir_ls( - proj_location, - type = "directory", - recurse = FALSE, - regexp = ".Rcheck|_files", - invert = TRUE - ) - - projects <- rlang::set_names(x = as.character(directories), nm = fs::path_file) - if (!is.na(proj) && anyDuplicated(names(projects)) > 0) { - which_duplicate <- names(projects)[duplicated(names(projects))] - if (proj %in% which_duplicate) { - # R only returns - duplicates_to_resolve <- projects[which(names(projects) == proj)] - cli::cli_abort(c( - "{which_duplicate} can be found in more than one location: {.file {duplicates_to_resolve}}.", - "i" = "Specify the full path to disambiguate." # ! (This may be improved in the future) - )) - } - } - if (!is.na(proj)) { - if (!grepl("~/", proj, fixed = TRUE)) { - # try to catch an invalid path - rlang::arg_match0( - proj, - names(projects) - ) - projects <- projects[proj] - } else { - cli::cli_abort(c("Can't find the project location for {.val {proj}} using {.topic fs::path}.")) - } - } - projects -} - -default_dirs <- function() { - fs::path_home("Documents", c("rrr", "rrr-forks")) -} +#' Opens a RStudio project in a new session +#' +#' If not specified, will generate hyperlinks that call [usethis::proj_activate()]. +#' `proj_switch()` looks at `options(reuseme.reposdir)`. +#' +#' @param proj the name of a project located in the default locations or `NA` +#' @param new_session Should open in a new session? +#' +#' @returns Single logical value indicating if current session is modified. +#' @export +#' @seealso [usethis::proj_activate()] +#' @family project management helpers +proj_switch <- function(proj = NA, new_session = TRUE) { + # This is my default places (reuseme.reposdir possibly) + # See fs::path_home + project <- proj_list(proj) + + if (rlang::has_length(project, 1)) { + # Doing the switch + if (new_session) { + usethis::proj_activate(unname(project)) + } else { + # remove if https://github.com/r-lib/usethis/pull/1954 is implemented. + rstudioapi::openProject(path = unname(project), newSession = FALSE) + } + return(invisible(project)) + } + + # Displaying possibilities + # TODO maybe add a max? + bullets <- paste0("Open {.run [", names(project), "](reuseme::proj_switch('", names(project), "', new_session = ", new_session, "))}") + cli::cli_bullets(bullets) +} + + +#' Access the file outline within other project +#' +#' It can be used as [file_outline()] + `proj`. +#' +#' @param file A filename or regexp to a file inside `proj` +#' @param path a project path [proj_list()]. If `NULL`, +#' will return active project. +#' @param pattern A regular expression to look for +#' @return The file outline if multiple matches are found +#' @export +#' +#' @examples +#' try(proj_file("A non-existent file")) +#' @family project management helpers +proj_file <- function(file = NULL, path = active_rs_proj(), pattern = NULL) { + rlang::check_required(file) + check_proj(path, allow_null = TRUE) + # search will only be conducted with pattern + if (is.null(pattern) && is.null(file)) { + cli::cli_abort( + "One of {.arg pattern} or {.arg file} must exist." + ) + } + file <- file %||% "A non-existent rubbish file placeholder" + if (fs::is_file(file)) { + file_outline(file) + open_rs_doc(file) + return(invisible(file)) + } + proj_path <- proj_list(path) + 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, type = "file") + possible_files <- fs::path_filter(possible_files, regexp = file) + if (length(possible_files) > 1L) { + # exclude these files if multiple matches + possible_files <- fs::path_filter(possible_files, regexp = "_snaps|testthat/test-", invert = TRUE) + } + if (length(possible_files) == 0L) { + if (is.null(pattern)) { + cli::cli_abort("No match found for {.val {file}} in {.file {proj_path}}") + } else { + return(proj_outline( + path = proj_path, + pattern = pattern + )) + } + } + + if (length(possible_files) == 1L) { + return(file_outline(possible_files, pattern = pattern)) + } + + cli::cli_inform(c( # TODO improve on this message + "A couple files found. Access the desired place." + )) + file_outline(path = possible_files, pattern = pattern) +} + +#' Specify `proj` in functions +#' +#' @description +#' +#' Two main ways to specify proj: +#' * Set `options(reuseme.reposdir)` in `.Rprofile` that contains a character +#' vector of paths to your projects. (i.e. `~/rrr` that contains all your projects) +#' * Specify the full path to `proj`. (like you would for usethis function) +#' +#' @param proj A project path or name to match. If `NA`, returns all projects. +#' If `NULL`, returns the active project. +#' @param dirs The directories in which we want to list projects. +#' +#' @returns A named character vector with the project name as name, and path as value. +#' If `proj` is supplied +#' @export +#' @family project management helpers +proj_list <- function(proj = NA, dirs = getOption("reuseme.reposdir")) { + check_proj(proj, allow_null = TRUE, allow_na = TRUE) + proj <- proj %||% proj_get2() + if (!is.na(proj) && length(proj) == 1) { + # avoid creating a nested project. + # known direct + if (!proj %in% c("pkgdown", "testthat", "R", "man", "tests", "inst", "src", "vignettes", "images")) { + if (fs::dir_exists(proj)) { + + return( + rlang::set_names( + proj, + fs::path_file(proj) + ) + ) + } + } + } + + proj_location <- dirs %||% default_dirs() %||% getOption("usethis.destdir") + directories <- fs::dir_ls( + proj_location, + type = "directory", + recurse = FALSE, + regexp = ".Rcheck|_files", + invert = TRUE + ) + + projects <- rlang::set_names(x = as.character(directories), nm = fs::path_file) + if (!is.na(proj) && anyDuplicated(names(projects)) > 0) { + which_duplicate <- names(projects)[duplicated(names(projects))] + if (proj %in% which_duplicate) { + # R only returns + duplicates_to_resolve <- projects[which(names(projects) == proj)] + cli::cli_abort(c( + "{which_duplicate} can be found in more than one location: {.file {duplicates_to_resolve}}.", + "i" = "Specify the full path to disambiguate." # ! (This may be improved in the future) + )) + } + } + if (!is.na(proj)) { + if (!grepl("~/", proj, fixed = TRUE)) { + # try to catch an invalid path + rlang::arg_match0( + proj, + names(projects) + ) + projects <- projects[proj] + } else { + cli::cli_abort(c("Can't find the project location for {.val {proj}} using {.topic fs::path}.")) + } + } + projects +} + +default_dirs <- function() { + fs::path_home("Documents", c("rrr", "rrr-forks")) +} diff --git a/R/rename.R b/R/rename.R index 3a08666..3f1b002 100644 --- a/R/rename.R +++ b/R/rename.R @@ -50,12 +50,12 @@ rename_files2 <- function(old, check_bool(overwrite) if (lifecycle::is_present(force)) { - lifecycle::deprecate_warn( - when = "0.0.9006", + lifecycle::deprecate_stop( + when = "0.1.0", what = "rename_files2(force)", with = "rename_files2(warn_conflicts)", details = cli::format_inline( - "{.arg overwrite} must be used with caution to allow overwriing {.code new}" + "{.arg overwrite} must be used with caution to allow overwriing {.code new}." ) ) if (isTRUE(force)) { @@ -141,7 +141,8 @@ rename_files2 <- function(old, dir = ".", extra_msg = extra_msg_if_file_conflict, quiet = FALSE, - what = regex_friendly # either full path or basename. + what = regex_friendly, # either full path or basename. + new_file = new ) if (renaming_strategy != "free_for_all" && n_file_names_conflicts > 10) { diff --git a/_pkgdown.yml b/_pkgdown.yml index d861e63..4f1873c 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -26,6 +26,8 @@ reference: - use_todo - screenshot - rename_files2 + - file_move_temp_auto + - file_rename_auto - starts_with("active_rs_doc") - title: Data analysis helpers / EDA