├── .gitignore
├── Patch.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── Patch
├── GopherPageStyle-Light.css
├── GopherPageStyle-Dark.css
├── Gopher.swift
├── PreferencesWindowController.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── GopherResponsePart.swift
├── GopherResponse.swift
├── Info.plist
├── GopherRequest.swift
├── GopherPage.swift
├── AppDelegate.swift
├── PreferencesWindow.xib
├── MainWindowController.swift
├── MainWindow.xib
└── Base.lproj
│ └── MainMenu.xib
├── Patch.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── README.md
├── Podfile.lock
├── Podfile
├── PatchTests
├── Info.plist
└── PatchTests.swift
└── PatchUITests
├── Info.plist
└── PatchUITests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | Pods
2 | xcuserdata
3 |
--------------------------------------------------------------------------------
/Patch.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Patch/GopherPageStyle-Light.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: monospace;
3 | margin: 0 auto;
4 | white-space: pre;
5 | }
6 |
7 | body .type-text, .type-directory {
8 | padding: 1em;
9 | }
10 |
11 | img {
12 | max-width: 100%;
13 | max-height: 100%;
14 | }
15 |
--------------------------------------------------------------------------------
/Patch.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Patch.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Patch/GopherPageStyle-Dark.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: black;
3 | color: white;
4 | font-family: monospace;
5 | margin: 0 auto;
6 | white-space: pre;
7 | }
8 |
9 | body .type-text, .type-directory {
10 | padding: 1em;
11 | }
12 |
13 | img {
14 | max-width: 100%;
15 | max-height: 100%;
16 | }
17 |
--------------------------------------------------------------------------------
/Patch/Gopher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Gopher.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 3/15/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum GopherStatus {
12 | case Queued, Loading, Loaded, Parsed, Failed
13 | }
14 |
15 | enum GopherResponseError {
16 | case Encoding, Incomplete
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Patch
2 |
3 | Patch is a native, modern [Gopher](https://en.wikipedia.org/wiki/Gopher_(protocol)) client for macOS. The app is not stable and highly experimental.
4 |
5 | ## Technologies
6 |
7 | - Swift 4.2
8 | - macOS SDK
9 |
10 | ## Requirements
11 |
12 | - macOS 10.12+
13 | - Xcode 11.3+
14 | - [CocoaPods](https://cocoapods.org) (see packages in [`Podfile`](Podfile))
15 |
16 | ## Building
17 |
18 | 1. Install [CocoaPods](https://cocoapods.org). Run `$ pod install`.
19 | 2. Open `Patch.xcworkspace`.
20 | 3. Run.
21 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - BlueSocket (1.0.52)
3 | - ReactiveSwift (7.0.0)
4 | - Swime (3.0.6)
5 |
6 | DEPENDENCIES:
7 | - BlueSocket (~> 1.0)
8 | - ReactiveSwift (~> 7.0)
9 | - Swime (~> 3.0)
10 |
11 | SPEC REPOS:
12 | trunk:
13 | - BlueSocket
14 | - ReactiveSwift
15 | - Swime
16 |
17 | SPEC CHECKSUMS:
18 | BlueSocket: 1acd943acb07b55905291d608649fcfbf8cbd57d
19 | ReactiveSwift: 48c4b9d3b497e8dd20b10300bb1a28ff93550f13
20 | Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
21 |
22 | PODFILE CHECKSUM: ab1ebfbac945a0cdfbfd9e467b46d13708eb018e
23 |
24 | COCOAPODS: 1.11.3
25 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'Patch' do
5 | platform :macos, '10.11'
6 |
7 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
8 | use_frameworks!
9 |
10 | pod 'BlueSocket', '~> 1.0'
11 | pod 'ReactiveSwift', '~> 7.0'
12 | pod 'Swime', '~> 3.0'
13 |
14 | # Pods for Patch
15 |
16 | target 'PatchTests' do
17 | inherit! :search_paths
18 | # Pods for testing
19 | end
20 |
21 | target 'PatchUITests' do
22 | inherit! :search_paths
23 | # Pods for testing
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/PatchTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PatchUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Patch/PreferencesWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreferencesWindowController.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 11/5/22.
6 | // Copyright © 2022 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import AppKit
10 | import Foundation
11 |
12 | class PreferencesWindowController: NSWindowController {
13 |
14 | @IBOutlet weak var homepageTextField: NSTextField!
15 |
16 | override var windowNibName : String! {
17 | return "PreferencesWindow"
18 | }
19 |
20 | @IBAction func setHomepageToCurrent(sender: AnyObject?) {
21 | guard let mainWindowController = appDelegate.topMainWindowController else {
22 | return
23 | }
24 | homepageTextField.becomeFirstResponder()
25 | homepageTextField.currentEditor()?.insertText(mainWindowController.urlTextField.stringValue)
26 | homepageTextField.selectText(nil)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/PatchTests/PatchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PatchTests.swift
3 | // PatchTests
4 | //
5 | // Created by Jacob Budin on 3/15/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Patch
11 |
12 | class PatchTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Patch/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/Patch/GopherResponsePart.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GopherResponsePart.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 4/8/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class GopherResponsePart {
12 |
13 | let type: Character
14 | let content: String
15 | let url: URL?
16 |
17 | var html: String {
18 | if type == "0" || type == "1" { // file or directory
19 | return "
" + content + "
"
20 | }
21 | else if type == "g" || type == "I" { // GIF or image
22 | return "Image: " + content + "
"
23 | }
24 |
25 | return "" + content + "
"
26 | }
27 |
28 | init(string: String) {
29 | let parts = string.components(separatedBy: "\t")
30 |
31 | self.type = string[string.startIndex]
32 | self.content = String(parts[0][string.index(string.startIndex, offsetBy: 1)...])
33 |
34 | if parts.count >= 3 {
35 | self.url = URL(string: "gopher://" + parts[2] + parts[1])
36 | }
37 | else {
38 | self.url = nil
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/PatchUITests/PatchUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PatchUITests.swift
3 | // PatchUITests
4 | //
5 | // Created by Jacob Budin on 3/15/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class PatchUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | XCUIApplication().launch()
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testExample() {
32 | // Use recording to get started writing UI tests.
33 | // Use XCTAssert and related functions to verify your tests produce the correct results.
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Patch/GopherResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GopherResponse.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 4/8/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class GopherResponse {
12 |
13 | let text: String?
14 | let data: Data?
15 | var error: GopherResponseError?
16 | let lineSeparator = String(bytes: [13, 10], encoding: String.Encoding.ascii)!
17 |
18 | var isText: Bool { text != nil }
19 | var isBinary: Bool { data != nil }
20 |
21 | var isDirectory: Bool {
22 | guard let text = text else {
23 | return false
24 | }
25 |
26 | if text.contains("\n\n") {
27 | return false
28 | }
29 |
30 | let parts = text.components(separatedBy: lineSeparator)
31 |
32 | for part in parts.dropLast() {
33 | // if there's an empty line, this cannot be a directory listing
34 | if part.isEmpty {
35 | return false
36 | }
37 | }
38 |
39 | return true
40 | }
41 |
42 | init(data: Data) {
43 | guard let text = String(data: data, encoding: .utf8) else {
44 | self.data = data
45 | self.text = nil
46 | return
47 | }
48 |
49 | self.text = text
50 | self.data = nil
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Patch/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleTypeRole
25 | Viewer
26 | CFBundleURLName
27 | $(PRODUCT_BUNDLE_IDENTIFIER)
28 | CFBundleURLSchemes
29 |
30 | gopher
31 |
32 |
33 |
34 | CFBundleVersion
35 | 1
36 | LSMinimumSystemVersion
37 | $(MACOSX_DEPLOYMENT_TARGET)
38 | NSHumanReadableCopyright
39 | Copyright © 2017 Jacob Budin. All rights reserved.
40 | NSMainNibFile
41 | MainMenu
42 | NSPrincipalClass
43 | NSApplication
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Patch/GopherRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GopherRequest.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 4/8/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Socket
11 |
12 | class GopherRequest {
13 | let url: URL
14 | var socket: Socket?
15 |
16 | init?(url: URL) {
17 | self.url = url
18 | }
19 |
20 | func load(handler: @escaping (Data) -> Void) throws {
21 | guard let host = self.url.host else {
22 | throw URLError(URLError.cannotFindHost)
23 | }
24 |
25 | socket = try Socket.create(family: Socket.ProtocolFamily.inet)
26 | try socket!.connect(to: host, port: 70)
27 |
28 | let queue = DispatchQueue.global(qos: .default)
29 |
30 | // Create the run loop work item and dispatch to the default priority global queue...
31 | queue.async { [unowned socket] in
32 |
33 | var shouldKeepRunning = true
34 |
35 | var readData = Data(capacity: 1024000)
36 |
37 | do {
38 | var requestData = Data(base64Encoded: "DQo=", options: NSData.Base64DecodingOptions())
39 |
40 | if self.url.path.isEmpty == false {
41 | let crlf = String(bytes: [13, 10], encoding: String.Encoding.ascii)!
42 | // TODO: Retain real selector path (i.e., does it include the starting with `/`
43 | requestData = String(self.url.path).appending(crlf).data(using: String.Encoding.ascii)
44 | }
45 |
46 | try socket!.write(from: requestData!)
47 |
48 | repeat {
49 | let bytesRead = try socket!.read(into: &readData)
50 |
51 | if bytesRead == 0 {
52 | shouldKeepRunning = false
53 | break
54 | }
55 |
56 | } while shouldKeepRunning
57 |
58 | socket!.close()
59 | handler(readData)
60 | }
61 | catch let error {
62 | guard error is Socket.Error else {
63 | print("Unexpected error by connection at \(socket!.remoteHostname):\(socket!.remotePort)...")
64 | return
65 | }
66 | }
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/Patch/GopherPage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GopherPage.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 4/8/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 | import Swime
12 |
13 | class GopherPage {
14 | var request: GopherRequest?
15 | var response: GopherResponse?
16 | var status: MutableProperty = MutableProperty(.Queued)
17 |
18 | var html: String {
19 | let type = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") ?? "Light"
20 | let cssName = "GopherPageStyle-" + type
21 | guard let cssPath = Bundle.main.path(forResource: cssName, ofType: "css") else {
22 | fatalError("Gopher page stylesheet (\(cssName)) could not be located")
23 | }
24 |
25 | let styles: String
26 | do {
27 | styles = try String(contentsOfFile: cssPath, encoding: .utf8)
28 | } catch {
29 | fatalError("Gopher page stylesheet (\(cssName)) could not be loaded")
30 | }
31 |
32 | var contentHtml: String
33 | var contentType: String
34 |
35 | if (self.status.value == GopherStatus.Failed) {
36 | print("Showing error...")
37 | contentHtml = "Could not load \(self.request!.url)"
38 | contentType = "error"
39 | }
40 | else if (self.response?.isBinary)! {
41 | print("Showing binary...")
42 | contentHtml = parseBinary()
43 | contentType = "image"
44 | }
45 | else if (self.response?.isDirectory)! {
46 | print("Showing directory...")
47 | contentHtml = parseDirectory().map({
48 | $0.html
49 | }).joined()
50 | contentType = "directory"
51 | }
52 | else {
53 | print("Showing file...")
54 | contentHtml = parsePlain()
55 | contentType = "text"
56 | }
57 |
58 | return "" + contentHtml + ""
59 | }
60 |
61 | let lineSeparator = String(bytes: [13, 10], encoding: String.Encoding.ascii)!
62 | let terminatingSequence = [".", ""]
63 |
64 | init(url: URL) {
65 | self.request = GopherRequest(url: url)
66 | }
67 |
68 | func load() {
69 | guard let request = self.request else {
70 | return
71 | }
72 |
73 | self.status.value = .Loading
74 |
75 | do {
76 | try request.load() {
77 | (data) in
78 | self.status.value = .Loaded
79 | self.response = GopherResponse(data: data)
80 | self.status.value = .Parsed
81 | }
82 | } catch {
83 | self.status.value = .Failed
84 | }
85 | }
86 |
87 | private func parseBinary() -> String {
88 | guard let data = self.response?.data else {
89 | return ""
90 | }
91 | let mimeType = Swime.mimeType(data: data)
92 | let encodedData = data.base64EncodedString()
93 | switch mimeType?.type {
94 | case .gif?, .jpg?, .png?, .webp?:
95 | guard let mime = mimeType?.mime else {
96 | return ""
97 | }
98 | return "
"
99 | default:
100 | return ""
101 | }
102 | }
103 |
104 | private func parsePlain() -> String {
105 | guard let body = self.response?.text else {
106 | return ""
107 | }
108 |
109 | let html = body.components(separatedBy: "\n\n").map({
110 | "" + $0 + "
"
111 | }).joined()
112 |
113 | return html
114 | }
115 |
116 | private func parseDirectory() -> [GopherResponsePart] {
117 | guard let parts = self.response?.text?.components(separatedBy: lineSeparator) else {
118 | return []
119 | }
120 |
121 | let completeResponse = parts.suffix(2).elementsEqual(terminatingSequence)
122 | if completeResponse == false {
123 | return []
124 | }
125 |
126 | return parts.dropLast(2).map {
127 | return GopherResponsePart(string: $0)
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Patch/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 3/15/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSWindowController {
12 | var appDelegate: AppDelegate {
13 | return NSApplication.shared.delegate as! AppDelegate
14 | }
15 | }
16 |
17 | @NSApplicationMain
18 | class AppDelegate: NSObject, NSApplicationDelegate {
19 |
20 | var topMainWindowController: MainWindowController?
21 | var mainWindowControllers: Set = []
22 | var preferencesWindowController: PreferencesWindowController?
23 |
24 | @objc dynamic var backEnabled: Bool {
25 | guard let mainWindowController = topMainWindowController else {
26 | return false
27 | }
28 | return mainWindowController.backEnabled
29 | }
30 |
31 | @objc dynamic var forwardEnabled: Bool {
32 | guard let mainWindowController = topMainWindowController else {
33 | return false
34 | }
35 | return mainWindowController.forwardEnabled
36 | }
37 |
38 | func applicationDidFinishLaunching(_ aNotification: Notification) {
39 | NotificationCenter.default.addObserver(self, selector: #selector(onWindowBecomeMain), name: NSWindow.didBecomeMainNotification, object: nil)
40 | NotificationCenter.default.addObserver(self, selector: #selector(onWindowWillClose), name: NSWindow.willCloseNotification, object: nil)
41 | newWindow(url: nil)
42 | }
43 |
44 | func applicationWillTerminate(_ aNotification: Notification) {
45 | // Insert code here to tear down your application
46 | }
47 |
48 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
49 | for mainWindowController in mainWindowControllers {
50 | mainWindowController.window?.makeKeyAndOrderFront(self)
51 | }
52 |
53 | return true
54 | }
55 |
56 | @objc func onWindowWillClose(notification: Notification) {
57 | let windowController = (notification.object as? NSWindow)?.delegate
58 |
59 | if let mainWindowController = windowController as? MainWindowController {
60 | mainWindowControllers.remove(mainWindowController)
61 | if mainWindowController == topMainWindowController {
62 | topMainWindowController = nil
63 | }
64 | mainWindowController.dismissController(self)
65 | }
66 |
67 | if let preferencesWindowController = windowController as? PreferencesWindowController {
68 | self.preferencesWindowController = nil
69 | preferencesWindowController.dismissController(self)
70 | }
71 | }
72 |
73 | @objc func onWindowBecomeMain(notification: Notification) {
74 | let windowController = (notification.object as? NSWindow)?.delegate
75 |
76 | if let mainWindowController = windowController as? MainWindowController {
77 | topMainWindowController = mainWindowController
78 | }
79 | }
80 |
81 | func newWindow(url: URL?) {
82 | let mainWindowController: MainWindowController
83 | if let url = url {
84 | mainWindowController = MainWindowController(url: url)
85 | }
86 | else {
87 | mainWindowController = MainWindowController()
88 | }
89 | mainWindowController.window?.makeKeyAndOrderFront(self)
90 | mainWindowController.window?.makeFirstResponder(mainWindowController.window)
91 | mainWindowControllers.insert(mainWindowController)
92 | }
93 |
94 | func newWindowIfNone() {
95 | if mainWindowControllers.isEmpty {
96 | newWindow(url: nil)
97 | }
98 | }
99 |
100 | func openPreferences() {
101 | if preferencesWindowController == nil {
102 | preferencesWindowController = PreferencesWindowController()
103 | }
104 | preferencesWindowController!.window?.makeKeyAndOrderFront(self)
105 | preferencesWindowController!.window?.makeFirstResponder(preferencesWindowController!.window)
106 | }
107 |
108 | @IBAction func newDocument(sender: AnyObject?) {
109 | newWindow(url: nil)
110 | }
111 |
112 | @IBAction func openPreferences(sender: AnyObject?) {
113 | openPreferences()
114 | }
115 |
116 | @IBAction func openLocation(sender: AnyObject?) {
117 | newWindowIfNone()
118 | topMainWindowController?.urlTextField.selectText(nil)
119 | }
120 |
121 | func application(_ application: NSApplication, open urls: [URL]) {
122 | for url in urls {
123 | newWindow(url: url)
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Patch/PreferencesWindow.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Patch/MainWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainWindowController.swift
3 | // Patch
4 | //
5 | // Created by Jacob Budin on 3/15/17.
6 | // Copyright © 2017 Jacob Budin. All rights reserved.
7 | //
8 |
9 | import AppKit
10 | import Foundation
11 | import WebKit
12 | import ReactiveSwift
13 |
14 | class MainWindowController: NSWindowController, WKNavigationDelegate {
15 |
16 | @IBOutlet weak var urlTextField: NSTextField!
17 | @IBOutlet weak var contentWebView: WKWebView!
18 |
19 | var history: [URL] = []
20 | var historyI = -1
21 | var initialUrl: URL?
22 | var page: GopherPage?
23 | var loaded = false
24 |
25 | @objc dynamic var backEnabled = false
26 | @objc dynamic var forwardEnabled = false
27 |
28 | var homepage: URL? {
29 | guard let homepage = NSUserDefaultsController.shared.defaults.string(forKey: "homepage") else {
30 | return URL(string: "gopher://gopher.floodgap.com")
31 | }
32 |
33 | return URL(string: homepage)
34 | }
35 |
36 | override var windowNibName : String! {
37 | return "MainWindow"
38 | }
39 |
40 | convenience init(url: URL) {
41 | self.init()
42 | self.initialUrl = url
43 | }
44 |
45 | override func windowDidLoad() {
46 | // Load home page
47 | if let url = initialUrl {
48 | load(url)
49 | }
50 | else {
51 | load(homepage!)
52 | }
53 | }
54 |
55 | /*
56 | Load previous state in history
57 | */
58 | @IBAction func back(sender: AnyObject?) {
59 | // Disallow when on "first" page
60 | if historyI == 0 {
61 | return
62 | }
63 |
64 | historyI -= 1
65 | let previousUrl = history[historyI]
66 | load(previousUrl, affectsHistory: false)
67 | }
68 |
69 | /*
70 | Undo previous state in history
71 | */
72 | @IBAction func forward(sender: AnyObject?) {
73 | // Disallow when on "last" page
74 | if history.count == historyI + 1 {
75 | return
76 | }
77 |
78 | historyI += 1
79 | let nextUrl = history[historyI]
80 | load(nextUrl, affectsHistory: false)
81 | }
82 |
83 | /*
84 | Listen to home page clicks
85 | */
86 | @IBAction func home(sender: AnyObject?) {
87 | load(homepage!)
88 | }
89 |
90 | func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
91 | if navigationAction.navigationType == WKNavigationType.other {
92 | return WKNavigationActionPolicy.allow
93 | }
94 | if let url = navigationAction.request.mainDocumentURL {
95 | load(url)
96 | }
97 | return WKNavigationActionPolicy.cancel
98 | }
99 |
100 | /*
101 | Listen to submit of URL input
102 | */
103 | @IBAction func submit(sender: AnyObject?) {
104 | print("submit")
105 | guard let url = URL(string: urlTextField.stringValue) else {
106 | return
107 | }
108 |
109 | load(url)
110 | }
111 |
112 | /*
113 | Load URL in webview
114 | */
115 | func load(_ url: URL, affectsHistory: Bool = true) {
116 | print("Loading \(url)")
117 | urlTextField.stringValue = url.absoluteString
118 |
119 | let page = GopherPage(url: url)
120 | loaded = false
121 | page.status.signal.observe(on: UIScheduler()).observeValues { _ in
122 | if self.loaded == true {
123 | return
124 | }
125 |
126 | if page.status.value == .Parsed {
127 | self.loaded = true
128 | guard page.response != nil else {
129 | return
130 | }
131 | let html = page.html
132 | let url = URL(string: page.request!.url.path)
133 | self.contentWebView.loadHTMLString(html, baseURL: url)
134 | }
135 | else if page.status.value == .Failed {
136 | self.loaded = true
137 | let html = page.html
138 | self.contentWebView.loadHTMLString(html, baseURL: nil)
139 | }
140 | }
141 | page.load()
142 |
143 | if affectsHistory {
144 | // Never modify history on same URL
145 | if self.history.last == url {
146 | return
147 | }
148 |
149 | // Remove any forward-facing URLs
150 | self.history.removeLast(self.history.count - self.historyI - 1)
151 |
152 | // Update history
153 | self.historyI += 1
154 | self.history.append(url)
155 | }
156 |
157 | self.page = page
158 | self.backEnabled = self.historyI > 0
159 | self.forwardEnabled = self.history.count != self.historyI + 1
160 | }
161 |
162 | /*
163 | Save current page to file
164 | */
165 | @IBAction func saveDocumentAs(sender: AnyObject?) {
166 | let panel = NSSavePanel()
167 | let url = self.page?.request?.url
168 | let fileName = url?.pathComponents.count != 0 ? url?.pathComponents.last : url?.host
169 | panel.nameFieldStringValue = "\(fileName ?? "untitled").html"
170 | panel.allowedFileTypes = ["html"]
171 | panel.allowsOtherFileTypes = false
172 | panel.begin { (result) in
173 | guard
174 | result.rawValue == NSFileHandlingPanelOKButton,
175 | let fileLocation = panel.url
176 | else {
177 | return
178 | }
179 |
180 | do {
181 | try self.page?.html.data(using: .utf8)?.write(to: fileLocation)
182 | } catch {
183 | // TODO: Write error
184 | }
185 | }
186 | }
187 |
188 | /*
189 | Print current page
190 | */
191 | @available(macOS 11.0, *)
192 | @IBAction func printAs(sender: AnyObject?) {
193 | let info = NSPrintInfo.shared
194 | let operation = contentWebView.printOperation(with: info)
195 | operation.view?.frame = contentWebView.bounds
196 |
197 | guard let window = contentWebView.window else { return }
198 |
199 | operation.runModal(for: window, delegate: nil, didRun: nil, contextInfo: nil)
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/Patch/MainWindow.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/Patch/Base.lproj/MainMenu.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
416 |
417 |
418 |
419 |
--------------------------------------------------------------------------------
/Patch.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 20DAC8926DEC11E0A9C5F9C1 /* Pods_Patch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 974BC531AAC39DB943DA3EE6 /* Pods_Patch.framework */; };
11 | A0129795291760D8008314BE /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0129794291760D8008314BE /* PreferencesWindow.xib */; };
12 | A012979729176275008314BE /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A012979629176275008314BE /* PreferencesWindowController.swift */; };
13 | A02176A71E99B68900180ED6 /* GopherPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02176A61E99B68900180ED6 /* GopherPage.swift */; };
14 | A02176A91E99B6AB00180ED6 /* GopherRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02176A81E99B6AB00180ED6 /* GopherRequest.swift */; };
15 | A02176AB1E99B6DA00180ED6 /* GopherResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02176AA1E99B6DA00180ED6 /* GopherResponse.swift */; };
16 | A02176AD1E99B6EB00180ED6 /* GopherResponsePart.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02176AC1E99B6EB00180ED6 /* GopherResponsePart.swift */; };
17 | A08ED1622916D58200B3C4A8 /* GopherPageStyle-Dark.css in Resources */ = {isa = PBXBuildFile; fileRef = A08ED1612916D0F700B3C4A8 /* GopherPageStyle-Dark.css */; };
18 | A093BE721E7A15B400884283 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A093BE711E7A15B400884283 /* AppDelegate.swift */; };
19 | A093BE741E7A15B400884283 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A093BE731E7A15B400884283 /* Assets.xcassets */; };
20 | A093BE771E7A15B400884283 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = A093BE751E7A15B400884283 /* MainMenu.xib */; };
21 | A093BE821E7A15B400884283 /* PatchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A093BE811E7A15B400884283 /* PatchTests.swift */; };
22 | A093BE8D1E7A15B400884283 /* PatchUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A093BE8C1E7A15B400884283 /* PatchUITests.swift */; };
23 | A0ACB5B01E8DDDB8009FC2D1 /* GopherPageStyle-Light.css in Resources */ = {isa = PBXBuildFile; fileRef = A0ACB5AF1E8DDDB8009FC2D1 /* GopherPageStyle-Light.css */; };
24 | A0C7B7FF1E91AE4E0010BD86 /* Gopher.swift in Sources */ = {isa = PBXBuildFile; fileRef = A093BE9C1E7A185200884283 /* Gopher.swift */; };
25 | A0DAE5142439687200DA2CE7 /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0DAE5132439687100DA2CE7 /* MainWindowController.swift */; };
26 | A0EFA2B61E7A269A005BED6B /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0EFA2B51E7A269A005BED6B /* MainWindow.xib */; };
27 | F814D148AFA92AC171CE6EEA /* Pods_PatchTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F9AF05B37DCF4DAADCE3D1F /* Pods_PatchTests.framework */; };
28 | F88A74BE4F2E6D21EB8E0ACF /* Pods_PatchUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D5876C85B35E56955E3D93 /* Pods_PatchUITests.framework */; };
29 | /* End PBXBuildFile section */
30 |
31 | /* Begin PBXContainerItemProxy section */
32 | A093BE7E1E7A15B400884283 /* PBXContainerItemProxy */ = {
33 | isa = PBXContainerItemProxy;
34 | containerPortal = A093BE661E7A15B400884283 /* Project object */;
35 | proxyType = 1;
36 | remoteGlobalIDString = A093BE6D1E7A15B400884283;
37 | remoteInfo = Patch;
38 | };
39 | A093BE891E7A15B400884283 /* PBXContainerItemProxy */ = {
40 | isa = PBXContainerItemProxy;
41 | containerPortal = A093BE661E7A15B400884283 /* Project object */;
42 | proxyType = 1;
43 | remoteGlobalIDString = A093BE6D1E7A15B400884283;
44 | remoteInfo = Patch;
45 | };
46 | /* End PBXContainerItemProxy section */
47 |
48 | /* Begin PBXFileReference section */
49 | 0779031732B8B817151D8258 /* Pods-PatchTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PatchTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PatchTests/Pods-PatchTests.debug.xcconfig"; sourceTree = ""; };
50 | 3F9AF05B37DCF4DAADCE3D1F /* Pods_PatchTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PatchTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
51 | 46528105DF5300D80CF24631 /* Pods-PatchTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PatchTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PatchTests/Pods-PatchTests.release.xcconfig"; sourceTree = ""; };
52 | 6591A9162B512F9C3D688CEA /* Pods-PatchUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PatchUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PatchUITests/Pods-PatchUITests.debug.xcconfig"; sourceTree = ""; };
53 | 974BC531AAC39DB943DA3EE6 /* Pods_Patch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Patch.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54 | A0129794291760D8008314BE /* PreferencesWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PreferencesWindow.xib; sourceTree = ""; };
55 | A012979629176275008314BE /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; };
56 | A02176A61E99B68900180ED6 /* GopherPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GopherPage.swift; sourceTree = ""; };
57 | A02176A81E99B6AB00180ED6 /* GopherRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GopherRequest.swift; sourceTree = ""; };
58 | A02176AA1E99B6DA00180ED6 /* GopherResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GopherResponse.swift; sourceTree = ""; };
59 | A02176AC1E99B6EB00180ED6 /* GopherResponsePart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GopherResponsePart.swift; sourceTree = ""; };
60 | A08ED1612916D0F700B3C4A8 /* GopherPageStyle-Dark.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = "GopherPageStyle-Dark.css"; sourceTree = ""; };
61 | A093BE6E1E7A15B400884283 /* Patch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Patch.app; sourceTree = BUILT_PRODUCTS_DIR; };
62 | A093BE711E7A15B400884283 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
63 | A093BE731E7A15B400884283 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
64 | A093BE761E7A15B400884283 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
65 | A093BE781E7A15B400884283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
66 | A093BE7D1E7A15B400884283 /* PatchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PatchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
67 | A093BE811E7A15B400884283 /* PatchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchTests.swift; sourceTree = ""; };
68 | A093BE831E7A15B400884283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
69 | A093BE881E7A15B400884283 /* PatchUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PatchUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
70 | A093BE8C1E7A15B400884283 /* PatchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchUITests.swift; sourceTree = ""; };
71 | A093BE8E1E7A15B400884283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
72 | A093BE9C1E7A185200884283 /* Gopher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Gopher.swift; sourceTree = ""; };
73 | A0ACB5AF1E8DDDB8009FC2D1 /* GopherPageStyle-Light.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = "GopherPageStyle-Light.css"; sourceTree = ""; };
74 | A0DAE5132439687100DA2CE7 /* MainWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = ""; };
75 | A0EFA2B51E7A269A005BED6B /* MainWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; };
76 | BE98003AB72354086962BBD1 /* Pods-Patch.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Patch.release.xcconfig"; path = "Pods/Target Support Files/Pods-Patch/Pods-Patch.release.xcconfig"; sourceTree = ""; };
77 | CDF9F3265897C3E3A4F041DB /* Pods-Patch.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Patch.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Patch/Pods-Patch.debug.xcconfig"; sourceTree = ""; };
78 | E5D5876C85B35E56955E3D93 /* Pods_PatchUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PatchUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
79 | F189FBBD0923581C6C334CC5 /* Pods-PatchUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PatchUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PatchUITests/Pods-PatchUITests.release.xcconfig"; sourceTree = ""; };
80 | /* End PBXFileReference section */
81 |
82 | /* Begin PBXFrameworksBuildPhase section */
83 | A093BE6B1E7A15B400884283 /* Frameworks */ = {
84 | isa = PBXFrameworksBuildPhase;
85 | buildActionMask = 2147483647;
86 | files = (
87 | 20DAC8926DEC11E0A9C5F9C1 /* Pods_Patch.framework in Frameworks */,
88 | );
89 | runOnlyForDeploymentPostprocessing = 0;
90 | };
91 | A093BE7A1E7A15B400884283 /* Frameworks */ = {
92 | isa = PBXFrameworksBuildPhase;
93 | buildActionMask = 2147483647;
94 | files = (
95 | F814D148AFA92AC171CE6EEA /* Pods_PatchTests.framework in Frameworks */,
96 | );
97 | runOnlyForDeploymentPostprocessing = 0;
98 | };
99 | A093BE851E7A15B400884283 /* Frameworks */ = {
100 | isa = PBXFrameworksBuildPhase;
101 | buildActionMask = 2147483647;
102 | files = (
103 | F88A74BE4F2E6D21EB8E0ACF /* Pods_PatchUITests.framework in Frameworks */,
104 | );
105 | runOnlyForDeploymentPostprocessing = 0;
106 | };
107 | /* End PBXFrameworksBuildPhase section */
108 |
109 | /* Begin PBXGroup section */
110 | 3409B2701B5AB5736FA40E2E /* Pods */ = {
111 | isa = PBXGroup;
112 | children = (
113 | CDF9F3265897C3E3A4F041DB /* Pods-Patch.debug.xcconfig */,
114 | BE98003AB72354086962BBD1 /* Pods-Patch.release.xcconfig */,
115 | 0779031732B8B817151D8258 /* Pods-PatchTests.debug.xcconfig */,
116 | 46528105DF5300D80CF24631 /* Pods-PatchTests.release.xcconfig */,
117 | 6591A9162B512F9C3D688CEA /* Pods-PatchUITests.debug.xcconfig */,
118 | F189FBBD0923581C6C334CC5 /* Pods-PatchUITests.release.xcconfig */,
119 | );
120 | name = Pods;
121 | sourceTree = "";
122 | };
123 | 8B27B894F8DBEDBB4C67E34E /* Frameworks */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 974BC531AAC39DB943DA3EE6 /* Pods_Patch.framework */,
127 | 3F9AF05B37DCF4DAADCE3D1F /* Pods_PatchTests.framework */,
128 | E5D5876C85B35E56955E3D93 /* Pods_PatchUITests.framework */,
129 | );
130 | name = Frameworks;
131 | sourceTree = "";
132 | };
133 | A02176AE1E99B72400180ED6 /* Gopher */ = {
134 | isa = PBXGroup;
135 | children = (
136 | A093BE9C1E7A185200884283 /* Gopher.swift */,
137 | A02176AC1E99B6EB00180ED6 /* GopherResponsePart.swift */,
138 | A02176AA1E99B6DA00180ED6 /* GopherResponse.swift */,
139 | A02176A81E99B6AB00180ED6 /* GopherRequest.swift */,
140 | A02176A61E99B68900180ED6 /* GopherPage.swift */,
141 | );
142 | name = Gopher;
143 | sourceTree = "";
144 | };
145 | A093BE651E7A15B400884283 = {
146 | isa = PBXGroup;
147 | children = (
148 | A093BE701E7A15B400884283 /* Patch */,
149 | A093BE801E7A15B400884283 /* PatchTests */,
150 | A093BE8B1E7A15B400884283 /* PatchUITests */,
151 | A093BE6F1E7A15B400884283 /* Products */,
152 | 3409B2701B5AB5736FA40E2E /* Pods */,
153 | 8B27B894F8DBEDBB4C67E34E /* Frameworks */,
154 | );
155 | sourceTree = "";
156 | };
157 | A093BE6F1E7A15B400884283 /* Products */ = {
158 | isa = PBXGroup;
159 | children = (
160 | A093BE6E1E7A15B400884283 /* Patch.app */,
161 | A093BE7D1E7A15B400884283 /* PatchTests.xctest */,
162 | A093BE881E7A15B400884283 /* PatchUITests.xctest */,
163 | );
164 | name = Products;
165 | sourceTree = "";
166 | };
167 | A093BE701E7A15B400884283 /* Patch */ = {
168 | isa = PBXGroup;
169 | children = (
170 | A093BE711E7A15B400884283 /* AppDelegate.swift */,
171 | A0DAE5132439687100DA2CE7 /* MainWindowController.swift */,
172 | A012979629176275008314BE /* PreferencesWindowController.swift */,
173 | A02176AE1E99B72400180ED6 /* Gopher */,
174 | A0ACB5AF1E8DDDB8009FC2D1 /* GopherPageStyle-Light.css */,
175 | A08ED1612916D0F700B3C4A8 /* GopherPageStyle-Dark.css */,
176 | A093BE731E7A15B400884283 /* Assets.xcassets */,
177 | A093BE751E7A15B400884283 /* MainMenu.xib */,
178 | A0EFA2B51E7A269A005BED6B /* MainWindow.xib */,
179 | A0129794291760D8008314BE /* PreferencesWindow.xib */,
180 | A093BE781E7A15B400884283 /* Info.plist */,
181 | );
182 | path = Patch;
183 | sourceTree = "";
184 | };
185 | A093BE801E7A15B400884283 /* PatchTests */ = {
186 | isa = PBXGroup;
187 | children = (
188 | A093BE811E7A15B400884283 /* PatchTests.swift */,
189 | A093BE831E7A15B400884283 /* Info.plist */,
190 | );
191 | path = PatchTests;
192 | sourceTree = "";
193 | };
194 | A093BE8B1E7A15B400884283 /* PatchUITests */ = {
195 | isa = PBXGroup;
196 | children = (
197 | A093BE8C1E7A15B400884283 /* PatchUITests.swift */,
198 | A093BE8E1E7A15B400884283 /* Info.plist */,
199 | );
200 | path = PatchUITests;
201 | sourceTree = "";
202 | };
203 | /* End PBXGroup section */
204 |
205 | /* Begin PBXNativeTarget section */
206 | A093BE6D1E7A15B400884283 /* Patch */ = {
207 | isa = PBXNativeTarget;
208 | buildConfigurationList = A093BE911E7A15B400884283 /* Build configuration list for PBXNativeTarget "Patch" */;
209 | buildPhases = (
210 | B5219CD20518C3C5C738C60E /* [CP] Check Pods Manifest.lock */,
211 | A093BE6A1E7A15B400884283 /* Sources */,
212 | A093BE6B1E7A15B400884283 /* Frameworks */,
213 | A093BE6C1E7A15B400884283 /* Resources */,
214 | 66ED9EA4E197A235912DD023 /* [CP] Embed Pods Frameworks */,
215 | );
216 | buildRules = (
217 | );
218 | dependencies = (
219 | );
220 | name = Patch;
221 | productName = Patch;
222 | productReference = A093BE6E1E7A15B400884283 /* Patch.app */;
223 | productType = "com.apple.product-type.application";
224 | };
225 | A093BE7C1E7A15B400884283 /* PatchTests */ = {
226 | isa = PBXNativeTarget;
227 | buildConfigurationList = A093BE941E7A15B400884283 /* Build configuration list for PBXNativeTarget "PatchTests" */;
228 | buildPhases = (
229 | D6416A975B6FAFE16C94973C /* [CP] Check Pods Manifest.lock */,
230 | A093BE791E7A15B400884283 /* Sources */,
231 | A093BE7A1E7A15B400884283 /* Frameworks */,
232 | A093BE7B1E7A15B400884283 /* Resources */,
233 | );
234 | buildRules = (
235 | );
236 | dependencies = (
237 | A093BE7F1E7A15B400884283 /* PBXTargetDependency */,
238 | );
239 | name = PatchTests;
240 | productName = PatchTests;
241 | productReference = A093BE7D1E7A15B400884283 /* PatchTests.xctest */;
242 | productType = "com.apple.product-type.bundle.unit-test";
243 | };
244 | A093BE871E7A15B400884283 /* PatchUITests */ = {
245 | isa = PBXNativeTarget;
246 | buildConfigurationList = A093BE971E7A15B400884283 /* Build configuration list for PBXNativeTarget "PatchUITests" */;
247 | buildPhases = (
248 | F6A01FF22DBDCB6BCEA01F11 /* [CP] Check Pods Manifest.lock */,
249 | A093BE841E7A15B400884283 /* Sources */,
250 | A093BE851E7A15B400884283 /* Frameworks */,
251 | A093BE861E7A15B400884283 /* Resources */,
252 | );
253 | buildRules = (
254 | );
255 | dependencies = (
256 | A093BE8A1E7A15B400884283 /* PBXTargetDependency */,
257 | );
258 | name = PatchUITests;
259 | productName = PatchUITests;
260 | productReference = A093BE881E7A15B400884283 /* PatchUITests.xctest */;
261 | productType = "com.apple.product-type.bundle.ui-testing";
262 | };
263 | /* End PBXNativeTarget section */
264 |
265 | /* Begin PBXProject section */
266 | A093BE661E7A15B400884283 /* Project object */ = {
267 | isa = PBXProject;
268 | attributes = {
269 | LastSwiftUpdateCheck = 0820;
270 | LastUpgradeCheck = 1130;
271 | ORGANIZATIONNAME = "Jacob Budin";
272 | TargetAttributes = {
273 | A093BE6D1E7A15B400884283 = {
274 | CreatedOnToolsVersion = 8.2.1;
275 | LastSwiftMigration = 1010;
276 | ProvisioningStyle = Automatic;
277 | };
278 | A093BE7C1E7A15B400884283 = {
279 | CreatedOnToolsVersion = 8.2.1;
280 | LastSwiftMigration = 1010;
281 | ProvisioningStyle = Automatic;
282 | TestTargetID = A093BE6D1E7A15B400884283;
283 | };
284 | A093BE871E7A15B400884283 = {
285 | CreatedOnToolsVersion = 8.2.1;
286 | LastSwiftMigration = 1010;
287 | ProvisioningStyle = Automatic;
288 | TestTargetID = A093BE6D1E7A15B400884283;
289 | };
290 | };
291 | };
292 | buildConfigurationList = A093BE691E7A15B400884283 /* Build configuration list for PBXProject "Patch" */;
293 | compatibilityVersion = "Xcode 3.2";
294 | developmentRegion = en;
295 | hasScannedForEncodings = 0;
296 | knownRegions = (
297 | en,
298 | Base,
299 | );
300 | mainGroup = A093BE651E7A15B400884283;
301 | productRefGroup = A093BE6F1E7A15B400884283 /* Products */;
302 | projectDirPath = "";
303 | projectRoot = "";
304 | targets = (
305 | A093BE6D1E7A15B400884283 /* Patch */,
306 | A093BE7C1E7A15B400884283 /* PatchTests */,
307 | A093BE871E7A15B400884283 /* PatchUITests */,
308 | );
309 | };
310 | /* End PBXProject section */
311 |
312 | /* Begin PBXResourcesBuildPhase section */
313 | A093BE6C1E7A15B400884283 /* Resources */ = {
314 | isa = PBXResourcesBuildPhase;
315 | buildActionMask = 2147483647;
316 | files = (
317 | A093BE741E7A15B400884283 /* Assets.xcassets in Resources */,
318 | A0ACB5B01E8DDDB8009FC2D1 /* GopherPageStyle-Light.css in Resources */,
319 | A08ED1622916D58200B3C4A8 /* GopherPageStyle-Dark.css in Resources */,
320 | A093BE771E7A15B400884283 /* MainMenu.xib in Resources */,
321 | A0129795291760D8008314BE /* PreferencesWindow.xib in Resources */,
322 | A0EFA2B61E7A269A005BED6B /* MainWindow.xib in Resources */,
323 | );
324 | runOnlyForDeploymentPostprocessing = 0;
325 | };
326 | A093BE7B1E7A15B400884283 /* Resources */ = {
327 | isa = PBXResourcesBuildPhase;
328 | buildActionMask = 2147483647;
329 | files = (
330 | );
331 | runOnlyForDeploymentPostprocessing = 0;
332 | };
333 | A093BE861E7A15B400884283 /* Resources */ = {
334 | isa = PBXResourcesBuildPhase;
335 | buildActionMask = 2147483647;
336 | files = (
337 | );
338 | runOnlyForDeploymentPostprocessing = 0;
339 | };
340 | /* End PBXResourcesBuildPhase section */
341 |
342 | /* Begin PBXShellScriptBuildPhase section */
343 | 66ED9EA4E197A235912DD023 /* [CP] Embed Pods Frameworks */ = {
344 | isa = PBXShellScriptBuildPhase;
345 | buildActionMask = 2147483647;
346 | files = (
347 | );
348 | inputPaths = (
349 | "${PODS_ROOT}/Target Support Files/Pods-Patch/Pods-Patch-frameworks.sh",
350 | "${BUILT_PRODUCTS_DIR}/BlueSocket/Socket.framework",
351 | "${BUILT_PRODUCTS_DIR}/ReactiveSwift/ReactiveSwift.framework",
352 | "${BUILT_PRODUCTS_DIR}/Swime/Swime.framework",
353 | );
354 | name = "[CP] Embed Pods Frameworks";
355 | outputPaths = (
356 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Socket.framework",
357 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveSwift.framework",
358 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Swime.framework",
359 | );
360 | runOnlyForDeploymentPostprocessing = 0;
361 | shellPath = /bin/sh;
362 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Patch/Pods-Patch-frameworks.sh\"\n";
363 | showEnvVarsInLog = 0;
364 | };
365 | B5219CD20518C3C5C738C60E /* [CP] Check Pods Manifest.lock */ = {
366 | isa = PBXShellScriptBuildPhase;
367 | buildActionMask = 2147483647;
368 | files = (
369 | );
370 | inputPaths = (
371 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
372 | "${PODS_ROOT}/Manifest.lock",
373 | );
374 | name = "[CP] Check Pods Manifest.lock";
375 | outputPaths = (
376 | "$(DERIVED_FILE_DIR)/Pods-Patch-checkManifestLockResult.txt",
377 | );
378 | runOnlyForDeploymentPostprocessing = 0;
379 | shellPath = /bin/sh;
380 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
381 | showEnvVarsInLog = 0;
382 | };
383 | D6416A975B6FAFE16C94973C /* [CP] Check Pods Manifest.lock */ = {
384 | isa = PBXShellScriptBuildPhase;
385 | buildActionMask = 2147483647;
386 | files = (
387 | );
388 | inputPaths = (
389 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
390 | "${PODS_ROOT}/Manifest.lock",
391 | );
392 | name = "[CP] Check Pods Manifest.lock";
393 | outputPaths = (
394 | "$(DERIVED_FILE_DIR)/Pods-PatchTests-checkManifestLockResult.txt",
395 | );
396 | runOnlyForDeploymentPostprocessing = 0;
397 | shellPath = /bin/sh;
398 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
399 | showEnvVarsInLog = 0;
400 | };
401 | F6A01FF22DBDCB6BCEA01F11 /* [CP] Check Pods Manifest.lock */ = {
402 | isa = PBXShellScriptBuildPhase;
403 | buildActionMask = 2147483647;
404 | files = (
405 | );
406 | inputPaths = (
407 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
408 | "${PODS_ROOT}/Manifest.lock",
409 | );
410 | name = "[CP] Check Pods Manifest.lock";
411 | outputPaths = (
412 | "$(DERIVED_FILE_DIR)/Pods-PatchUITests-checkManifestLockResult.txt",
413 | );
414 | runOnlyForDeploymentPostprocessing = 0;
415 | shellPath = /bin/sh;
416 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
417 | showEnvVarsInLog = 0;
418 | };
419 | /* End PBXShellScriptBuildPhase section */
420 |
421 | /* Begin PBXSourcesBuildPhase section */
422 | A093BE6A1E7A15B400884283 /* Sources */ = {
423 | isa = PBXSourcesBuildPhase;
424 | buildActionMask = 2147483647;
425 | files = (
426 | A02176AD1E99B6EB00180ED6 /* GopherResponsePart.swift in Sources */,
427 | A0C7B7FF1E91AE4E0010BD86 /* Gopher.swift in Sources */,
428 | A02176AB1E99B6DA00180ED6 /* GopherResponse.swift in Sources */,
429 | A02176A71E99B68900180ED6 /* GopherPage.swift in Sources */,
430 | A02176A91E99B6AB00180ED6 /* GopherRequest.swift in Sources */,
431 | A0DAE5142439687200DA2CE7 /* MainWindowController.swift in Sources */,
432 | A012979729176275008314BE /* PreferencesWindowController.swift in Sources */,
433 | A093BE721E7A15B400884283 /* AppDelegate.swift in Sources */,
434 | );
435 | runOnlyForDeploymentPostprocessing = 0;
436 | };
437 | A093BE791E7A15B400884283 /* Sources */ = {
438 | isa = PBXSourcesBuildPhase;
439 | buildActionMask = 2147483647;
440 | files = (
441 | A093BE821E7A15B400884283 /* PatchTests.swift in Sources */,
442 | );
443 | runOnlyForDeploymentPostprocessing = 0;
444 | };
445 | A093BE841E7A15B400884283 /* Sources */ = {
446 | isa = PBXSourcesBuildPhase;
447 | buildActionMask = 2147483647;
448 | files = (
449 | A093BE8D1E7A15B400884283 /* PatchUITests.swift in Sources */,
450 | );
451 | runOnlyForDeploymentPostprocessing = 0;
452 | };
453 | /* End PBXSourcesBuildPhase section */
454 |
455 | /* Begin PBXTargetDependency section */
456 | A093BE7F1E7A15B400884283 /* PBXTargetDependency */ = {
457 | isa = PBXTargetDependency;
458 | target = A093BE6D1E7A15B400884283 /* Patch */;
459 | targetProxy = A093BE7E1E7A15B400884283 /* PBXContainerItemProxy */;
460 | };
461 | A093BE8A1E7A15B400884283 /* PBXTargetDependency */ = {
462 | isa = PBXTargetDependency;
463 | target = A093BE6D1E7A15B400884283 /* Patch */;
464 | targetProxy = A093BE891E7A15B400884283 /* PBXContainerItemProxy */;
465 | };
466 | /* End PBXTargetDependency section */
467 |
468 | /* Begin PBXVariantGroup section */
469 | A093BE751E7A15B400884283 /* MainMenu.xib */ = {
470 | isa = PBXVariantGroup;
471 | children = (
472 | A093BE761E7A15B400884283 /* Base */,
473 | );
474 | name = MainMenu.xib;
475 | sourceTree = "";
476 | };
477 | /* End PBXVariantGroup section */
478 |
479 | /* Begin XCBuildConfiguration section */
480 | A093BE8F1E7A15B400884283 /* Debug */ = {
481 | isa = XCBuildConfiguration;
482 | buildSettings = {
483 | ALWAYS_SEARCH_USER_PATHS = NO;
484 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
485 | CLANG_ANALYZER_NONNULL = YES;
486 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
487 | CLANG_CXX_LIBRARY = "libc++";
488 | CLANG_ENABLE_MODULES = YES;
489 | CLANG_ENABLE_OBJC_ARC = YES;
490 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
491 | CLANG_WARN_BOOL_CONVERSION = YES;
492 | CLANG_WARN_COMMA = YES;
493 | CLANG_WARN_CONSTANT_CONVERSION = YES;
494 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
495 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
496 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
497 | CLANG_WARN_EMPTY_BODY = YES;
498 | CLANG_WARN_ENUM_CONVERSION = YES;
499 | CLANG_WARN_INFINITE_RECURSION = YES;
500 | CLANG_WARN_INT_CONVERSION = YES;
501 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
502 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
503 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
504 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
505 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
506 | CLANG_WARN_STRICT_PROTOTYPES = YES;
507 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
508 | CLANG_WARN_UNREACHABLE_CODE = YES;
509 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
510 | CODE_SIGN_IDENTITY = "-";
511 | COPY_PHASE_STRIP = NO;
512 | DEBUG_INFORMATION_FORMAT = dwarf;
513 | ENABLE_STRICT_OBJC_MSGSEND = YES;
514 | ENABLE_TESTABILITY = YES;
515 | GCC_C_LANGUAGE_STANDARD = gnu99;
516 | GCC_DYNAMIC_NO_PIC = NO;
517 | GCC_NO_COMMON_BLOCKS = YES;
518 | GCC_OPTIMIZATION_LEVEL = 0;
519 | GCC_PREPROCESSOR_DEFINITIONS = (
520 | "DEBUG=1",
521 | "$(inherited)",
522 | );
523 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
524 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
525 | GCC_WARN_UNDECLARED_SELECTOR = YES;
526 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
527 | GCC_WARN_UNUSED_FUNCTION = YES;
528 | GCC_WARN_UNUSED_VARIABLE = YES;
529 | MACOSX_DEPLOYMENT_TARGET = 10.12;
530 | MTL_ENABLE_DEBUG_INFO = YES;
531 | ONLY_ACTIVE_ARCH = YES;
532 | SDKROOT = macosx;
533 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
534 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
535 | };
536 | name = Debug;
537 | };
538 | A093BE901E7A15B400884283 /* Release */ = {
539 | isa = XCBuildConfiguration;
540 | buildSettings = {
541 | ALWAYS_SEARCH_USER_PATHS = NO;
542 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
543 | CLANG_ANALYZER_NONNULL = YES;
544 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
545 | CLANG_CXX_LIBRARY = "libc++";
546 | CLANG_ENABLE_MODULES = YES;
547 | CLANG_ENABLE_OBJC_ARC = YES;
548 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
549 | CLANG_WARN_BOOL_CONVERSION = YES;
550 | CLANG_WARN_COMMA = YES;
551 | CLANG_WARN_CONSTANT_CONVERSION = YES;
552 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
553 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
554 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
555 | CLANG_WARN_EMPTY_BODY = YES;
556 | CLANG_WARN_ENUM_CONVERSION = YES;
557 | CLANG_WARN_INFINITE_RECURSION = YES;
558 | CLANG_WARN_INT_CONVERSION = YES;
559 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
560 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
561 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
562 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
563 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
564 | CLANG_WARN_STRICT_PROTOTYPES = YES;
565 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
566 | CLANG_WARN_UNREACHABLE_CODE = YES;
567 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
568 | CODE_SIGN_IDENTITY = "-";
569 | COPY_PHASE_STRIP = NO;
570 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
571 | ENABLE_NS_ASSERTIONS = NO;
572 | ENABLE_STRICT_OBJC_MSGSEND = YES;
573 | GCC_C_LANGUAGE_STANDARD = gnu99;
574 | GCC_NO_COMMON_BLOCKS = YES;
575 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
576 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
577 | GCC_WARN_UNDECLARED_SELECTOR = YES;
578 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
579 | GCC_WARN_UNUSED_FUNCTION = YES;
580 | GCC_WARN_UNUSED_VARIABLE = YES;
581 | MACOSX_DEPLOYMENT_TARGET = 10.12;
582 | MTL_ENABLE_DEBUG_INFO = NO;
583 | SDKROOT = macosx;
584 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
585 | };
586 | name = Release;
587 | };
588 | A093BE921E7A15B400884283 /* Debug */ = {
589 | isa = XCBuildConfiguration;
590 | baseConfigurationReference = CDF9F3265897C3E3A4F041DB /* Pods-Patch.debug.xcconfig */;
591 | buildSettings = {
592 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
593 | CODE_SIGN_IDENTITY = "-";
594 | COMBINE_HIDPI_IMAGES = YES;
595 | INFOPLIST_FILE = Patch/Info.plist;
596 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
597 | PRODUCT_BUNDLE_IDENTIFIER = com.jacobbudin.Patch;
598 | PRODUCT_NAME = "$(TARGET_NAME)";
599 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
600 | SWIFT_VERSION = 4.2;
601 | };
602 | name = Debug;
603 | };
604 | A093BE931E7A15B400884283 /* Release */ = {
605 | isa = XCBuildConfiguration;
606 | baseConfigurationReference = BE98003AB72354086962BBD1 /* Pods-Patch.release.xcconfig */;
607 | buildSettings = {
608 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
609 | CODE_SIGN_IDENTITY = "-";
610 | COMBINE_HIDPI_IMAGES = YES;
611 | INFOPLIST_FILE = Patch/Info.plist;
612 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
613 | PRODUCT_BUNDLE_IDENTIFIER = com.jacobbudin.Patch;
614 | PRODUCT_NAME = "$(TARGET_NAME)";
615 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
616 | SWIFT_VERSION = 4.2;
617 | };
618 | name = Release;
619 | };
620 | A093BE951E7A15B400884283 /* Debug */ = {
621 | isa = XCBuildConfiguration;
622 | baseConfigurationReference = 0779031732B8B817151D8258 /* Pods-PatchTests.debug.xcconfig */;
623 | buildSettings = {
624 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
625 | BUNDLE_LOADER = "$(TEST_HOST)";
626 | COMBINE_HIDPI_IMAGES = YES;
627 | INFOPLIST_FILE = PatchTests/Info.plist;
628 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
629 | PRODUCT_BUNDLE_IDENTIFIER = com.jacobbudin.PatchTests;
630 | PRODUCT_NAME = "$(TARGET_NAME)";
631 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
632 | SWIFT_VERSION = 4.2;
633 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Patch.app/Contents/MacOS/Patch";
634 | };
635 | name = Debug;
636 | };
637 | A093BE961E7A15B400884283 /* Release */ = {
638 | isa = XCBuildConfiguration;
639 | baseConfigurationReference = 46528105DF5300D80CF24631 /* Pods-PatchTests.release.xcconfig */;
640 | buildSettings = {
641 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
642 | BUNDLE_LOADER = "$(TEST_HOST)";
643 | COMBINE_HIDPI_IMAGES = YES;
644 | INFOPLIST_FILE = PatchTests/Info.plist;
645 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
646 | PRODUCT_BUNDLE_IDENTIFIER = com.jacobbudin.PatchTests;
647 | PRODUCT_NAME = "$(TARGET_NAME)";
648 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
649 | SWIFT_VERSION = 4.2;
650 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Patch.app/Contents/MacOS/Patch";
651 | };
652 | name = Release;
653 | };
654 | A093BE981E7A15B400884283 /* Debug */ = {
655 | isa = XCBuildConfiguration;
656 | baseConfigurationReference = 6591A9162B512F9C3D688CEA /* Pods-PatchUITests.debug.xcconfig */;
657 | buildSettings = {
658 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
659 | COMBINE_HIDPI_IMAGES = YES;
660 | INFOPLIST_FILE = PatchUITests/Info.plist;
661 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
662 | PRODUCT_BUNDLE_IDENTIFIER = com.jacobbudin.PatchUITests;
663 | PRODUCT_NAME = "$(TARGET_NAME)";
664 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
665 | SWIFT_VERSION = 4.2;
666 | TEST_TARGET_NAME = Patch;
667 | };
668 | name = Debug;
669 | };
670 | A093BE991E7A15B400884283 /* Release */ = {
671 | isa = XCBuildConfiguration;
672 | baseConfigurationReference = F189FBBD0923581C6C334CC5 /* Pods-PatchUITests.release.xcconfig */;
673 | buildSettings = {
674 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
675 | COMBINE_HIDPI_IMAGES = YES;
676 | INFOPLIST_FILE = PatchUITests/Info.plist;
677 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
678 | PRODUCT_BUNDLE_IDENTIFIER = com.jacobbudin.PatchUITests;
679 | PRODUCT_NAME = "$(TARGET_NAME)";
680 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
681 | SWIFT_VERSION = 4.2;
682 | TEST_TARGET_NAME = Patch;
683 | };
684 | name = Release;
685 | };
686 | /* End XCBuildConfiguration section */
687 |
688 | /* Begin XCConfigurationList section */
689 | A093BE691E7A15B400884283 /* Build configuration list for PBXProject "Patch" */ = {
690 | isa = XCConfigurationList;
691 | buildConfigurations = (
692 | A093BE8F1E7A15B400884283 /* Debug */,
693 | A093BE901E7A15B400884283 /* Release */,
694 | );
695 | defaultConfigurationIsVisible = 0;
696 | defaultConfigurationName = Release;
697 | };
698 | A093BE911E7A15B400884283 /* Build configuration list for PBXNativeTarget "Patch" */ = {
699 | isa = XCConfigurationList;
700 | buildConfigurations = (
701 | A093BE921E7A15B400884283 /* Debug */,
702 | A093BE931E7A15B400884283 /* Release */,
703 | );
704 | defaultConfigurationIsVisible = 0;
705 | defaultConfigurationName = Release;
706 | };
707 | A093BE941E7A15B400884283 /* Build configuration list for PBXNativeTarget "PatchTests" */ = {
708 | isa = XCConfigurationList;
709 | buildConfigurations = (
710 | A093BE951E7A15B400884283 /* Debug */,
711 | A093BE961E7A15B400884283 /* Release */,
712 | );
713 | defaultConfigurationIsVisible = 0;
714 | defaultConfigurationName = Release;
715 | };
716 | A093BE971E7A15B400884283 /* Build configuration list for PBXNativeTarget "PatchUITests" */ = {
717 | isa = XCConfigurationList;
718 | buildConfigurations = (
719 | A093BE981E7A15B400884283 /* Debug */,
720 | A093BE991E7A15B400884283 /* Release */,
721 | );
722 | defaultConfigurationIsVisible = 0;
723 | defaultConfigurationName = Release;
724 | };
725 | /* End XCConfigurationList section */
726 | };
727 | rootObject = A093BE661E7A15B400884283 /* Project object */;
728 | }
729 |
--------------------------------------------------------------------------------