Skip to content

Commit

Permalink
Replaced String parameters and extensions with StringProtocol
Browse files Browse the repository at this point in the history
  • Loading branch information
orchetect committed Jul 25, 2022
1 parent e36bede commit 81efd57
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 292 deletions.
81 changes: 20 additions & 61 deletions Sources/SwiftASCII/ASCIICharacter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Foundation
/// A type containing a `Character` instance that is guaranteed to conform to ASCII encoding.
/// Offers a validating `exactly: Character` failable initializer and a `_ lossy: Character` conversion initializer.
public struct ASCIICharacter: Hashable {

/// The ASCII character returned as a `Character`
public let characterValue: Character

Expand All @@ -18,28 +17,23 @@ public struct ASCIICharacter: Hashable {

/// The ASCII character encoded as raw Data
public var rawData: Data {

Data([asciiValue])

}

/// Returns a new `ASCIICharacter` instance if the source character is a valid ASCII character.
@inlinable
public init?(exactly source: Character) {

guard let getASCIIValue = source.asciiValue else {
return nil
}

self.characterValue = source
self.asciiValue = getASCIIValue

}

/// Returns a new `ASCIICharacter` instance from the source character, converting a non-ASCII character to its closest ASCII equivalent if necessary.
@inlinable
public init(_ lossy: Character) {

guard let getASCIIValue = lossy.asciiValue else {
// if ASCII encoding fails, fall back to a default character instead of throwing an exception

Expand All @@ -54,14 +48,12 @@ public struct ASCIICharacter: Hashable {

self.characterValue = lossy
self.asciiValue = getASCIIValue

}

/// Returns a new `ASCIICharacter` instance if the source string contains a single character and the character is a valid ASCII character.
@_disfavoredOverload
@inlinable
public init?(exactly source: String) {

public init?<S: StringProtocol>(exactly source: S) {
guard source.count == 1,
let char = source.first
else { return nil }
Expand All @@ -72,24 +64,20 @@ public struct ASCIICharacter: Hashable {

self.characterValue = char
self.asciiValue = getASCIIValue

}

/// Returns a new `ASCIICharacter` instance if the source string contains a single character, converting a non-ASCII character to its closest ASCII equivalent if necessary.
@inlinable
public init(_ lossy: String) {

public init<S: StringProtocol>(_ lossy: S) {
let char: Character = lossy.first ?? "?"

self.init(char)

}

/// Returns a new `ASCIICharacter` instance if the source data is a single ASCII character.
/// Returns `nil` if the source data is not a single byte or if it contains a non-ASCII character byte.
@inlinable
public init?(exactly source: Data) {

guard source.count == 1 else { return nil }

guard let string = String(data: source, encoding: .nonLossyASCII) else {
Expand All @@ -102,58 +90,43 @@ public struct ASCIICharacter: Hashable {

self.characterValue = Character(scalar)
self.asciiValue = UInt8(ascii: scalar)

}

/// Returns a new `ASCIICharacter` instance from an ASCII character number.
/// Returns `nil` if the number is not within the valid ASCII table (0..<128).
@inlinable
public init?<T: BinaryInteger>(_ asciiValue: T) {

guard let getASCIIValue = UInt8(exactly: asciiValue) else { return nil }

self.init(exactly: Data([getASCIIValue]))

}

}

extension ASCIICharacter: ExpressibleByExtendedGraphemeClusterLiteral {

public typealias ExtendedGraphemeClusterLiteralType = Character

public init(extendedGraphemeClusterLiteral value: Character) {

self.init(value)

}

}

extension ASCIICharacter: CustomStringConvertible {

public var description: String {

// If not a printable character, return an empty string and don't allow any non-printable ASCII control characters through

(32...126).contains(asciiValue)
(32 ... 126).contains(asciiValue)
? String(characterValue)
: ""

: "?"
}

}

extension ASCIICharacter: CustomDebugStringConvertible {

public var debugDescription: String {
"ASCIICharacter(#\(asciiValue): \"" + description + "\")"
}

}

extension ASCIICharacter: Equatable {

// Self & Self

public static func == (lhs: Self, rhs: Self) -> Bool {
Expand Down Expand Up @@ -183,59 +156,45 @@ extension ASCIICharacter: Equatable {
public static func != (lhs: Character, rhs: Self) -> Bool {
lhs != rhs.characterValue
}

}

extension ASCIICharacter: Codable {

public init(from decoder: Decoder) throws {

let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
guard let newInstance = Self(exactly: string) else {
throw DecodingError.dataCorrupted(
.init(codingPath: container.codingPath,
debugDescription: "Value was not valid ASCII character number.")
.init(
codingPath: container.codingPath,
debugDescription: "Value was not valid ASCII character number."
)
)
}
self = newInstance

}

public func encode(to encoder: Encoder) throws {

var container = encoder.singleValueContainer()

try container.encode(String(characterValue))

}

}

extension ASCIICharacter {

public static func + (lhs: ASCIICharacter, rhs: ASCIICharacter) -> ASCIIString {

ASCIIString([lhs, rhs])

}

public static func + (lhs: ASCIICharacter, rhs: ASCIIString) -> ASCIIString {

ASCIIString(lhs) + rhs

}

public static func + (lhs: ASCIIString, rhs: ASCIICharacter) -> ASCIIString {

lhs + ASCIIString(rhs)

}

}

extension ASCIICharacter {

/// Convenience: initialize a `ASCIICharacter` instance.
public static func exactly(_ source: Character) -> ASCIICharacter? {
Self(exactly: source)
Expand All @@ -247,12 +206,12 @@ extension ASCIICharacter {
}

/// Convenience: initialize a `ASCIICharacter` instance.
public static func exactly(_ source: String) -> ASCIICharacter? {
public static func exactly<S: StringProtocol>(_ source: S) -> ASCIICharacter? {
Self(exactly: source)
}

/// Convenience: initialize a `ASCIICharacter` instance.
public static func lossy(_ source: String) -> ASCIICharacter {
public static func lossy<S: StringProtocol>(_ source: S) -> ASCIICharacter {
Self(source)
}

Expand All @@ -265,25 +224,25 @@ extension ASCIICharacter {
public static func exactly<T: BinaryInteger>(_ value: T) -> ASCIICharacter? {
Self(value)
}

}

extension Sequence where Element == ASCIICharacter {

/// Returns a new string by concatenating the elements of the sequence.
public func joined() -> ASCIIString {

ASCIIString(self)

}

/// Returns a new string by concatenating the elements of the sequence, adding the given separator between each element.
public func joined(separator: ASCIIString) -> ASCIIString {

let joinedStr = map { "\($0.characterValue)" }.joined(separator: separator.stringValue)
let joinedData = Data(map { $0.rawData }.joined(separator: separator.rawData))
return ASCIIString(guaranteedASCII: joinedStr, rawData: joinedData)

let joinedStr = map { "\($0.characterValue)" }
.joined(separator: separator.stringValue)
let joinedData = Data(
map { $0.rawData }
.joined(separator: separator.rawData)
)
return ASCIIString(
guaranteedASCII: joinedStr,
rawData: joinedData
)
}

}
Loading

0 comments on commit 81efd57

Please sign in to comment.