Skip to content

Instantly share code, notes, and snippets.

@erica
Last active June 17, 2020 18:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/b39da92707987f8455f25f49c4678000 to your computer and use it in GitHub Desktop.
Save erica/b39da92707987f8455f25f49c4678000 to your computer and use it in GitHub Desktop.
extension String {
/// Returns a new string with the camel-case-based words of this string
/// split by the specified separator.
///
/// Examples:
///
/// "myProperty".convertedToSnakeCase()
/// // my_property
/// "myURLProperty".convertedToSnakeCase()
/// // my_url_property
/// "myURLProperty".convertedToSnakeCase(separator: "-")
/// // my-url-property
func convertedToSnakeCase(separator: Character = "_") -> String {
guard !isEmpty else { return self }
return zip(self, indices)
.map({ (character: Character, idx: Index) -> String in
let lower = String(character).lowercased()
// Character is not uppercase or is start of string
if idx == startIndex || !character.isUppercase { return lower }
// Idx is not startIndex
let previousIdx = index(before: idx)
// An uppercase follows a non-uppercase/non-separator like P in myProperty, insert separator
if previousIdx != startIndex && !self[previousIdx].isUppercase && self[previousIdx] != separator {
return String(separator) + lower
}
let nextIdx = index(after: idx)
// Final letter _or_ next is uppercase, like L in myURL or R in myURLProperty, do not separate
if nextIdx == endIndex || self[nextIdx].isUppercase {
return lower
}
// Next is _not_ uppercase, previous _is_ uppercase, like P in myURLProperty. Insert separator
if self[previousIdx].isUppercase {
return String(separator) + lower
}
return lower
}).reduce("", +)
}
}
extension String {
/// Returns a new string with the camel-case-based words of this string
/// split by the specified separator.
///
/// Examples:
///
/// "myProperty".convertedToSnakeCase()
/// // my_property
/// "myURLProperty".convertedToSnakeCase()
/// // my_url_property
/// "myURLProperty".convertedToSnakeCase(separator: "-")
/// // my-url-property
func convertedToSnakeCase(separator: Character = "_") -> String {
guard !isEmpty else { return self }
var result = ""
for (character, idx) in zip(self, indices) {
let lower = String(character).lowercased()
// Character is not uppercase or is start of string
if idx == startIndex || !character.isUppercase {
result += lower
continue
}
// An uppercase follows a non-uppercase/non-separator like P in myProperty, insert separator
if !self[index(before: idx)].isUppercase &&
self[index(before: idx)] != separator {
result += String(separator) + lower
continue
}
let nextIdx = index(after: idx)
// Final letter _or_ next is uppercase, like L in myURL or R in myURLProperty, do not separate
if nextIdx == endIndex || self[nextIdx].isUppercase {
result += lower
continue
}
// Next is _not_ uppercase, previous _is_ uppercase, like P in myURLProperty. Insert separator
if self[index(before: idx)].isUppercase {
result += String(separator) + lower
continue
}
result += lower
}
return result
}
}
print("m_P_r_operty".convertedToSnakeCase(),
"myProperty".convertedToSnakeCase(),
"myURLProperty".convertedToSnakeCase(),
"myURLProperty".convertedToSnakeCase(separator: "-"))
extension String {
/// Returns a new string with the camel-case-based words of this string
/// split by the specified separator.
///
/// Examples:
///
/// "myProperty".convertedToSnakeCase()
/// // my_property
/// "myURLProperty".convertedToSnakeCase()
/// // my_url_property
/// "myURLProperty".convertedToSnakeCase(separator: "-")
/// // my-url-property
func convertedToSnakeCase(separator: Character = "_") -> String {
guard !isEmpty else { return self }
var result = ""
// Whether we should append a separator when we see a uppercase character.
var separateOnUppercase = true
for index in indices {
let nextIndex = self.index(after: index)
let character = self[index]
if character.isUppercase {
if separateOnUppercase && !result.isEmpty {
// Append the separator.
result += "\(separator)"
}
// If the next character is uppercase and the next-next character is lowercase, like "L" in "URLSession", we should separate words.
separateOnUppercase = nextIndex < endIndex && self[nextIndex].isUppercase && self.index(after: nextIndex) < endIndex && self[self.index(after: nextIndex)].isLowercase
} else {
// If the character is `separator`, we do not want to append another separator when we see the next uppercase character.
separateOnUppercase = character != separator
}
// Append the lowercased character.
result += character.lowercased()
}
return result
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment