├── .gitignore ├── Patch.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Patch.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Patch ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── Gopher.swift ├── GopherPage.swift ├── GopherPageStyle-Dark.css ├── GopherPageStyle-Light.css ├── GopherRequest.swift ├── GopherResponse.swift ├── GopherResponsePart.swift ├── Info.plist ├── MainWindow.xib ├── MainWindowController.swift ├── PreferencesWindow.xib └── PreferencesWindowController.swift ├── PatchTests ├── Info.plist └── PatchTests.swift ├── PatchUITests ├── Info.plist └── PatchUITests.swift ├── Podfile ├── Podfile.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Pods 2 | xcuserdata 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Patch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/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/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/Base.lproj/MainMenu.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 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 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 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------