|
| 1 | +import CustomDump |
1 | 2 | import Foundation |
2 | 3 |
|
3 | | -func debugOutput(_ value: Any, indent: Int = 0) -> String { |
4 | | - var visitedItems: Set<ObjectIdentifier> = [] |
5 | | - |
6 | | - func debugOutputHelp(_ value: Any, indent: Int = 0) -> String { |
7 | | - let mirror = Mirror(reflecting: value) |
8 | | - switch (value, mirror.displayStyle) { |
9 | | - case let (value as CustomDebugOutputConvertible, _): |
10 | | - return value.debugOutput.indent(by: indent) |
11 | | - case (_, .collection?): |
12 | | - return """ |
13 | | - [ |
14 | | - \(mirror.children.map { "\(debugOutput($0.value, indent: 2)),\n" }.joined())] |
15 | | - """ |
16 | | - .indent(by: indent) |
17 | | - |
18 | | - case (_, .dictionary?): |
19 | | - let pairs = mirror.children.map { label, value -> String in |
20 | | - let pair = value as! (key: AnyHashable, value: Any) |
21 | | - return |
22 | | - "\("\(debugOutputHelp(pair.key.base)): \(debugOutputHelp(pair.value)),".indent(by: 2))\n" |
23 | | - } |
24 | | - return """ |
25 | | - [ |
26 | | - \(pairs.sorted().joined())] |
27 | | - """ |
28 | | - .indent(by: indent) |
29 | | - |
30 | | - case (_, .set?): |
31 | | - return """ |
32 | | - Set([ |
33 | | - \(mirror.children.map { "\(debugOutputHelp($0.value, indent: 2)),\n" }.sorted().joined())]) |
34 | | - """ |
35 | | - .indent(by: indent) |
36 | | - |
37 | | - case (_, .optional?): |
38 | | - return mirror.children.isEmpty |
39 | | - ? "nil".indent(by: indent) |
40 | | - : debugOutputHelp(mirror.children.first!.value, indent: indent) |
41 | | - |
42 | | - case (_, .enum?) where !mirror.children.isEmpty: |
43 | | - let child = mirror.children.first! |
44 | | - let childMirror = Mirror(reflecting: child.value) |
45 | | - let elements = |
46 | | - childMirror.displayStyle != .tuple |
47 | | - ? debugOutputHelp(child.value, indent: 2) |
48 | | - : childMirror.children.map { child -> String in |
49 | | - let label = child.label! |
50 | | - return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutputHelp(child.value))" |
51 | | - } |
52 | | - .joined(separator: ",\n") |
53 | | - .indent(by: 2) |
54 | | - return """ |
55 | | - \(mirror.subjectType).\(child.label!)( |
56 | | - \(elements) |
57 | | - ) |
58 | | - """ |
59 | | - .indent(by: indent) |
60 | | - |
61 | | - case (_, .enum?): |
62 | | - return """ |
63 | | - \(mirror.subjectType).\(value) |
64 | | - """ |
65 | | - .indent(by: indent) |
66 | | - |
67 | | - case (_, .struct?) where !mirror.children.isEmpty: |
68 | | - let elements = mirror.children |
69 | | - .map { "\($0.label.map { "\($0): " } ?? "")\(debugOutputHelp($0.value))".indent(by: 2) } |
70 | | - .joined(separator: ",\n") |
71 | | - return """ |
72 | | - \(mirror.subjectType)( |
73 | | - \(elements) |
74 | | - ) |
75 | | - """ |
76 | | - .indent(by: indent) |
77 | | - |
78 | | - case let (value as AnyObject, .class?) |
79 | | - where !mirror.children.isEmpty && !visitedItems.contains(ObjectIdentifier(value)): |
80 | | - visitedItems.insert(ObjectIdentifier(value)) |
81 | | - let elements = mirror.children |
82 | | - .map { "\($0.label.map { "\($0): " } ?? "")\(debugOutputHelp($0.value))".indent(by: 2) } |
83 | | - .joined(separator: ",\n") |
84 | | - return """ |
85 | | - \(mirror.subjectType)( |
86 | | - \(elements) |
87 | | - ) |
88 | | - """ |
89 | | - .indent(by: indent) |
90 | | - |
91 | | - case let (value as AnyObject, .class?) |
92 | | - where !mirror.children.isEmpty && visitedItems.contains(ObjectIdentifier(value)): |
93 | | - return "\(mirror.subjectType)(↩︎)" |
94 | | - |
95 | | - case let (value as CustomStringConvertible, .class?): |
96 | | - return value.description |
97 | | - .replacingOccurrences( |
98 | | - of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression |
99 | | - ) |
100 | | - .indent(by: indent) |
101 | | - |
102 | | - case let (value as CustomDebugStringConvertible, _): |
103 | | - return value.debugDescription |
104 | | - .replacingOccurrences( |
105 | | - of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression |
106 | | - ) |
107 | | - .indent(by: indent) |
108 | | - |
109 | | - case let (value as CustomStringConvertible, _): |
110 | | - return value.description |
111 | | - .indent(by: indent) |
112 | | - |
113 | | - case (_, .struct?), (_, .class?): |
114 | | - return "\(mirror.subjectType)()" |
115 | | - .indent(by: indent) |
116 | | - |
117 | | - case (_, .tuple?) where mirror.children.isEmpty: |
118 | | - return "()" |
119 | | - .indent(by: indent) |
120 | | - |
121 | | - case (_, .tuple?): |
122 | | - let elements = mirror.children.map { child -> String in |
123 | | - let label = child.label! |
124 | | - return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutputHelp(child.value))" |
125 | | - .indent(by: 2) |
126 | | - } |
127 | | - return """ |
128 | | - ( |
129 | | - \(elements.joined(separator: ",\n")) |
130 | | - ) |
131 | | - """ |
132 | | - .indent(by: indent) |
133 | | - |
134 | | - case (_, nil): |
135 | | - return "\(value)" |
136 | | - .indent(by: indent) |
137 | | - |
138 | | - @unknown default: |
139 | | - return "\(value)" |
140 | | - .indent(by: indent) |
141 | | - } |
142 | | - } |
143 | | - |
144 | | - return debugOutputHelp(value, indent: indent) |
145 | | -} |
146 | | - |
147 | | -func debugDiff<T>(_ before: T, _ after: T, printer: (T) -> String = { debugOutput($0) }) -> String? |
148 | | -{ |
149 | | - diff(printer(before), printer(after)) |
150 | | -} |
151 | | - |
152 | 4 | extension String { |
153 | 5 | func indent(by indent: Int) -> String { |
154 | 6 | let indentation = String(repeating: " ", count: indent) |
155 | 7 | return indentation + self.replacingOccurrences(of: "\n", with: "\n\(indentation)") |
156 | 8 | } |
157 | 9 | } |
158 | | - |
159 | | -public protocol CustomDebugOutputConvertible { |
160 | | - var debugOutput: String { get } |
161 | | -} |
162 | | - |
163 | | -extension Date: CustomDebugOutputConvertible { |
164 | | - public var debugOutput: String { |
165 | | - dateFormatter.string(from: self) |
166 | | - } |
167 | | -} |
168 | | - |
169 | | -private let dateFormatter: ISO8601DateFormatter = { |
170 | | - let formatter = ISO8601DateFormatter() |
171 | | - formatter.timeZone = TimeZone(identifier: "UTC")! |
172 | | - return formatter |
173 | | -}() |
174 | | - |
175 | | -extension URL: CustomDebugOutputConvertible { |
176 | | - public var debugOutput: String { |
177 | | - self.absoluteString |
178 | | - } |
179 | | -} |
180 | | - |
181 | | -#if DEBUG |
182 | | - #if canImport(Speech) |
183 | | - import Speech |
184 | | - extension SFSpeechRecognizerAuthorizationStatus: CustomDebugOutputConvertible { |
185 | | - public var debugOutput: String { |
186 | | - switch self { |
187 | | - case .notDetermined: |
188 | | - return "notDetermined" |
189 | | - case .denied: |
190 | | - return "denied" |
191 | | - case .restricted: |
192 | | - return "restricted" |
193 | | - case .authorized: |
194 | | - return "authorized" |
195 | | - @unknown default: |
196 | | - return "unknown" |
197 | | - } |
198 | | - } |
199 | | - } |
200 | | - #endif |
201 | | -#endif |
0 commit comments