Last active
June 23, 2023 15:53
-
-
Save jakehawken/ddfdd2fd66f91f27f3a5e7194df79ded to your computer and use it in GitHub Desktop.
Some handy tools for making things into more human-readable strings. Helpful for debugging!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
protocol PrettyPrintable { | |
func prettyPrinted() -> String | |
func prettyPrintToConsole() | |
} | |
extension PrettyPrintable { | |
func prettyPrintToConsole() { | |
let pretty = prettyPrinted() | |
print("\n\(pretty)\n") | |
} | |
} | |
//MARK: - Common type conformances to PrettyPrintable | |
extension String: PrettyPrintable { | |
func prettyPrinted() -> String { | |
let truncated = self.truncateIfNeeded(maxLength: 40, trailing: "...") | |
return "\"\(truncated)\"" | |
} | |
} | |
extension Array: PrettyPrintable { | |
func prettyPrinted() -> String { | |
var outputString = "[" | |
forEach { | |
outputString += "\n" | |
if let prettyPrintable = $0 as? PrettyPrintable { | |
outputString += "\(prettyPrintable.prettyPrinted())" | |
} | |
else if let dict = $0 as? [String: Any] { | |
outputString += dict.prettyPrinted() | |
} | |
else if let encodable = $0 as? Encodable { | |
outputString += encodable.asDictionary()?.prettyPrinted() ?? "\(encodable)" | |
} | |
else { | |
outputString += "\($0)" | |
} | |
outputString += "," | |
} | |
outputString -= "," | |
outputString = outputString.replacingOccurrences(of: "\n", with: "\n ") | |
outputString += "\n]" | |
return outputString | |
} | |
} | |
extension Dictionary: PrettyPrintable where Key==String { | |
func prettyPrinted() -> String { | |
var outputString = "{" | |
for key in keys { | |
let value = self[key] | |
var valueString: String | |
if let prettyPrintable = value as? PrettyPrintable { | |
valueString = prettyPrintable.prettyPrinted() | |
} | |
else if let arrayValue = value as? [Any] { | |
valueString = arrayValue.prettyPrinted() | |
} | |
else if let stringVal = value as? String { | |
valueString = stringVal.prettyPrinted() | |
} | |
else { | |
valueString = (value == nil) ? "nil" : "\(value!)" | |
let components = valueString.components(separatedBy: "\n") | |
if components.count == 1 { | |
valueString = valueString.truncateIfNeeded(maxLength: 40, trailing: "...") | |
} | |
else { | |
let truncatedComponents = components.map { | |
$0.truncateIfNeeded(maxLength: 40, trailing: "...") | |
} | |
valueString = truncatedComponents.joined(separator: "\n") | |
} | |
} | |
outputString += "\n\(key) : \(valueString)," | |
} | |
outputString -= "," | |
// This ensures that the correct indentation is added at all recursive levels. | |
outputString = outputString.replacingOccurrences(of: "\n", with: "\n ") | |
outputString += "\n}" | |
return outputString | |
} | |
} | |
//MARK: - Quick translation of Encodable objects to Dictionaries | |
extension Encodable { | |
func asDictionary() -> [String: Any]? { | |
let jsonEncoder = JSONEncoder() | |
guard let data = try? jsonEncoder.encode(self) else { | |
return nil | |
} | |
guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { | |
return nil | |
} | |
return json as? [String: Any] | |
} | |
} | |
//MARK: - Class for pretty-printing anything. | |
class PrettyMaker { | |
class func makePrettyIfPossible(_ thing: Any) -> String { | |
var prettyString: String? | |
if let arrayOfAny = thing as? [Any] { | |
prettyString = arrayOfAny.prettyPrinted() | |
} | |
else if let encodableThing = thing as? Encodable { | |
prettyString = encodableThing.asDictionary()?.prettyPrinted() | |
} | |
else if let prettyPrintable = thing as? PrettyPrintable { | |
return prettyPrintable.prettyPrinted() | |
} | |
else { | |
let whatIsIt = String(describing: type(of: self)) | |
print("Can't pretty print for type: \(whatIsIt)") | |
} | |
return prettyString ?? "\(thing)" | |
} | |
} | |
//MARK: - some syntactic sugar for string manipulation | |
fileprivate extension String { | |
fileprivate static func -=(lhs: inout String, rhs: String) { | |
guard lhs.hasSuffix(rhs) else { | |
return | |
} | |
guard let range = lhs.range(of: rhs, options: [.backwards], range: nil, locale: nil) else { | |
return | |
} | |
lhs = lhs.replacingCharacters(in: range, with: "") | |
} | |
fileprivate func truncateIfNeeded(maxLength: Int, trailing: String = "") -> String { | |
if self.count > maxLength { | |
return truncate(maxLength: maxLength)! + trailing | |
} else { | |
return self | |
} | |
} | |
fileprivate func truncate(maxLength: Int, encoding: String.Encoding? = nil) -> String? { | |
guard maxLength >= 0 else { | |
return nil | |
} | |
let encoding = encoding ?? self.smallestEncoding | |
var bytes = [UInt8](repeating: 0, count: maxLength) | |
var remaining = self.startIndex..<self.endIndex | |
var usedLength = 0 | |
_ = self.getBytes(&bytes, maxLength: maxLength, | |
usedLength: &usedLength, | |
encoding: encoding, | |
options: .externalRepresentation, | |
range: self.startIndex..<self.endIndex, remaining: &remaining) | |
return String(bytes: bytes, encoding: encoding) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment