@@ -2,46 +2,191 @@ import Foundation
22import os. log
33import SwiftRex
44
5- final class LoggerMiddleware < AppAction, AppState: Equatable > : Middleware {
6- typealias InputActionType = AppAction
7- typealias OutputActionType = Never
8- typealias StateType = AppState
5+ public final class LoggerMiddleware < AppAction, AppState: Equatable > : Middleware {
6+ public typealias InputActionType = AppAction
7+ public typealias OutputActionType = Never
8+ public typealias StateType = AppState
99
10- private var getState : GetState < StateType > !
11- private let actionPrint : ( AppAction ) -> String ?
12- private let statePrint : ( AppState ) -> String ?
10+ private var getState : GetState < StateType > ?
11+ private let printer : ( StaticString , CVarArg . . . ) -> Void
1312
14- init ( actionPrint: @escaping ( AppAction ) -> String ? = { " \( $0) " } , statePrint: @escaping ( AppState ) -> String ? = { " \( $0) " } ) {
15- self . actionPrint = actionPrint
16- self . statePrint = statePrint
13+ public init ( printer: ( ( StaticString , CVarArg . . . ) -> Void ) ? = nil ) {
14+ self . printer = printer ?? { ( message: StaticString, args: CVarArg... ) - > Void in os_log ( . debug, log: . default, message, args) }
1715 }
1816
19- func receiveContext( getState: @escaping GetState < StateType > , output: AnyActionHandler < OutputActionType > ) {
17+ public init ( printer: @escaping ( String ) -> Void ) {
18+ self . printer = { ( message: StaticString, args: CVarArg... ) - > Void in printer ( String ( format: " \( message) " , args) ) }
19+ }
20+
21+ public func receiveContext( getState: @escaping GetState < StateType > , output: AnyActionHandler < OutputActionType > ) {
2022 self . getState = getState
2123 }
2224
23- func handle( action: InputActionType , from dispatcher: ActionSource , afterReducer: inout AfterReducer ) {
24- let stateBefore = getState ( )
25+ public func handle( action: InputActionType , from dispatcher: ActionSource , afterReducer: inout AfterReducer ) {
26+ guard let getState = self . getState else { return }
27+
28+ var stateBefore = " "
29+ dump ( getState ( ) , to: & stateBefore, name: nil , indent: 2 )
2530 afterReducer = . do {
26- let stateAfter = self . getState ( )
31+ var stateAfter = " "
32+ dump ( getState ( ) , to: & stateAfter, name: nil , indent: 2 )
2733
28- var enabled = false
29- if let actionString = self . actionPrint ( action) {
30- enabled = true
31- let message = " 🕹 \( actionString) from \( dispatcher) "
32- os_log ( . debug, log: . default, " %{PUBLIC}@ " , message)
33- }
34+ let message = " 🕹 \( action) from \( dispatcher) "
35+ self . printer ( " %{PUBLIC}@ " , message)
3436
35- if stateBefore != stateAfter, let stateString = self . statePrint ( stateAfter) {
36- enabled = true
37- os_log ( . debug, log: . default, " 🏛 %{PUBLIC}@ " , stateString)
37+ if let stateString = diff ( old: stateBefore, new: stateAfter) {
38+ self . printer ( " \n %{PUBLIC}@ " , stateString)
3839 } else {
39- os_log ( . debug , log : . default , " 🏛 No state mutation " )
40+ self . printer ( " 🏛 No state mutation " )
4041 }
4142
42- if enabled {
43- os_log ( . debug, log: . default, " " )
44- }
43+ self . printer ( " " )
44+ }
45+ }
46+ }
47+
48+ struct Difference < A> {
49+ enum Which {
50+ case first
51+ case second
52+ case both
53+ }
54+
55+ let elements : [ A ]
56+ let which : Which
57+ }
58+
59+ func diff( old: String , new: String ) -> String ? {
60+ guard old != new else { return nil }
61+ let oldSplit = old. split ( separator: " \n " , omittingEmptySubsequences: false ) . map ( String . init)
62+ let newSplit = new. split ( separator: " \n " , omittingEmptySubsequences: false ) . map ( String . init)
63+
64+ return chunk (
65+ diff: diff ( oldSplit, newSplit) ,
66+ context: 2 // Lines before and after the changes to be shown as context
67+ ) . lazy. flatMap { [ $0. patchMark] + $0. lines } . map { " 🏛 \( $0) " } . joined ( separator: " \n " )
68+ }
69+
70+ func diff( _ fst: [ String ] , _ snd: [ String ] ) -> [ Difference < String > ] {
71+ var idxsOf = [ String: [ Int] ] ( )
72+ fst. enumerated ( ) . forEach { idxsOf [ $1, default: [ ] ] . append ( $0) }
73+
74+ let sub = snd. enumerated ( ) . reduce ( ( overlap: [ Int: Int] ( ) , fst: 0 , snd: 0 , len: 0 ) ) { sub, sndPair in
75+ ( idxsOf [ sndPair. element] ?? [ ] )
76+ . reduce ( ( overlap: [ Int: Int] ( ) , fst: sub. fst, snd: sub. snd, len: sub. len) ) { innerSub, fstIdx in
77+
78+ var newOverlap = innerSub. overlap
79+ newOverlap [ fstIdx] = ( sub. overlap [ fstIdx - 1 ] ?? 0 ) + 1
80+
81+ if let newLen = newOverlap [ fstIdx] , newLen > sub. len {
82+ return ( newOverlap, fstIdx - newLen + 1 , sndPair. offset - newLen + 1 , newLen)
83+ }
84+ return ( newOverlap, innerSub. fst, innerSub. snd, innerSub. len)
4585 }
4686 }
87+ let ( _, fstIdx, sndIdx, len) = sub
88+
89+ if len == 0 {
90+ let fstDiff = fst. isEmpty ? [ ] : [ Difference ( elements: fst, which: . first) ]
91+ let sndDiff = snd. isEmpty ? [ ] : [ Difference ( elements: snd, which: . second) ]
92+ return fstDiff + sndDiff
93+ } else {
94+ let fstDiff = diff ( Array ( fst. prefix ( upTo: fstIdx) ) , Array ( snd. prefix ( upTo: sndIdx) ) )
95+ let midDiff = [ Difference ( elements: Array ( fst. suffix ( from: fstIdx) . prefix ( len) ) , which: . both) ]
96+ let lstDiff = diff ( Array ( fst. suffix ( from: fstIdx + len) ) , Array ( snd. suffix ( from: sndIdx + len) ) )
97+ return fstDiff + midDiff + lstDiff
98+ }
99+ }
100+
101+ let minus = " − "
102+ let plus = " + "
103+ private let figureSpace = " \u{2007} "
104+
105+ struct Hunk {
106+ let fstIdx : Int
107+ let fstLen : Int
108+ let sndIdx : Int
109+ let sndLen : Int
110+ let lines : [ String ]
111+
112+ var patchMark : String {
113+ let fstMark = " \( minus) \( fstIdx + 1 ) , \( fstLen) "
114+ let sndMark = " \( plus) \( sndIdx + 1 ) , \( sndLen) "
115+ return " @@ \( fstMark) \( sndMark) @@ "
116+ }
117+
118+ // Semigroup
119+ static func + ( lhs: Hunk , rhs: Hunk ) -> Hunk {
120+ return Hunk (
121+ fstIdx: lhs. fstIdx + rhs. fstIdx,
122+ fstLen: lhs. fstLen + rhs. fstLen,
123+ sndIdx: lhs. sndIdx + rhs. sndIdx,
124+ sndLen: lhs. sndLen + rhs. sndLen,
125+ lines: lhs. lines + rhs. lines
126+ )
127+ }
128+
129+ // Monoid
130+ init ( fstIdx: Int = 0 , fstLen: Int = 0 , sndIdx: Int = 0 , sndLen: Int = 0 , lines: [ String ] = [ ] ) {
131+ self . fstIdx = fstIdx
132+ self . fstLen = fstLen
133+ self . sndIdx = sndIdx
134+ self . sndLen = sndLen
135+ self . lines = lines
136+ }
137+
138+ init ( idx: Int = 0 , len: Int = 0 , lines: [ String ] = [ ] ) {
139+ self . init ( fstIdx: idx, fstLen: len, sndIdx: idx, sndLen: len, lines: lines)
140+ }
141+ }
142+
143+ func chunk( diff diffs: [ Difference < String > ] , context ctx: Int = 4 ) -> [ Hunk ] {
144+ func prepending( _ prefix: String ) -> ( String ) -> String {
145+ return { prefix + $0 + ( $0. hasSuffix ( " " ) ? " ¬ " : " " ) }
146+ }
147+ let changed : ( Hunk ) -> Bool = { $0. lines. contains ( where: { $0. hasPrefix ( minus) || $0. hasPrefix ( plus) } ) }
148+
149+ let ( hunk, hunks) = diffs
150+ . reduce ( ( current: Hunk ( ) , hunks: [ Hunk] ( ) ) ) { cursor, diff in
151+ let ( current, hunks) = cursor
152+ let len = diff. elements. count
153+
154+ switch diff. which {
155+ case . both where len > ctx * 2 :
156+ let hunk = current + Hunk( len: ctx, lines: diff. elements. prefix ( ctx) . map ( prepending ( figureSpace) ) )
157+ let next = Hunk (
158+ fstIdx: current. fstIdx + current. fstLen + len - ctx,
159+ fstLen: ctx,
160+ sndIdx: current. sndIdx + current. sndLen + len - ctx,
161+ sndLen: ctx,
162+ lines: ( diff. elements. suffix ( ctx) as ArraySlice < String > ) . map ( prepending ( figureSpace) )
163+ )
164+ return ( next, changed ( hunk) ? hunks + [ hunk] : hunks)
165+ case . both where current. lines. isEmpty:
166+ let lines = ( diff. elements. suffix ( ctx) as ArraySlice < String > ) . map ( prepending ( figureSpace) )
167+ let count = lines. count
168+ return ( current + Hunk( idx: len - count, len: count, lines: lines) , hunks)
169+ case . both:
170+ return ( current + Hunk( len: len, lines: diff. elements. map ( prepending ( figureSpace) ) ) , hunks)
171+ case . first:
172+ return ( current + Hunk( fstLen: len, lines: diff. elements. map ( prepending ( minus) ) ) , hunks)
173+ case . second:
174+ return ( current + Hunk( sndLen: len, lines: diff. elements. map ( prepending ( plus) ) ) , hunks)
175+ }
176+ }
177+
178+ return changed ( hunk) ? hunks + [ hunk] : hunks
47179}
180+
181+ extension LoggerMiddleware {
182+ public func lift( ) -> AnyMiddleware < AppAction , AppAction , AppState > {
183+ self . lift (
184+ inputActionMap: identity,
185+ outputActionMap: absurd,
186+ stateMap: identity
187+ ) . eraseToAnyMiddleware ( )
188+ }
189+ }
190+
191+ func absurd< T> ( _ never: Never ) -> T { }
192+ func identity< T> ( _ t: T ) -> T { t }
0 commit comments