@@ -25,4 +25,133 @@ class TestUtils {
2525 return result
2626 }
2727
28+ class func prettyFirstDifferenceBetweenStrings( s1: String , s2: String ) -> String {
29+ return prettyFirstDifferenceBetweenNSStrings ( s1: s1 as NSString , s2: s2 as NSString ) as String
30+ }
31+
32+ }
33+
34+ /// Find first differing character between two strings
35+ ///
36+ /// :param: s1 First String
37+ /// :param: s2 Second String
38+ ///
39+ /// :returns: .DifferenceAtIndex(i) or .NoDifference
40+ fileprivate func firstDifferenceBetweenStrings( s1: NSString , s2: NSString ) -> FirstDifferenceResult {
41+ let len1 = s1. length
42+ let len2 = s2. length
43+
44+ let lenMin = min ( len1, len2)
45+
46+ for i in 0 ..< lenMin {
47+ if s1. character ( at: i) != s2. character ( at: i) {
48+ return . DifferenceAtIndex( i)
49+ }
50+ }
51+
52+ if len1 < len2 {
53+ return . DifferenceAtIndex( len1)
54+ }
55+
56+ if len2 < len1 {
57+ return . DifferenceAtIndex( len2)
58+ }
59+
60+ return . NoDifference
61+ }
62+
63+
64+ /// Create a formatted String representation of difference between strings
65+ ///
66+ /// :param: s1 First string
67+ /// :param: s2 Second string
68+ ///
69+ /// :returns: a string, possibly containing significant whitespace and newlines
70+ fileprivate func prettyFirstDifferenceBetweenNSStrings( s1: NSString , s2: NSString ) -> NSString {
71+ let firstDifferenceResult = firstDifferenceBetweenStrings ( s1: s1, s2: s2)
72+ return prettyDescriptionOfFirstDifferenceResult ( firstDifferenceResult: firstDifferenceResult, s1: s1, s2: s2)
73+ }
74+
75+
76+ /// Create a formatted String representation of a FirstDifferenceResult for two strings
77+ ///
78+ /// :param: firstDifferenceResult FirstDifferenceResult
79+ /// :param: s1 First string used in generation of firstDifferenceResult
80+ /// :param: s2 Second string used in generation of firstDifferenceResult
81+ ///
82+ /// :returns: a printable string, possibly containing significant whitespace and newlines
83+ fileprivate func prettyDescriptionOfFirstDifferenceResult( firstDifferenceResult: FirstDifferenceResult , s1: NSString , s2: NSString ) -> NSString {
84+
85+ func diffString( index: Int , s1: NSString , s2: NSString ) -> NSString {
86+ let markerArrow = " \u{2b06} " // "⬆"
87+ let ellipsis = " \u{2026} " // "…"
88+ /// Given a string and a range, return a string representing that substring.
89+ ///
90+ /// If the range starts at a position other than 0, an ellipsis
91+ /// will be included at the beginning.
92+ ///
93+ /// If the range ends before the actual end of the string,
94+ /// an ellipsis is added at the end.
95+ func windowSubstring( s: NSString , range: NSRange ) -> String {
96+ let validRange = NSMakeRange ( range. location, min ( range. length, s. length - range. location) )
97+ let substring = s. substring ( with: validRange)
98+
99+ let prefix = range. location > 0 ? ellipsis : " "
100+ let suffix = ( s. length - range. location > range. length) ? ellipsis : " "
101+
102+ return " \( prefix) \( substring) \( suffix) "
103+ }
104+
105+ // Show this many characters before and after the first difference
106+ let windowPrefixLength = 10
107+ let windowSuffixLength = 10
108+ let windowLength = windowPrefixLength + 1 + windowSuffixLength
109+
110+ let windowIndex = max ( index - windowPrefixLength, 0 )
111+ let windowRange = NSMakeRange ( windowIndex, windowLength)
112+
113+ let sub1 = windowSubstring ( s: s1, range: windowRange)
114+ let sub2 = windowSubstring ( s: s2, range: windowRange)
115+
116+ let markerPosition = min ( windowSuffixLength, index) + ( windowIndex > 0 ? 1 : 0 )
117+
118+ let markerPrefix = String ( repeating: " " , count: markerPosition)
119+ let markerLine = " \( markerPrefix) \( markerArrow) "
120+
121+ return " Difference at index \( index) : \n \( sub1) \n \( sub2) \n \( markerLine) " as NSString
122+ }
123+
124+ switch firstDifferenceResult {
125+ case . NoDifference: return " No difference "
126+ case . DifferenceAtIndex( let index) : return diffString ( index: index, s1: s1, s2: s2)
127+ }
128+ }
129+
130+ /// Result type for firstDifferenceBetweenStrings()
131+ public enum FirstDifferenceResult {
132+ /// Strings are identical
133+ case NoDifference
134+
135+ /// Strings differ at the specified index.
136+ ///
137+ /// This could mean that characters at the specified index are different,
138+ /// or that one string is longer than the other
139+ case DifferenceAtIndex( Int )
140+ }
141+
142+ extension FirstDifferenceResult : CustomStringConvertible {
143+ /// Textual representation of a FirstDifferenceResult
144+ public var description : String {
145+ switch self {
146+ case . NoDifference:
147+ return " NoDifference "
148+ case . DifferenceAtIndex( let index) :
149+ return " DifferenceAtIndex( \( index) ) "
150+ }
151+ }
152+
153+ /// Textual representation of a FirstDifferenceResult for debugging purposes
154+ public var debugDescription : String {
155+ return self . description
156+ }
28157}
0 commit comments