From 4e44ac066d2165ecd10b13ad115a3c2723039b44 Mon Sep 17 00:00:00 2001 From: edward-burn <9583964+edward-burn@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:36:06 +0100 Subject: [PATCH] str_ilike #543 - add str_ilike function - remove ignore_case argument from str_like - add tests of case sensitivity for both --- DESCRIPTION | 2 +- NAMESPACE | 1 + R/detect.R | 46 ++++++++++++++++++++++++++++++------ man/str_ilike.Rd | 36 ++++++++++++++++++++++++++++ man/str_like.Rd | 10 ++++---- tests/testthat/test-detect.R | 7 ++++++ 6 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 man/str_ilike.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 78ce3863..093e3973 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -40,4 +40,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 diff --git a/NAMESPACE b/NAMESPACE index 07f842ab..bd7e32d7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,6 +30,7 @@ export(str_flatten) export(str_flatten_comma) export(str_glue) export(str_glue_data) +export(str_ilike) export(str_interp) export(str_length) export(str_like) diff --git a/R/detect.R b/R/detect.R index bdfdd970..5e719929 100644 --- a/R/detect.R +++ b/R/detect.R @@ -119,30 +119,62 @@ str_ends <- function(string, pattern, negate = FALSE) { #' * `_` matches a single character (like `.`). #' * `%` matches any number of characters (like `.*`). #' * `\%` and `\_` match literal `%` and `_`. -#' * The match is case insensitive by default. +#' * The match is case sensitive. #' #' @inheritParams str_detect #' @param pattern A character vector containing a SQL "like" pattern. #' See above for details. -#' @param ignore_case Ignore case of matches? Defaults to `TRUE` to match -#' the SQL `LIKE` operator. #' @return A logical vector the same length as `string`. #' @export #' @examples #' fruit <- c("apple", "banana", "pear", "pineapple") #' str_like(fruit, "app") #' str_like(fruit, "app%") +#' str_like(fruit, "APP%") #' str_like(fruit, "ba_ana") -#' str_like(fruit, "%APPLE") -str_like <- function(string, pattern, ignore_case = TRUE) { +#' str_like(fruit, "%apple") +str_like <- function(string, pattern) { check_lengths(string, pattern) check_character(pattern) if (inherits(pattern, "stringr_pattern")) { cli::cli_abort("{.arg pattern} must be a plain string, not a stringr modifier.") } - check_bool(ignore_case) - pattern <- regex(like_to_regex(pattern), ignore_case = ignore_case) + pattern <- regex(like_to_regex(pattern), ignore_case = FALSE) + stri_detect_regex(string, pattern, opts_regex = opts(pattern)) +} + +#' Detect a pattern in the same way as `SQL`'s `ILIKE` operator +#' +#' @description +#' `str_ilike()` follows the conventions of the SQL `ILIKE` operator: +#' +#' * Must match the entire string. +#' * `_` matches a single character (like `.`). +#' * `%` matches any number of characters (like `.*`). +#' * `\%` and `\_` match literal `%` and `_`. +#' * The match is case insensitive. +#' +#' @inheritParams str_detect +#' @param pattern A character vector containing a SQL "like" pattern. +#' See above for details. +#' @return A logical vector the same length as `string`. +#' @export +#' @examples +#' fruit <- c("apple", "banana", "pear", "pineapple") +#' str_ilike(fruit, "app") +#' str_ilike(fruit, "app%") +#' str_ilike(fruit, "APP%") +#' str_ilike(fruit, "ba_ana") +#' str_ilike(fruit, "%apple") +str_ilike <- function(string, pattern) { + check_lengths(string, pattern) + check_character(pattern) + if (inherits(pattern, "stringr_pattern")) { + cli::cli_abort("{.arg pattern} must be a plain string, not a stringr modifier.") + } + + pattern <- regex(like_to_regex(pattern), ignore_case = TRUE) stri_detect_regex(string, pattern, opts_regex = opts(pattern)) } diff --git a/man/str_ilike.Rd b/man/str_ilike.Rd new file mode 100644 index 00000000..60968819 --- /dev/null +++ b/man/str_ilike.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/detect.R +\name{str_ilike} +\alias{str_ilike} +\title{Detect a pattern in the same way as \code{SQL}'s \code{ILIKE} operator} +\usage{ +str_ilike(string, pattern) +} +\arguments{ +\item{string}{Input vector. Either a character vector, or something +coercible to one.} + +\item{pattern}{A character vector containing a SQL "like" pattern. +See above for details.} +} +\value{ +A logical vector the same length as \code{string}. +} +\description{ +\code{str_ilike()} follows the conventions of the SQL \code{ILIKE} operator: +\itemize{ +\item Must match the entire string. +\item \verb{_} matches a single character (like \code{.}). +\item \verb{\%} matches any number of characters (like \verb{.*}). +\item \verb{\\\%} and \verb{\\_} match literal \verb{\%} and \verb{_}. +\item The match is case insensitive. +} +} +\examples{ +fruit <- c("apple", "banana", "pear", "pineapple") +str_ilike(fruit, "app") +str_ilike(fruit, "app\%") +str_ilike(fruit, "APP\%") +str_ilike(fruit, "ba_ana") +str_ilike(fruit, "\%apple") +} diff --git a/man/str_like.Rd b/man/str_like.Rd index b3732ef9..b7d2dc8a 100644 --- a/man/str_like.Rd +++ b/man/str_like.Rd @@ -4,7 +4,7 @@ \alias{str_like} \title{Detect a pattern in the same way as \code{SQL}'s \code{LIKE} operator} \usage{ -str_like(string, pattern, ignore_case = TRUE) +str_like(string, pattern) } \arguments{ \item{string}{Input vector. Either a character vector, or something @@ -12,9 +12,6 @@ coercible to one.} \item{pattern}{A character vector containing a SQL "like" pattern. See above for details.} - -\item{ignore_case}{Ignore case of matches? Defaults to \code{TRUE} to match -the SQL \code{LIKE} operator.} } \value{ A logical vector the same length as \code{string}. @@ -26,13 +23,14 @@ A logical vector the same length as \code{string}. \item \verb{_} matches a single character (like \code{.}). \item \verb{\%} matches any number of characters (like \verb{.*}). \item \verb{\\\%} and \verb{\\_} match literal \verb{\%} and \verb{_}. -\item The match is case insensitive by default. +\item The match is case sensitive. } } \examples{ fruit <- c("apple", "banana", "pear", "pineapple") str_like(fruit, "app") str_like(fruit, "app\%") +str_like(fruit, "APP\%") str_like(fruit, "ba_ana") -str_like(fruit, "\%APPLE") +str_like(fruit, "\%apple") } diff --git a/tests/testthat/test-detect.R b/tests/testthat/test-detect.R index ab3458db..8266823a 100644 --- a/tests/testthat/test-detect.R +++ b/tests/testthat/test-detect.R @@ -57,9 +57,16 @@ test_that("functions use tidyverse recycling rules", { test_that("str_like works", { expect_true(str_like("abc", "ab%")) + expect_false(str_like("abc", "AB%")) expect_snapshot(str_like("abc", regex("x")), error = TRUE) }) +test_that("str_ilike works", { + expect_true(str_ilike("abc", "ab%")) + expect_true(str_ilike("abc", "AB%")) + expect_snapshot(str_ilike("abc", regex("x")), error = TRUE) +}) + test_that("like_to_regex generates expected regexps",{ expect_equal(like_to_regex("ab%"), "^ab.*$") expect_equal(like_to_regex("ab_"), "^ab.$")