1
1
mirror of https://github.com/github/semantic.git synced 2024-11-24 17:04:47 +03:00

Merge remote-tracking branch 'origin/master' into show-empty-space

This commit is contained in:
joshvera 2015-11-02 12:29:44 -05:00
commit d9cb6594ac
13 changed files with 106 additions and 234 deletions

6
.gitignore vendored
View File

@ -1,2 +1,6 @@
.DS_Store
xcuserdata
*.mode*
*.pbxuser
*.xcuserdatad
*.xccheckout

2
.gitmodules vendored
View File

@ -14,7 +14,7 @@
path = prototype/External/tree-sitter
url = https://github.com/maxbrunsfeld/tree-sitter.git
[submodule "prototype/External/node-tree-sitter-javascript"]
path = prototype/External/node-tree-sitter-javascript
path = prototype/External/tree-sitter-javascript
url = https://github.com/maxbrunsfeld/node-tree-sitter-javascript.git
[submodule "prototype/External/tree-sitter-c"]
path = prototype/External/tree-sitter-c

View File

@ -8,8 +8,10 @@ This is a living document and will be updated as necessary.
1. A semantic diff prototyping toolkit. Library & wrapper tool for experimentation with diffing algorithms & spitting out actual diffs so we can evaluate alternatives more easily.
2. [Table of contents for .com diffs](https://github.com/github/semantic-diff/issues/16), linking to defined symbols in Ruby sources.
2. A prototype of user-facing semantic diff presentation and interaction.
3. [Table of contents for .com diffs](https://github.com/github/semantic-diff/issues/16), linking to defined symbols in supported sources.
## Q1 2016
1. A prototype of user-facing semantic diff presentation and interaction.
1. [Semantic diffing on .com](https://github.com/github/semantic-diff/milestones/Dot%20Calm).

View File

@ -25,10 +25,6 @@
D42F096C1BCCC41600B95610 /* Memo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09681BCCC41600B95610 /* Memo.framework */; };
D42F096D1BCCC41600B95610 /* Prelude.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09691BCCC41600B95610 /* Prelude.framework */; };
D42F096E1BCCC41600B95610 /* Stream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F096A1BCCC41600B95610 /* Stream.framework */; };
D42F09731BCCC5CE00B95610 /* Either.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09671BCCC41600B95610 /* Either.framework */; };
D42F09741BCCC5CE00B95610 /* Memo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09681BCCC41600B95610 /* Memo.framework */; };
D42F09751BCCC5CE00B95610 /* Prelude.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09691BCCC41600B95610 /* Prelude.framework */; };
D42F09761BCCC5CE00B95610 /* Stream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F096A1BCCC41600B95610 /* Stream.framework */; };
D42F09771BCCC5DC00B95610 /* Either.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09671BCCC41600B95610 /* Either.framework */; };
D42F09781BCCC5DC00B95610 /* Memo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09681BCCC41600B95610 /* Memo.framework */; };
D42F09791BCCC5DC00B95610 /* Prelude.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09691BCCC41600B95610 /* Prelude.framework */; };
@ -50,9 +46,7 @@
D4AAE5491B5AE2D0004E581F /* StringLiteralConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AAE53E1B5AE2D0004E581F /* StringLiteralConvertible.swift */; };
D4AAE54A1B5AE2D0004E581F /* Syntax.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AAE53F1B5AE2D0004E581F /* Syntax.swift */; };
D4DF96ED1BC46B630040F41F /* SES.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DF96EC1BC46B630040F41F /* SES.swift */; };
D4DF970A1BC5DF800040F41F /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DF97091BC5DF800040F41F /* main.swift */; };
D4DF970C1BC5DF9E0040F41F /* BoundsCheckedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = D435B7521BB31BBC000902F6 /* BoundsCheckedArray.swift */; };
D4DF970D1BC5E1B40040F41F /* Doubt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4AAE4FD1B5AE22E004E581F /* Doubt.framework */; };
D4FB2CD61BDEBC9000B3CCE0 /* Doubt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4AAE4FD1B5AE22E004E581F /* Doubt.framework */; };
D4FB2CD71BDEBC9D00B3CCE0 /* Either.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09671BCCC41600B95610 /* Either.framework */; };
D4FB2CD81BDEBC9D00B3CCE0 /* Memo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D42F09681BCCC41600B95610 /* Memo.framework */; };
@ -124,14 +118,12 @@
D4AAE53F1B5AE2D0004E581F /* Syntax.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syntax.swift; sourceTree = "<group>"; };
D4DF96EC1BC46B630040F41F /* SES.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SES.swift; sourceTree = "<group>"; };
D4DF96F01BC54C970040F41F /* Doubt.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = Doubt.modulemap; sourceTree = "<group>"; };
D4DF96FB1BC5DF050040F41F /* doubt-json.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "doubt-json.app"; sourceTree = BUILT_PRODUCTS_DIR; };
D4DF97091BC5DF800040F41F /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D4FB2CC91BDEBC6300B3CCE0 /* doubt-difftool.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "doubt-difftool.app"; sourceTree = BUILT_PRODUCTS_DIR; };
D4FB2CDB1BDEBCCD00B3CCE0 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D4FB2CDE1BDEBD1C00B3CCE0 /* libruntime.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libruntime.a; path = "External/tree-sitter/out/Release/libruntime.a"; sourceTree = SOURCE_ROOT; };
D4FB2CE51BDEBE7900B3CCE0 /* doubt-difftool-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "doubt-difftool-Bridging-Header.h"; sourceTree = "<group>"; };
D4FB2CF71BE1560400B3CCE0 /* TSNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSNode.swift; sourceTree = "<group>"; };
D4FB2CF91BE28F6D00B3CCE0 /* parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = parser.c; path = "External/node-tree-sitter-javascript/src/parser.c"; sourceTree = SOURCE_ROOT; };
D4FB2CF91BE28F6D00B3CCE0 /* parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = parser.c; path = "External/tree-sitter-javascript/src/parser.c"; sourceTree = SOURCE_ROOT; };
D4FB2CFB1BE292BB00B3CCE0 /* parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = parser.c; path = "External/tree-sitter-c/src/parser.c"; sourceTree = SOURCE_ROOT; };
D4FB2D011BE2943A00B3CCE0 /* Info.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -163,18 +155,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
D4DF96F81BC5DF050040F41F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D4DF970D1BC5E1B40040F41F /* Doubt.framework in Frameworks */,
D42F09731BCCC5CE00B95610 /* Either.framework in Frameworks */,
D42F09741BCCC5CE00B95610 /* Memo.framework in Frameworks */,
D42F09751BCCC5CE00B95610 /* Prelude.framework in Frameworks */,
D42F09761BCCC5CE00B95610 /* Stream.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D4FB2CC61BDEBC6300B3CCE0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -196,7 +176,6 @@
children = (
D4AAE4FF1B5AE22E004E581F /* Doubt */,
D4AAE5111B5AE22E004E581F /* DoubtTests */,
D4DF96FC1BC5DF050040F41F /* doubt-json */,
D4FB2CCA1BDEBC6300B3CCE0 /* doubt-difftool */,
D4AAE4FE1B5AE22E004E581F /* Products */,
);
@ -207,7 +186,6 @@
children = (
D4AAE4FD1B5AE22E004E581F /* Doubt.framework */,
D4AAE50D1B5AE22E004E581F /* DoubtTests.xctest */,
D4DF96FB1BC5DF050040F41F /* doubt-json.app */,
D4FB2CC91BDEBC6300B3CCE0 /* doubt-difftool.app */,
);
name = Products;
@ -273,14 +251,6 @@
path = DoubtTests;
sourceTree = "<group>";
};
D4DF96FC1BC5DF050040F41F /* doubt-json */ = {
isa = PBXGroup;
children = (
D4DF97091BC5DF800040F41F /* main.swift */,
);
path = "doubt-json";
sourceTree = "<group>";
};
D4FB2CCA1BDEBC6300B3CCE0 /* doubt-difftool */ = {
isa = PBXGroup;
children = (
@ -385,22 +355,6 @@
productReference = D4AAE50D1B5AE22E004E581F /* DoubtTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
D4DF96FA1BC5DF050040F41F /* doubt-json */ = {
isa = PBXNativeTarget;
buildConfigurationList = D4DF97071BC5DF060040F41F /* Build configuration list for PBXNativeTarget "doubt-json" */;
buildPhases = (
D4DF96F71BC5DF050040F41F /* Sources */,
D4DF96F81BC5DF050040F41F /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "doubt-json";
productName = "doubt-json";
productReference = D4DF96FB1BC5DF050040F41F /* doubt-json.app */;
productType = "com.apple.product-type.application";
};
D4FB2CC81BDEBC6300B3CCE0 /* doubt-difftool */ = {
isa = PBXNativeTarget;
buildConfigurationList = D4FB2CD31BDEBC6300B3CCE0 /* Build configuration list for PBXNativeTarget "doubt-difftool" */;
@ -437,9 +391,6 @@
D4AAE50C1B5AE22E004E581F = {
CreatedOnToolsVersion = 7.0;
};
D4DF96FA1BC5DF050040F41F = {
CreatedOnToolsVersion = 7.0.1;
};
D4FB2CC81BDEBC6300B3CCE0 = {
CreatedOnToolsVersion = 7.1;
};
@ -460,7 +411,6 @@
targets = (
D4AAE4FC1B5AE22E004E581F /* Doubt */,
D4AAE50C1B5AE22E004E581F /* DoubtTests */,
D4DF96FA1BC5DF050040F41F /* doubt-json */,
D4FB2CC81BDEBC6300B3CCE0 /* doubt-difftool */,
D485A7841BDEB6C5003A17B6 /* runtime */,
);
@ -527,14 +477,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
D4DF96F71BC5DF050040F41F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D4DF970A1BC5DF800040F41F /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D4FB2CC51BDEBC6300B3CCE0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -748,33 +690,6 @@
};
name = Release;
};
D4DF97051BC5DF050040F41F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "doubt-json/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = "com.antitypical.doubt-json";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
D4DF97061BC5DF050040F41F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = "doubt-json/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = "com.antitypical.doubt-json";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
D4FB2CD41BDEBC6300B3CCE0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -858,15 +773,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D4DF97071BC5DF060040F41F /* Build configuration list for PBXNativeTarget "doubt-json" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D4DF97051BC5DF050040F41F /* Debug */,
D4DF97061BC5DF050040F41F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D4FB2CD31BDEBC6300B3CCE0 /* Build configuration list for PBXNativeTarget "doubt-difftool" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -1,23 +1,26 @@
//
// JSONLeaf.swift
// Doubt
//
// Created by Josh Vera on 10/16/15.
// Copyright © 2015 GitHub. All rights reserved.
//
import Foundation
typealias Term = Cofree<JSONLeaf, Range<String.Index>>
typealias Diff = Free<JSONLeaf, Term.Annotation, Patch<Term>>
public enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable {
public enum JSONLeaf: Categorizable, CustomJSONConvertible, CustomStringConvertible, Equatable {
case Number(Double)
case Boolean(Bool)
case String(Swift.String)
case Null
// MARK: Categorizable
public var categories: Set<Swift.String> {
switch self {
case .Number:
return [ "number" ]
case .Boolean:
return [ "boolean" ]
case .String:
return [ "string" ]
case .Null:
return [ "null" ]
}
}
// MARK: CustomJSONConvertible
public var JSON: Doubt.JSON {

@ -1 +0,0 @@
Subproject commit 5acee08f86a848f56560548004422f989169042d

@ -0,0 +1 @@
Subproject commit 74444328e7910ea0d42a4a735b336a963e4b1105

View File

@ -1,4 +1,15 @@
struct Info: Categorizable, CustomJSONConvertible, Equatable {
init(range: Range<Int>, categories: Set<String>) {
self.range = range
self.categories = categories
}
init(range: Range<String.CharacterView.Index>, categories: Set<String>) {
// FIXME: this is terrible. see also https://github.com/github/semantic-diff/issues/136
self.range = Int(String(range.startIndex))!..<Int(String(range.endIndex))!
self.categories = categories
}
let range: Range<Int>

View File

@ -1,6 +1,15 @@
import Cocoa
import Doubt
import Prelude
import Madness
func benchmark<T>(label: String? = nil, _ f: () -> T) -> T {
let start = NSDate.timeIntervalSinceReferenceDate()
let result = f()
let end = NSDate.timeIntervalSinceReferenceDate()
print((label.map { "\($0): " } ?? "") + "\(end - start)s")
return result
}
extension String: ErrorType {}
@ -9,14 +18,20 @@ typealias Parser = String throws -> Term
struct Source {
init(_ argument: String) throws {
URL = NSURL(string: argument) ?? NSURL(fileURLWithPath: argument)
guard let type = URL.pathExtension else { throw "cannot tell the type of \(URL)" }
self.type = type
let supportedSchemes = [ "http", "https", "file" ]
if let URL = NSURL(string: argument) where supportedSchemes.contains(URL.scheme) {
self.URL = URL
} else {
self.URL = NSURL(fileURLWithPath: argument)
}
contents = try NSString(contentsOfURL: URL, encoding: NSUTF8StringEncoding) as String
}
let URL: NSURL
let type: String
var type: String {
if let pathExtension = URL.pathExtension where pathExtension != "" { return pathExtension }
return URL.fragment ?? ""
}
let contents: String
private static let languagesByType: [String:TSLanguage] = [
@ -82,13 +97,59 @@ func termWithInput(language: TSLanguage)(_ string: String) throws -> Term {
}
}
func toTerm(term: CofreeJSON) -> Term {
let annotation = Info(range: term.extract, categories: [])
switch term.unwrap {
case let .Leaf(a):
return Term(Info(range: term.extract, categories: a.categories), Syntax<Term, String>.Leaf(String(a)))
case let .Indexed(i):
return Term(annotation, .Indexed(i.map(toTerm)))
case let .Fixed(f):
return Term(annotation, .Fixed(f.map(toTerm)))
case let .Keyed(k):
return Term(annotation, .Keyed(Dictionary(elements: k.map { ($0, toTerm($1)) })))
}
}
func lines(input: String) -> Term {
var lines: [Term] = []
var previous = 0
input.enumerateSubstringsInRange(input.characters.indices, options: .ByLines) { (line, _, enclosingRange, _) in
let range: Range<Int> = previous..<(previous + enclosingRange.count)
previous = range.endIndex
if let line = line {
lines.append(Term(Info(range: range, categories: []), Syntax.Leaf(line)))
}
}
return Term(Info(range: 0..<input.utf16.count, categories: []), .Indexed(lines))
}
func parserForType(type: String) -> String throws -> Term {
switch type {
case "json":
return { (input: String) throws -> Term in
switch parse(json, input: input.characters) {
case let .Right(term):
return toTerm(term)
case let .Left(error):
throw error.description
}
}
default:
if let parser = Source.languagesByType[type].map(termWithInput) {
return parser
}
return lines
}
}
let arguments = BoundsCheckedArray(array: Process.arguments)
guard let aSource = try arguments[1].map(Source.init) else { throw "need source A" }
guard let bSource = try arguments[2].map(Source.init) else { throw "need source B" }
let jsonURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent("diff.json")
guard let uiPath = NSBundle.mainBundle().infoDictionary?["PathToUISource"] as? String else { throw "need ui path" }
guard aSource.type == bSource.type else { throw "cant compare files of different types" }
guard let parser = Source.languagesByType[aSource.type].map(termWithInput) else { throw "dont know how to parse files of type \(aSource.type)" }
let parser = parserForType(aSource.type)
let a = try parser(aSource.contents)
let b = try parser(bSource.contents)

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 GitHub. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -1,81 +0,0 @@
import Cocoa
import Doubt
import Either
import Prelude
import Madness
func benchmark<T>(label: String? = nil, _ f: () -> T) -> T {
let start = NSDate.timeIntervalSinceReferenceDate()
let result = f()
let end = NSDate.timeIntervalSinceReferenceDate()
print((label.map { "\($0): " } ?? "") + "\(end - start)s")
return result
}
let arguments = BoundsCheckedArray(array: Process.arguments)
let empty = "{}\n"
print(parse(json, input: empty))
let dict = "{\"hello\":\"world\"}"
print(parse(json, input: dict))
let dictWithSpaces = "{ \"hello\" : \"world\" }"
print(parse(json, input: dictWithSpaces))
let dictWithMembers = "{\"hello\":\"world\",\"sup\":\"cat\"}"
print(parse(json, input: dictWithMembers))
let dictWithArray = "{\"hello\": [\"world\"],\"sup\": [\"cat\", \"dog\", \"keith\"] }"
print(parse(json, input: dictWithArray))
func diffAndSerialize(a aString: String, b bString: String, to: String) throws {
let aParsed = benchmark("parsing a") { curry(parse)(json)(aString) }
guard let a = aParsed.right else {
_ = aParsed.left.map { print("error parsing a:", $0) }
return
}
let bParsed = benchmark("parsing b") { curry(parse)(json)(bString) }
guard let b = bParsed.right else {
_ = bParsed.left.map { print("error parsing b:", $0) }
return
}
let diff = benchmark("diffing a & b") {
Interpreter<CofreeJSON>(equal: CofreeJSON.equals(annotation: const(true), leaf: ==), comparable: const(true), cost: Free.sum(Patch.difference)).run(a, b)
}
let range: Range<String.Index> -> Doubt.JSON = {
let start = Int(String($0.startIndex))!
let end = Int(String($0.endIndex))!
return [
.Number(Double(start)),
.Number(Double(end - start)),
]
}
let JSON: Doubt.JSON = benchmark("converting diff to JSON") {
[
"before": .String(aString),
"after": .String(bString),
"diff": diff.JSON(pure: { $0.JSON { $0.JSON(annotation: range, leaf: { $0.JSON }) } }, leaf: { $0.JSON }, annotation: {
[
"before": range($0),
"after": range($1),
]
}),
]
}
let data = benchmark("serializing JSON to NSData") {
JSON.serialize()
}
try data.writeToFile(to, options: .DataWritingAtomic)
}
let readFile = { (path: String) -> String? in
guard let data = try? NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding) else { return nil }
return data as String?
}
if let a = arguments[1].flatMap(readFile), b = arguments[2].flatMap(readFile), c = arguments[3] {
try diffAndSerialize(a: a, b: b, to: c)
}