diff --git a/NAMESPACE b/NAMESPACE index da483518..acd9602a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -57,8 +57,11 @@ export(str_starts) export(str_sub) export(str_sub_all) export(str_subset) +export(str_to_camel) +export(str_to_kebab) export(str_to_lower) export(str_to_sentence) +export(str_to_snake) export(str_to_title) export(str_to_upper) export(str_trim) diff --git a/NEWS.md b/NEWS.md index 9b02066b..8ff273bb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # stringr (development version) +* New `str_to_camel()`, `str_to_snake()`, and `str_to_kebab()` for changing "programming" case (@librill, #573). * All relevant stringr functions now preserve names (@jonovik, #575). * New `vignette("locale-sensitive")` about locale sensitive functions (@kylieainslie, #404) * New `str_ilike()` that follows the conventions of the SQL ILIKE operator (@edward-burn, #543). diff --git a/R/case.R b/R/case.R index 565d6873..9c882010 100644 --- a/R/case.R +++ b/R/case.R @@ -55,3 +55,68 @@ str_to_sentence <- function(string, locale = "en") { ) copy_names(string, out) } + + +#' Convert between different types of programming case +#' +#' @description +#' * `str_to_camel()` converts to camel case, where the first letter of +#' each word is capitalized, with no separation between words. By default +#' the first letter of the first word is not capitalized. +#' +#' * `str_to_kebab()` converts to kebab case, where words are converted to +#' lower case and separated by dashes (`-`). +#' +#' * `str_to_snake()` converts to snake case, where words are converted to +#' lower case and separated by underscores (`_`). +#' @inheritParams str_to_lower +#' @export +#' @param first_upper Logical. Should the first letter be capitalized? +#' @examples +#' str_to_camel("my-variable") +#' str_to_camel("my-variable", first_upper = TRUE) +#' +#' str_to_snake("MyVariable") +#' str_to_kebab("MyVariable") +str_to_camel <- function(string, first_upper = FALSE) { + check_character(string) + check_bool(first_upper) + + string <- string |> + to_words() |> + str_to_title() |> + str_remove_all(pattern = "\\s+") + + if (!first_upper) { + str_sub(string, 1, 1) <- str_to_lower(str_sub(string, 1, 1)) + } + + string +} +#' @export +#' @rdname str_to_camel +str_to_snake <- function(string) { + check_character(string) + string |> + to_words() |> + str_replace_all(pattern = "\\s+", replacement = "_") +} +#' @export +#' @rdname str_to_camel +str_to_kebab <- function(string) { + check_character(string) + string |> + to_words() |> + str_replace_all(pattern = "\\s+", replacement = "-") +} + +to_words <- function(string) { + string |> + str_replace_all("([a-z])([A-Z])", "\\1 \\2") |> + str_replace_all("([a-zA-Z])([0-9])", "\\1 \\2") |> + str_replace_all("([0-9])([a-zA-Z])", "\\1 \\2") |> + str_replace_all("([A-Z]+)([A-Z][a-z])", "\\1 \\2") |> + str_to_lower() |> + str_replace_all(pattern = "[:punct:]", replacement = " ") |> + str_trim() +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 741e9dee..77b0971b 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -69,6 +69,7 @@ reference: - str_conv - str_like - str_replace_na + - str_to_camel - str_view - word diff --git a/man/str_to_camel.Rd b/man/str_to_camel.Rd new file mode 100644 index 00000000..f6516b62 --- /dev/null +++ b/man/str_to_camel.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/case.R +\name{str_to_camel} +\alias{str_to_camel} +\alias{str_to_snake} +\alias{str_to_kebab} +\title{Convert between different types of programming case} +\usage{ +str_to_camel(string, first_upper = FALSE) + +str_to_snake(string) + +str_to_kebab(string) +} +\arguments{ +\item{string}{Input vector. Either a character vector, or something +coercible to one.} + +\item{first_upper}{Logical. Should the first letter be capitalized?} +} +\description{ +\itemize{ +\item \code{str_to_camel()} converts to camel case, where the first letter of +each word is capitalized, with no separation between words. By default +the first letter of the first word is not capitalized. +\item \code{str_to_kebab()} converts to kebab case, where words are converted to +lower case and separated by dashes (\code{-}). +\item \code{str_to_snake()} converts to snake case, where words are converted to +lower case and separated by underscores (\verb{_}). +} +} +\examples{ +str_to_camel("my-variable") +str_to_camel("my-variable", first_upper = TRUE) + +str_to_snake("MyVariable") +str_to_kebab("MyVariable") +} diff --git a/tests/testthat/test-case.R b/tests/testthat/test-case.R index 94f8c651..9e29321c 100644 --- a/tests/testthat/test-case.R +++ b/tests/testthat/test-case.R @@ -10,7 +10,6 @@ test_that("to_title creates one capital letter per word", { }) test_that("to_sentence capitalizes just the first letter", { - x <- "This is a sentence." expect_identical(str_to_sentence("a Test"), "A test") }) @@ -20,3 +19,30 @@ test_that("case conversions preserve names", { expect_equal(names(str_to_upper(x)), names(x)) expect_equal(names(str_to_title(x)), names(x)) }) + +# programming cases ----------------------------------------------------------- + +test_that("to_camel can control case of first argument", { + expect_equal(str_to_camel("my_variable"), "myVariable") + expect_equal(str_to_camel("my_variable", first_upper = TRUE), "MyVariable") +}) + +test_that("to_kebab converts to kebab case", { + expect_equal(str_to_kebab("myVariable"), "my-variable") + expect_equal(str_to_kebab("MyVariable"), "my-variable") + expect_equal(str_to_kebab("MyVariable1"), "my-variable-1") +}) + +test_that("to_snake converts to snake case", { + expect_equal(str_to_snake("myVariable"), "my_variable") + expect_equal(str_to_snake("MyVariable"), "my_variable") + expect_equal(str_to_snake("MyVariable1"), "my_variable_1") +}) + +test_that("to_words handles common compound cases", { + expect_equal(to_words("a_b"), "a b") + expect_equal(to_words("a-b"), "a b") + expect_equal(to_words("aB"), "a b") + expect_equal(to_words("a123b"), "a 123 b") + expect_equal(to_words("HTML"), "html") +})