Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ S3method(type,stringr_fixed)
S3method(type,stringr_regex)
export("%>%")
export("str_sub<-")
export(StrToPascal)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I see them, I don't think it's worth having the camel case variants; it feels a bit too clever.

export(boundary)
export(coll)
export(fixed)
export(invert_match)
export(regex)
export(strToCamel)
export(str_c)
export(str_conv)
export(str_count)
Expand Down Expand Up @@ -57,8 +59,12 @@ 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_pascal)
export(str_to_sentence)
export(str_to_snake)
export(str_to_title)
export(str_to_upper)
export(str_trim)
Expand Down
69 changes: 68 additions & 1 deletion R/case.R
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
#' Convert string to upper case, lower case, title case, or sentence case
#' Convert string to upper case, lower case, title case, sentence case, pascal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to document these new functions in a new documentation topic? Yes, they're are changing case, but you're more likely to be apply them to function names, not regular sentences.

#' case, camel case, kebab case, or snake case
#'
#' * `str_to_upper()` converts to upper case.
#' * `str_to_lower()` converts to lower case.
#' * `str_to_title()` converts to title case, where only the first letter of
#' each word is capitalized.
#' * `str_to_sentence()` convert to sentence case, where only the first letter
#' of sentence is capitalized.
#' * `str_to_pascal()` converts to pascal case, where only the first letter of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure Pascal case is particularly well known (at least I didn't recognise it) so how about it make it an argument to str_camel_case(), e.g. first_upper = TRUE?

#' each word is capitalized, with no separation between
#' words.
#' * `str_to_pascal()` and `StrToPascal()` are synonyms
#' * `str_to_camel()` converts to camel case, where only the first letter of
#' each word after the first word is capitalized, with no separation between
#' words.
#' * `str_to_camel()` and `strToCamel()` are synonyms
#' * `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_detect
#' @inheritParams coll
#' @param separator string. For snake case, can change the `_`
#' @return A character vector the same length as `string`.
#' @examples
#' dog <- "The quick brown dog"
#' str_to_upper(dog)
#' str_to_lower(dog)
#' str_to_title(dog)
#' str_to_sentence("the quick brown dog")
#' str_to_pascal(dog)
#' StrToPascal(dog)
#' str_to_camel(dog)
#' strToCamel(dog)
#' str_to_kebab(dog)
#' str_to_snake(dog)
#'
#' # Locale matters!
#' str_to_upper("i") # English
Expand Down Expand Up @@ -54,3 +74,50 @@ str_to_sentence <- function(string, locale = "en") {
opts_brkiter = stri_opts_brkiter(type = "sentence", locale = locale)
)
}
#' @export
#' @rdname case
str_to_pascal <- function(string, locale = "en") {
stopifnot(is.character(string))
string <- string |>
str_replace_all("([a-z])([A-Z])", "\\1 \\2") |>
str_replace_all("([0-9])([a-zA-Z])", "\\1 \\2") |>
str_replace_all("([A-Z]+)([A-Z][a-z])", "\\1 \\2") |>
str_replace_all(pattern = "[:punct:]", replace = " ") |>
str_to_title(locale = locale) |>
str_remove_all(pattern = "\\s+")
return(string)
}
#' @export
#' @rdname case
StrToPascal <- str_to_pascal
#' @export
#' @rdname case
str_to_camel <- function(string, locale = "en") {
string <- str_to_pascal(string, locale = locale)
string <- str_replace(string, pattern = "^.", replace = str_to_lower(str_sub(string, 1, 1)))
return(string)
}
#' @export
#' @rdname case
strToCamel <- str_to_camel
#' @export
#' @rdname case
str_to_snake <- function(string, separator = "_", locale = "en") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could eliminate the separator argument by creating an internal function that both snake and kebab case uses.

stopifnot(is.character(string))
string <- string |>
str_replace_all("([a-z])([A-Z])", "\\1 \\2") |>
str_replace_all("([a-zA-Z])([0-9])", "\\1 \\2") |>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to extract out this code for "break into words" into a separate function since it's reasonably complex and giving it a name would be useful.

str_replace_all("([0-9])([a-zA-Z])", "\\1 \\2") |>
str_replace_all("([A-Z]+)([A-Z][a-z])", "\\1 \\2") |>
str_to_lower(locale = locale) |>
str_replace_all(pattern = "[:punct:]", replace = " ") |>
str_trim() |>
str_replace_all(pattern = "\\s+", replace = separator)
return(string)
}
#' @export
#' @rdname case
str_to_kebab <- function(string, locale = "en") {
string <- str_to_snake(string, separator = "-", locale = locale)
return(string)
}
41 changes: 40 additions & 1 deletion man/case.Rd

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

21 changes: 20 additions & 1 deletion tests/testthat/test-case.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ 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")
})

test_that("to_pascal converts to pascal case", {
expect_identical(str_to_pascal("This is a sentence."), "ThisIsASentence")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest testing cases the exercise each of the different regular expressions. I think that will be easier once you extract out a common "break into words" function.

})

test_that("to_camel converts to camel casee", {
expect_identical(str_to_camel("This is a sentence."), "thisIsASentence")
})

test_that("to_kebab converts to kebab case", {
expect_identical(str_to_kebab("This is a sentence."), "this-is-a-sentence")
})

test_that("to_snake converts to snake case", {
expect_identical(str_to_snake("This is a sentence."), "this_is_a_sentence")
})

test_that("to_snake converts to snake case", {
expect_identical(str_to_snake("This is a sentence.", separator = "!"), "this!is!a!sentence")
})
1 change: 1 addition & 0 deletions vignettes/stringr.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ A handful of stringr functions are locale-sensitive: they will perform different
x <- "I like horses."
str_to_upper(x)
str_to_title(x)
str_to_snake(x) # case transformation for programming

str_to_lower(x)
# Turkish has two sorts of i: with and without the dot
Expand Down
Loading