Add string utils
This commit is contained in:
parent
9fe09382b5
commit
7d9a661080
Binary file not shown.
|
@ -1,6 +1,15 @@
|
|||
# Release notes
|
||||
|
||||
|
||||
## 1.1
|
||||
|
||||
### ✨ New features
|
||||
|
||||
* `String+Characters` contains single-char characters like `newLine` and `tab`.
|
||||
* `String+Subscript` contains functionality for accessing chars in a String.
|
||||
|
||||
|
||||
|
||||
## 1.0
|
||||
|
||||
I think it's finally time to push the major release button.
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// String+Characters.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2021-11-29.
|
||||
// Copyright © 2021 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension String.Element {
|
||||
|
||||
static var carriageReturn: String.Element { "\r" }
|
||||
static var newLine: String.Element { "\n" }
|
||||
static var tab: String.Element { "\t" }
|
||||
}
|
||||
|
||||
|
||||
public extension String {
|
||||
|
||||
static let newLine = String(.newLine)
|
||||
static let tab = String(.tab)
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// String+Paragraph.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2021-11-29.
|
||||
// Copyright © 2021 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension String {
|
||||
|
||||
/**
|
||||
Backs to find the index of the first new line paragraph
|
||||
before the provided location, if any.
|
||||
|
||||
A new paragraph is considered to start at the character
|
||||
after the newline char, not the newline itself.
|
||||
*/
|
||||
func findIndexOfCurrentParagraph(from location: UInt) -> UInt {
|
||||
if isEmpty { return 0 }
|
||||
let count = UInt(count)
|
||||
var index = min(location, count-1)
|
||||
repeat {
|
||||
guard index > 0, index < count else { break }
|
||||
guard let char = character(at: index - 1) else { break }
|
||||
if char == .newLine || char == .carriageReturn { break }
|
||||
index -= 1
|
||||
} while true
|
||||
return max(index, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
Looks forward to find the next new line paragraph after
|
||||
the provided location, if any. If no next paragraph can
|
||||
be found, the current is returned.
|
||||
|
||||
A new paragraph is considered to start at the character
|
||||
after the newline char, not the newline itself.
|
||||
*/
|
||||
func findIndexOfNextParagraph(from location: UInt) -> UInt {
|
||||
var index = location
|
||||
repeat {
|
||||
guard let char = character(at: index) else { break }
|
||||
index += 1
|
||||
guard index < count else { break }
|
||||
if char == .newLine || char == .carriageReturn { break }
|
||||
} while true
|
||||
let found = index < count
|
||||
return found ? index : findIndexOfCurrentParagraph(from: location)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// String+Subscript.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2021-11-29.
|
||||
// Copyright © 2021 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This extension makes it possible to fetch characters from a
|
||||
string, as discussed here:
|
||||
|
||||
https://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift-programming-language
|
||||
*/
|
||||
public extension StringProtocol {
|
||||
|
||||
func character(at index: Int) -> String.Element? {
|
||||
guard count > index else { return nil }
|
||||
return self[index]
|
||||
}
|
||||
|
||||
func character(at index: UInt) -> String.Element? {
|
||||
character(at: Int(index))
|
||||
}
|
||||
|
||||
subscript(_ offset: Int) -> Element {
|
||||
self[index(startIndex, offsetBy: offset)]
|
||||
}
|
||||
|
||||
subscript(_ range: Range<Int>) -> SubSequence {
|
||||
prefix(range.lowerBound+range.count).suffix(range.count)
|
||||
}
|
||||
|
||||
subscript(_ range: ClosedRange<Int>) -> SubSequence {
|
||||
prefix(range.lowerBound+range.count).suffix(range.count)
|
||||
}
|
||||
|
||||
subscript(_ range: PartialRangeThrough<Int>) -> SubSequence {
|
||||
prefix(range.upperBound.advanced(by: 1))
|
||||
}
|
||||
|
||||
subscript(_ range: PartialRangeUpTo<Int>) -> SubSequence {
|
||||
prefix(range.upperBound)
|
||||
}
|
||||
|
||||
subscript(_ range: PartialRangeFrom<Int>) -> SubSequence {
|
||||
suffix(Swift.max(0, count-range.lowerBound))
|
||||
}
|
||||
}
|
||||
|
||||
private extension LosslessStringConvertible {
|
||||
|
||||
var string: String { .init(self) }
|
||||
}
|
||||
|
||||
private extension BidirectionalCollection {
|
||||
|
||||
subscript(safe offset: Int) -> Element? {
|
||||
if isEmpty { return nil }
|
||||
guard let index = index(
|
||||
startIndex,
|
||||
offsetBy: offset,
|
||||
limitedBy: index(before: endIndex))
|
||||
else { return nil }
|
||||
return self[index]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// String+ParagraphTests.swift
|
||||
// SwiftKitTests
|
||||
//
|
||||
// Created by Daniel Saidi on 2021-11-29.
|
||||
// Copyright © 2021 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
import SwiftKit
|
||||
|
||||
class String_ParagraphTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
let none = "foo bar baz"
|
||||
let single = "foo\nbar baz"
|
||||
let multi = "foo\nbar\rbaz"
|
||||
|
||||
describe("index of current paragraph") {
|
||||
|
||||
func result(for string: String, from location: UInt) -> UInt {
|
||||
string.findIndexOfCurrentParagraph(from: location)
|
||||
}
|
||||
|
||||
it("is correct for empty string") {
|
||||
expect(result(for: "", from: 0)).to(equal(0))
|
||||
expect(result(for: "", from: 20)).to(equal(0))
|
||||
}
|
||||
|
||||
it("is correct for string with no previous paragraph") {
|
||||
expect(result(for: none, from: 0)).to(equal(0))
|
||||
expect(result(for: none, from: 10)).to(equal(0))
|
||||
expect(result(for: none, from: 20)).to(equal(0))
|
||||
}
|
||||
|
||||
it("is correct for string with single previous paragraph") {
|
||||
expect(result(for: single, from: 0)).to(equal(0))
|
||||
expect(result(for: single, from: 5)).to(equal(4))
|
||||
expect(result(for: single, from: 10)).to(equal(4))
|
||||
}
|
||||
|
||||
it("is correct for string with multiple previous paragraphs") {
|
||||
expect(result(for: multi, from: 0)).to(equal(0))
|
||||
expect(result(for: multi, from: 5)).to(equal(4))
|
||||
expect(result(for: multi, from: 10)).to(equal(8))
|
||||
}
|
||||
}
|
||||
|
||||
describe("index of next paragraph") {
|
||||
|
||||
func result(for string: String, from location: UInt) -> UInt {
|
||||
string.findIndexOfNextParagraph(from: location)
|
||||
}
|
||||
|
||||
it("is correct for empty string") {
|
||||
expect(result(for: "", from: 0)).to(equal(0))
|
||||
expect(result(for: "", from: 20)).to(equal(0))
|
||||
}
|
||||
|
||||
it("is correct for string with no next paragraph") {
|
||||
expect(result(for: none, from: 0)).to(equal(0))
|
||||
expect(result(for: none, from: 10)).to(equal(0))
|
||||
expect(result(for: none, from: 20)).to(equal(0))
|
||||
}
|
||||
|
||||
it("is correct for string with single next paragraph") {
|
||||
expect(result(for: single, from: 0)).to(equal(4))
|
||||
expect(result(for: single, from: 5)).to(equal(4))
|
||||
expect(result(for: single, from: 10)).to(equal(4))
|
||||
}
|
||||
|
||||
it("is correct for string with multiple next paragraphs") {
|
||||
expect(result(for: multi, from: 0)).to(equal(4))
|
||||
expect(result(for: multi, from: 5)).to(equal(8))
|
||||
expect(result(for: multi, from: 10)).to(equal(8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue