diff --git a/Markingbird.xcodeproj/project.pbxproj b/Markingbird.xcodeproj/project.pbxproj index df55035..0ccfc8d 100644 --- a/Markingbird.xcodeproj/project.pbxproj +++ b/Markingbird.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 4EAF8DBF19A212F30020CA43 /* Markingbird.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EAF8DBE19A212F30020CA43 /* Markingbird.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4EAF8DD319A213340020CA43 /* Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAF8DD219A213340020CA43 /* Markdown.swift */; }; 4EAF8DD519A213470020CA43 /* SimpleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAF8DD419A213470020CA43 /* SimpleTests.swift */; }; + 4EC0AF3519A90DAC0068A458 /* differences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC0AF3419A90DAC0068A458 /* differences.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -36,6 +37,7 @@ 4EAF8DC719A212F30020CA43 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4EAF8DD219A213340020CA43 /* Markdown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Markdown.swift; sourceTree = ""; }; 4EAF8DD419A213470020CA43 /* SimpleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleTests.swift; sourceTree = ""; }; + 4EC0AF3419A90DAC0068A458 /* differences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = differences.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -100,6 +102,7 @@ 4EAF8DD419A213470020CA43 /* SimpleTests.swift */, 4EAF8DC619A212F30020CA43 /* Supporting Files */, 4E2A478C19A5EADD00B23DB6 /* testfiles */, + 4EC0AF3419A90DAC0068A458 /* differences.swift */, ); path = MarkingbirdTests; sourceTree = ""; @@ -230,6 +233,7 @@ files = ( 4E2A478F19A5EB6F00B23DB6 /* MDTestTests.swift in Sources */, 4EAF8DD519A213470020CA43 /* SimpleTests.swift in Sources */, + 4EC0AF3519A90DAC0068A458 /* differences.swift in Sources */, 4E3CBBA519A4A70400940D90 /* ConfigTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Markingbird/Markdown.swift b/Markingbird/Markdown.swift index c584c0a..99906a9 100644 --- a/Markingbird/Markdown.swift +++ b/Markingbird/Markdown.swift @@ -384,12 +384,13 @@ public struct Markdown { var keepGoing = true // as long as replacements where made, keep going while keepGoing && sanityCheck > 0 { keepGoing = false - grafs[i] = Markdown._htmlBlockHash.replace(grafs[i]) { match in - if let value = self._htmlBlocks[match.value] { + let graf = grafs[i] + grafs[i] = Markdown._htmlBlockHash.replace(graf) { match in + if let replacementValue = self._htmlBlocks[match.value] { keepGoing = true - return value + return replacementValue } - return grafs[i] + return graf } sanityCheck-- } @@ -1825,8 +1826,9 @@ private struct MarkdownRegex { return } let match = MarkdownRegexMatch(textCheckingResult: result, string: s) + let range = result.range let replacementText = evaluator(match) - let replacement = (result.range, replacementText) + let replacement = (range, replacementText) replacements.append(replacement) }) @@ -1928,24 +1930,24 @@ private struct MarkdownRegexMatch { let textCheckingResult: NSTextCheckingResult let string: NSString - private init(textCheckingResult: NSTextCheckingResult, string: NSString) { + init(textCheckingResult: NSTextCheckingResult, string: NSString) { self.textCheckingResult = textCheckingResult self.string = string } - private var value: NSString { + var value: NSString { return string.substringWithRange(textCheckingResult.range) } - private var index: Int { + var index: Int { return textCheckingResult.range.location } - private var length: Int { + var length: Int { return textCheckingResult.range.length } - private func valueOfGroupAtIndex(idx: Int) -> NSString { + func valueOfGroupAtIndex(idx: Int) -> NSString { if 0 <= idx && idx < textCheckingResult.numberOfRanges { let groupRange = textCheckingResult.rangeAtIndex(idx) if (groupRange.location == NSNotFound) { @@ -1973,17 +1975,17 @@ private struct MarkdownRegexMatch { /// - ExplicitCapture private struct MarkdownRegexOptions { /// Allow ^ and $ to match the start and end of lines. - private static let Multiline = NSRegularExpressionOptions.AnchorsMatchLines + static let Multiline = NSRegularExpressionOptions.AnchorsMatchLines /// Ignore whitespace and #-prefixed comments in the pattern. - private static let IgnorePatternWhitespace = NSRegularExpressionOptions.AllowCommentsAndWhitespace + static let IgnorePatternWhitespace = NSRegularExpressionOptions.AllowCommentsAndWhitespace /// Allow . to match any character, including line separators. - private static let Singleline = NSRegularExpressionOptions.DotMatchesLineSeparators + static let Singleline = NSRegularExpressionOptions.DotMatchesLineSeparators /// Match letters in the pattern independent of case. - private static let IgnoreCase = NSRegularExpressionOptions.CaseInsensitive + static let IgnoreCase = NSRegularExpressionOptions.CaseInsensitive /// Default options - private static let None = NSRegularExpressionOptions(0) + static let None = NSRegularExpressionOptions(0) } diff --git a/MarkingbirdTests/MDTestTests.swift b/MarkingbirdTests/MDTestTests.swift index 418d039..569c6fb 100644 --- a/MarkingbirdTests/MDTestTests.swift +++ b/MarkingbirdTests/MDTestTests.swift @@ -10,6 +10,17 @@ class MDTestTests: XCTestCase { /// and then compare the result with the corresponding .html file func testTests() { for test in getTests() { + + // If there is a difference, print it in a more readable way than + // XCTest does + switch firstDifferenceBetweenStrings(test.actualResult, test.expectedResult) { + case .NoDifference: + break; + case .DifferenceAtIndex(let index): + let prettyDiff = prettyFirstDifferenceBetweenStrings(test.actualResult, test.expectedResult) + println("\n====\n\(test.actualName): \(prettyDiff)\n====\n") + } + XCTAssertEqual(test.actualResult, test.expectedResult, "Mismatch between '\(test.actualName)' and the transformed '\(test.expectedName)'") } diff --git a/MarkingbirdTests/differences.swift b/MarkingbirdTests/differences.swift new file mode 100644 index 0000000..531e520 --- /dev/null +++ b/MarkingbirdTests/differences.swift @@ -0,0 +1,129 @@ +import Foundation + + +/// Find first differing character between two strings +/// +/// :param: s1 First String +/// :param: s2 Second String +/// +/// :returns: .DifferenceAtIndex(i) or .NoDifference +public func firstDifferenceBetweenStrings(s1: NSString, s2: NSString) -> FirstDifferenceResult { + let len1 = s1.length + let len2 = s2.length + + let lenMin = min(len1, len2) + + for i in 0.. NSString { + let firstDifferenceResult = firstDifferenceBetweenStrings(s1, s2) + return prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult, s1, s2) +} + + +/// Create a formatted String representation of a FirstDifferenceResult for two strings +/// +/// :param: firstDifferenceResult FirstDifferenceResult +/// :param: s1 First string used in generation of firstDifferenceResult +/// :param: s2 Second string used in generation of firstDifferenceResult +/// +/// :returns: a printable string, possibly containing significant whitespace and newlines +public func prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: FirstDifferenceResult, s1: NSString, s2: NSString) -> NSString { + + func diffString(index: Int, s1: NSString, s2: NSString) -> NSString { + let markerArrow = "\u{2b06}" // "⬆" + let ellipsis = "\u{2026}" // "…" + + /// Given a string and a range, return a string representing that substring. + /// + /// If the range starts at a position other than 0, an ellipsis + /// will be included at the beginning. + /// + /// If the range ends before the actual end of the string, + /// an ellipsis is added at the end. + func windowSubstring(s: NSString, range: NSRange) -> String { + let validRange = NSMakeRange(range.location, min(range.length, s.length - range.location)) + let substring = s.substringWithRange(validRange) + + let prefix = range.location > 0 ? ellipsis : "" + let suffix = (s.length - range.location > range.length) ? ellipsis : "" + + return "\(prefix)\(substring)\(suffix)" + } + + // Show this many characters before and after the first difference + let windowPrefixLength = 10 + let windowSuffixLength = 10 + let windowLength = windowPrefixLength + 1 + windowSuffixLength + + let windowIndex = max(index - windowPrefixLength, 0) + let windowRange = NSMakeRange(windowIndex, windowLength) + + let sub1 = windowSubstring(s1, windowRange) + let sub2 = windowSubstring(s2, windowRange) + + let markerPosition = min(windowSuffixLength, index) + (windowIndex > 0 ? 1 : 0) + + let markerPrefix = String(count: markerPosition, repeatedValue: " " as Character) + let markerLine = "\(markerPrefix)\(markerArrow)" + + return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)" + } + + switch firstDifferenceResult { + case .NoDifference: return "No difference" + case .DifferenceAtIndex(let index): return diffString(index, s1, s2) + } +} + + +/// Result type for firstDifferenceBetweenStrings() +public enum FirstDifferenceResult { + /// Strings are identical + case NoDifference + + /// Strings differ at the specified index. + /// + /// This could mean that characters at the specified index are different, + /// or that one string is longer than the other + case DifferenceAtIndex(Int) +} + +extension FirstDifferenceResult: Printable, DebugPrintable { + /// Textual representation of a FirstDifferenceResult + public var description: String { + switch self { + case .NoDifference: + return "NoDifference" + case .DifferenceAtIndex(let index): + return "DifferenceAtIndex(\(index))" + } + } + + /// Textual representation of a FirstDifferenceResult for debugging purposes + public var debugDescription: String { + return self.description + } +}