native macOS codings agent orchestrator
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 151 lines 4.6 kB view raw
1import ComposableArchitecture 2import CustomDump 3import Foundation 4import Sentry 5 6extension Reducer where State: Equatable { 7 @ReducerBuilder<State, Action> 8 func logActions() -> some Reducer<State, Action> { 9 LogActionsReducer(base: self) 10 } 11} 12 13struct LogActionsReducer<Base: Reducer>: Reducer where Base.State: Equatable { 14 let base: Base 15 16 private let logger = SupaLogger("TCA") 17 18 func reduce(into state: inout Base.State, action: Base.Action) -> Effect<Base.Action> { 19 #if DEBUG 20 let actionLabel = debugCaseOutput(action) 21 logger.debug("Action: \(actionLabel)") 22 let previousState = state 23 let effects = base.reduce(into: &state, action: action) 24 if previousState != state, let diff = CustomDump.diff(previousState, state) { 25 print(diff) 26 } 27 return effects 28 #else 29 let actionLabel = releaseActionLabel(action) 30 logger.debug("Action: \(actionLabel)") 31 SentrySDK.logger.info("Action: \(actionLabel)") 32 let breadcrumb = Breadcrumb(level: .debug, category: "action") 33 breadcrumb.message = actionLabel 34 SentrySDK.addBreadcrumb(breadcrumb) 35 return base.reduce(into: &state, action: action) 36 #endif 37 } 38} 39 40func debugCaseOutput( 41 _ value: Any, 42 abbreviated: Bool = false 43) -> String { 44 func debugCaseOutputHelp(_ value: Any) -> String { 45 let mirror = Mirror(reflecting: value) 46 switch mirror.displayStyle { 47 case .enum: 48 guard let child = mirror.children.first else { 49 let childOutput = "\(value)" 50 return childOutput == "\(typeName(type(of: value)))" ? "" : ".\(childOutput)" 51 } 52 let childOutput = debugCaseOutputHelp(child.value) 53 return ".\(child.label ?? "")\(childOutput.isEmpty ? "" : "(\(childOutput))")" 54 case .tuple: 55 return mirror.children.map { label, value in 56 let childOutput = debugCaseOutputHelp(value) 57 let labelValue = label.map { isUnlabeledArgument($0) ? "_:" : "\($0):" } ?? "" 58 let suffix = childOutput.isEmpty ? "" : " \(childOutput)" 59 return "\(labelValue)\(suffix)" 60 } 61 .joined(separator: ", ") 62 default: 63 return "" 64 } 65 } 66 67 return (value as? any CustomDebugStringConvertible)?.debugDescription 68 ?? "\(abbreviated ? "" : typeName(type(of: value)))\(debugCaseOutputHelp(value))" 69} 70 71func releaseActionLabel(_ value: Any) -> String { 72 let rootType = shortTypeName(type(of: value)) 73 let casePath = releaseEnumCasePath(value) 74 guard !casePath.isEmpty else { 75 return rootType 76 } 77 return "\(rootType).\(casePath.joined(separator: "."))" 78} 79 80private func isUnlabeledArgument(_ label: String) -> Bool { 81 label.firstIndex(where: { $0 != "." && !$0.isNumber }) == nil 82} 83 84private func releaseEnumCasePath(_ value: Any) -> [String] { 85 var labels: [String] = [] 86 var currentValue = value 87 88 while true { 89 let mirror = Mirror(reflecting: currentValue) 90 guard mirror.displayStyle == .enum else { 91 return labels 92 } 93 if let child = mirror.children.first, let label = child.label { 94 labels.append(label) 95 let childMirror = Mirror(reflecting: child.value) 96 guard childMirror.displayStyle == .enum else { 97 return labels 98 } 99 currentValue = child.value 100 } else { 101 labels.append(caseName(String(describing: currentValue))) 102 return labels 103 } 104 } 105} 106 107private func caseName(_ description: String) -> String { 108 if let parenIndex = description.firstIndex(of: "(") { 109 return String(description[..<parenIndex]) 110 } 111 return description 112} 113 114private func shortTypeName(_ type: Any.Type) -> String { 115 let components = String(reflecting: type) 116 .split(separator: ".") 117 .filter { !$0.hasPrefix("(unknown context at $") } 118 .suffix(2) 119 return components.isEmpty ? String(reflecting: type) : components.joined(separator: ".") 120} 121 122private func typeName( 123 _ type: Any.Type, 124 qualified: Bool = true, 125 genericsAbbreviated: Bool = true 126) -> String { 127 var name = _typeName(type, qualified: qualified) 128 .replacing(#/\(unknown context at \$[0-9A-Fa-f]+\)\./#, with: "") 129 for _ in 1...10 { 130 let abbreviated = 131 name 132 .replacing(#/\bSwift\.Optional<([^><]+)>/#) { match in 133 "\(match.1)?" 134 } 135 .replacing(#/\bSwift\.Array<([^><]+)>/#) { match in 136 "[\(match.1)]" 137 } 138 .replacing(#/\bSwift\.Dictionary<([^,<]+), ([^><]+)>/#) { match in 139 "[\(match.1): \(match.2)]" 140 } 141 if abbreviated == name { break } 142 name = abbreviated 143 } 144 name = name.replacing(#/\w+\.([\w.]+)/#) { match in 145 "\(match.1)" 146 } 147 if genericsAbbreviated { 148 name = name.replacing(#/<.+>/#, with: "") 149 } 150 return name 151}