| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- //===----------------------------------------------------------------------===//
- //
- // This source file is part of the Swift Argument Parser open source project
- //
- // Copyright (c) 2020 Apple Inc. and the Swift project authors
- // Licensed under Apache License v2.0 with Runtime Library Exception
- //
- // See https://swift.org/LICENSE.txt for license information
- //
- //===----------------------------------------------------------------------===//
- extension StringProtocol where SubSequence == Substring {
- func wrapped(to columns: Int, wrappingIndent: Int = 0) -> String {
- let columns = columns - wrappingIndent
- guard columns > 0 else {
- // Skip wrapping logic if the number of columns is less than 1 in release
- // builds and assert in debug builds.
- assertionFailure(
- "`columns - wrappingIndent` should be always be greater than 0.")
- return ""
- }
- var result: [Substring] = []
- var currentIndex = startIndex
- while true {
- let nextChunk = self[currentIndex...].prefix(columns)
- if let lastLineBreak = nextChunk.lastIndex(of: "\n") {
- result.append(
- contentsOf: self[currentIndex..<lastLineBreak].split(
- separator: "\n", omittingEmptySubsequences: false))
- currentIndex = index(after: lastLineBreak)
- } else if nextChunk.endIndex == self.endIndex {
- result.append(self[currentIndex...])
- break
- } else if let lastSpace = nextChunk.lastIndex(of: " ") {
- result.append(self[currentIndex..<lastSpace])
- currentIndex = index(after: lastSpace)
- } else if let nextSpace = self[currentIndex...].firstIndex(of: " ") {
- result.append(self[currentIndex..<nextSpace])
- currentIndex = index(after: nextSpace)
- } else {
- result.append(self[currentIndex...])
- break
- }
- }
- return
- result
- .map {
- $0.isEmpty ? $0 : String(repeating: " ", count: wrappingIndent) + $0
- }
- .joined(separator: "\n")
- }
- /// Returns this string prefixed using a camel-case style.
- ///
- /// Example:
- ///
- /// "hello".addingIntercappedPrefix("my")
- /// // myHello
- func addingIntercappedPrefix(_ prefix: String) -> String {
- guard let firstChar = first else { return prefix }
- return "\(prefix)\(firstChar.uppercased())\(self.dropFirst())"
- }
- /// Returns this string prefixed using kebab-, snake-, or camel-case style
- /// depending on what can be detected from the string.
- ///
- /// Examples:
- ///
- /// "hello".addingPrefixWithAutodetectedStyle("my")
- /// // my-hello
- /// "hello_there".addingPrefixWithAutodetectedStyle("my")
- /// // my_hello_there
- /// "hello-there".addingPrefixWithAutodetectedStyle("my")
- /// // my-hello-there
- /// "helloThere".addingPrefixWithAutodetectedStyle("my")
- /// // myHelloThere
- func addingPrefixWithAutodetectedStyle(_ prefix: String) -> String {
- if contains("-") {
- return "\(prefix)-\(self)"
- } else if contains("_") {
- return "\(prefix)_\(self)"
- } else if first?.isLowercase == true && contains(where: { $0.isUppercase })
- {
- return addingIntercappedPrefix(prefix)
- } else {
- return "\(prefix)-\(self)"
- }
- }
- /// 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 "" }
- 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
- }
- /// Returns the edit distance between this string and the provided target string.
- ///
- /// Uses the Levenshtein distance algorithm internally.
- ///
- /// See: https://en.wikipedia.org/wiki/Levenshtein_distance
- ///
- /// Examples:
- ///
- /// "kitten".editDistance(to: "sitting")
- /// // 3
- /// "bar".editDistance(to: "baz")
- /// // 1
- func editDistance(to target: String) -> Int {
- let rows = self.count
- let columns = target.count
- if rows <= 0 || columns <= 0 {
- return Swift.max(rows, columns)
- }
- // Trim common prefix and suffix
- var selfStartTrim = self.startIndex
- var targetStartTrim = target.startIndex
- while selfStartTrim < self.endIndex && targetStartTrim < target.endIndex
- && self[selfStartTrim] == target[targetStartTrim]
- {
- self.formIndex(after: &selfStartTrim)
- target.formIndex(after: &targetStartTrim)
- }
- var selfEndTrim = self.endIndex
- var targetEndTrim = target.endIndex
- while selfEndTrim > selfStartTrim && targetEndTrim > targetStartTrim {
- let selfIdx = self.index(before: selfEndTrim)
- let targetIdx = target.index(before: targetEndTrim)
- guard self[selfIdx] == target[targetIdx] else {
- break
- }
- selfEndTrim = selfIdx
- targetEndTrim = targetIdx
- }
- // Equal strings
- guard
- !(selfStartTrim == self.endIndex && targetStartTrim == target.endIndex)
- else {
- return 0
- }
- // After trimming common prefix and suffix, self is empty.
- guard selfStartTrim < selfEndTrim else {
- return target.distance(
- from: targetStartTrim,
- to: targetEndTrim)
- }
- // After trimming common prefix and suffix, target is empty.
- guard targetStartTrim < targetEndTrim else {
- return distance(
- from: selfStartTrim,
- to: selfEndTrim)
- }
- let newSelf = self[selfStartTrim..<selfEndTrim]
- let newTarget = target[targetStartTrim..<targetEndTrim]
- let m = newSelf.count
- let n = newTarget.count
- // Initialize the levenshtein matrix with only two rows
- // current and previous.
- var previousRow = [Int](repeating: 0, count: n + 1)
- var currentRow = [Int](0...n)
- var sourceIdx = newSelf.startIndex
- for i in 1...m {
- swap(&previousRow, ¤tRow)
- currentRow[0] = i
- var targetIdx = newTarget.startIndex
- for j in 1...n {
- // If characteres are equal for the levenshtein algorithm the
- // minimum will always be the substitution cost, so we can fast
- // path here in order to avoid min calls.
- if newSelf[sourceIdx] == newTarget[targetIdx] {
- currentRow[j] = previousRow[j - 1]
- } else {
- let deletion = previousRow[j]
- let insertion = currentRow[j - 1]
- let substitution = previousRow[j - 1]
- currentRow[j] =
- Swift.min(deletion, Swift.min(insertion, substitution)) + 1
- }
- // j += 1
- newTarget.formIndex(after: &targetIdx)
- }
- // i += 1
- newSelf.formIndex(after: &sourceIdx)
- }
- return currentRow[n]
- }
- func indentingEachLine(by n: Int) -> String {
- let lines = self.split(separator: "\n", omittingEmptySubsequences: false)
- let spacer = String(repeating: " ", count: n)
- return lines.map {
- $0.isEmpty ? $0 : spacer + $0
- }.joined(separator: "\n")
- }
- func hangingIndentingEachLine(by n: Int) -> String {
- let lines = self.split(
- separator: "\n",
- maxSplits: 1,
- omittingEmptySubsequences: false)
- guard lines.count == 2 else { return lines.joined(separator: "") }
- return "\(lines[0])\n\(lines[1].indentingEachLine(by: n))"
- }
- var nonEmpty: Self? {
- isEmpty ? nil : self
- }
- }
|