Skip to content

Commit

Permalink
Prefer multiple functions with convenient syntax (#82)
Browse files Browse the repository at this point in the history
Fixes #77
  • Loading branch information
hadley committed Jan 17, 2023
1 parent 2117364 commit c35d8ee
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 45 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export(conflict_prefer)
export(conflict_prefer_all)
export(conflict_prefer_matching)
export(conflict_scout)
export(conflicts_prefer)
import(rlang)
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# conflicted (development version)

* New `conflicts_prefer()` to easily declare multiple preferences at once:
`conflicts_prefer(dplyr::filter, lubridate::week, ...)` (#82).

* Conflict dismabiguation message now provides clickable preferences (#74).

# conflicted 1.1.0
Expand Down
11 changes: 8 additions & 3 deletions R/disambiguate.R
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,22 @@ add_ns <- function(fun = "") {
# Helpers -----------------------------------------------------------------

prefer_bullets <- function(pkgs, name) {
if (make.names(name) != name) {
name <- backtick(name)
}

ns <- add_ns()
prefer <- map_chr(pkgs, function(pkg) {
sprintf(
"{.run [%sconflict_prefer(\"%s\", \"%s\")](conflicted::conflict_prefer(\"%s\", \"%s\"))}",
"{.run [%sconflicts_prefer(%s::%s)](conflicted::conflicts_prefer(%s::%s))}",
ns,
name,
pkg,
name,
pkg
pkg,
name
)
})

names(prefer) <- rep("*", length(prefer))
prefer
}
Expand Down
56 changes: 56 additions & 0 deletions R/favor.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#' Declare many preferences at once
#'
#' @description
#' `conflicts_prefer()` allows you to declare "winners" of conflicts,
#' declaring one or many winners at once.
#'
#' See [conflict_prefer()] for more more precise control.
#'
#' @section Best practices:
#' I recommend placing a single call to `conflicts_prefer()` at the top of
#' your script, immediately after loading all needed packages with calls to
#' `library()`.
#'
#' @export
#' @param ... Functions to prefer in form `pkg::fun` or `pkg::fun()`.
#' @inheritParams conflict_prefer
#' @examples
#' conflicts_prefer(
#' dplyr::filter(),
#' dplyr::lag(),
#' )
conflicts_prefer <- function(..., .quiet = FALSE) {
calls <- exprs(...)
# accept pkg::fun() or pkg::fun
calls <- lapply(calls, standardise_call)

is_ok <- vapply(calls, is_ns_call, logical(1))
if (any(!is_ok)) {
cli::cli_abort(
"All arguments must be in form {.code pkg::fun} or {.fn pkg::fun}"
)
}

pkgs <- vapply(calls, function(x) as.character(x[[2]]), character(1))
funs <- vapply(calls, function(x) as.character(x[[3]]), character(1))

for (i in seq_along(calls)) {
conflict_preference_register(funs[[i]], pkgs[[i]], quiet = .quiet)
}

conflicts_register()

invisible()
}

standardise_call <- function(x) {
if (is_call(x, n = 0)) {
x[[1]]
} else {
x
}
}

is_ns_call <- function(x) {
is_call(x, "::", n = 2)
}
9 changes: 4 additions & 5 deletions R/prefer.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
#' `conflict_prefer()` allows you to declare "winners" of conflicts.
#' You can either declare a specific pairing (i.e. `dplyr::filter()` beats
#' `base::filter()`), or an overall winner (i.e. `dplyr::filter()` beats
#' all comers).
#' all comers). As conflicted 1.2.0, in most case you should use
#' [conflicts_prefer()] instead as it's both faster and easier to use.
#'
#' Use `conficted_prefer_all()` to prefer all functions in a package, or
#' `conflicted_prefer_matching()` to prefer functions that match a regular
#' expression.
#'
#' @section Best practices:
#' I recommend placing calls to `conflict_prefer()` at the top of your
#' script, immediately underneath the relevant `library()` call.
#'
#' @param name Name of function.
#' @param winner Name of package that should win the conflict.
#' @param losers Optional vector of packages that should lose the conflict.
Expand Down Expand Up @@ -76,8 +73,10 @@ conflict_preference_register <- function(name, winner, losers = NULL, quiet = FA

env_bind(prefs, !!name := c(winner, losers))

invisible()
}


#' @export
#' @param pattern Regular expression used to select objects from the `winner`
#' package.
Expand Down
4 changes: 2 additions & 2 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ dplyr::filter(mtcars, am & cyl == 8)
Or declare a session-wide preference:

```{r}
conflict_prefer("filter", "dplyr")
conflicts_prefer(dplyr::filter())
filter(mtcars, am & cyl == 8)
```

I recommend declaring preferences directly underneath the corresponding library call:

```{r, eval = FALSE}
library(dplyr)
conflict_prefer("filter", "dplyr")
conflicts_prefer(dplyr::filter)
```

You can ask conflicted to report any conflicts in the current session:
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ library(dplyr)

filter(mtcars, cyl == 8)
#> Error:
#> ! [conflicted] `filter` found in 2 packages.
#> Either pick the one you want with `::`
#> * dplyr::filter
#> * stats::filter
#> Or declare a preference with `conflict_prefer()`
#> * conflict_prefer("filter", "dplyr")
#> * conflict_prefer("filter", "stats")
#> ! [conflicted] filter found in 2 packages.
#> Either pick the one you want with `::`:
#> dplyr::filter
#> stats::filter
#> Or declare a preference with `conflict_prefer()`:
#> • `conflicts_prefer(dplyr::filter)`
#> • `conflicts_prefer(stats::filter)`
```

As suggested, you can either namespace individual calls:
Expand All @@ -61,8 +61,8 @@ dplyr::filter(mtcars, am & cyl == 8)
Or declare a session-wide preference:

``` r
conflict_prefer("filter", "dplyr")
#> [conflicted] Will prefer dplyr::filter over any other package
conflicts_prefer(dplyr::filter())
#> [conflicted] Will prefer dplyr::filter over any other package.
filter(mtcars, am & cyl == 8)
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> Ford Pantera L 15.8 8 351 264 4.22 3.17 14.5 0 1 5 4
Expand All @@ -74,16 +74,16 @@ library call:

``` r
library(dplyr)
conflict_prefer("filter", "dplyr")
conflicts_prefer(dplyr::filter)
```

You can ask conflicted to report any conflicts in the current session:

``` r
conflict_scout()
#> 2 conflicts:
#> * `filter`: [dplyr]
#> * `lag` : dplyr, stats
#> 2 conflicts
#> `filter()`: dplyr
#> `lag()`: dplyr and stats
```

Functions surrounded by `[]` have been chosen using one of the built-in
Expand Down
9 changes: 2 additions & 7 deletions man/conflict_prefer.Rd

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

32 changes: 32 additions & 0 deletions man/conflicts_prefer.Rd

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

30 changes: 15 additions & 15 deletions tests/testthat/_snaps/disambiguate.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
* b::x
* c::x
Or declare a preference with `conflict_prefer()`:
* `conflict_prefer("x", "a")`
* `conflict_prefer("x", "b")`
* `conflict_prefer("x", "c")`
* `conflicts_prefer(a::x)`
* `conflicts_prefer(b::x)`
* `conflicts_prefer(c::x)`
Code
disambiguate_prefix("if", c("a", "b", "c"))()
Condition
Expand All @@ -23,18 +23,18 @@
* b::`if`
* c::`if`
Or declare a preference with `conflict_prefer()`:
* `conflict_prefer("if", "a")`
* `conflict_prefer("if", "b")`
* `conflict_prefer("if", "c")`
* `` conflicts_prefer(a::`if`) ``
* `` conflicts_prefer(b::`if`) ``
* `` conflicts_prefer(c::`if`) ``
Code
disambiguate_infix("%in%", c("a", "b", "c"))()
Condition
Error:
! [conflicted] %in% found in 3 packages.
Declare a preference with `conflict_prefer()`:
* `conflict_prefer("%in%", "a")`
* `conflict_prefer("%in%", "b")`
* `conflict_prefer("%in%", "c")`
* `` conflicts_prefer(a::`%in%`) ``
* `` conflicts_prefer(b::`%in%`) ``
* `` conflicts_prefer(c::`%in%`) ``

# display namespace if not attached

Expand All @@ -49,17 +49,17 @@
* b::x
* c::x
Or declare a preference with `conflicted::conflict_prefer()`:
* `conflicted::conflict_prefer("x", "a")`
* `conflicted::conflict_prefer("x", "b")`
* `conflicted::conflict_prefer("x", "c")`
* `conflicted::conflicts_prefer(a::x)`
* `conflicted::conflicts_prefer(b::x)`
* `conflicted::conflicts_prefer(c::x)`
Code
cnds$infix
Output
<error/rlang_error>
Error:
! [conflicted] %in% found in 3 packages.
Declare a preference with `conflicted::conflict_prefer()`:
* `conflicted::conflict_prefer("%in%", "a")`
* `conflicted::conflict_prefer("%in%", "b")`
* `conflicted::conflict_prefer("%in%", "c")`
* `` conflicted::conflicts_prefer(a::`%in%`) ``
* `` conflicted::conflicts_prefer(b::`%in%`) ``
* `` conflicted::conflicts_prefer(c::`%in%`) ``

8 changes: 8 additions & 0 deletions tests/testthat/_snaps/favor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# trailing () is optional

Code
conflicts_prefer(dplyr::lag, dplyr::filter())
Message
[conflicted] Will prefer dplyr::lag over any other package.
[conflicted] Will prefer dplyr::filter over any other package.

10 changes: 10 additions & 0 deletions tests/testthat/test-favor.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
test_that("trailing () is optional", {
withr::defer(prefs_reset())

expect_snapshot({
conflicts_prefer(
dplyr::lag,
dplyr::filter()
)
})
})

0 comments on commit c35d8ee

Please sign in to comment.