Add string utils

This commit is contained in:
Daniel Saidi 2021-11-29 17:05:36 +01:00
parent 9fe09382b5
commit 7d9a661080
6 changed files with 234 additions and 0 deletions

Binary file not shown.

View File

@ -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.

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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]
}
}

View File

@ -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))
}
}
}
}