├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── Gemfile ├── Gemfile.lock ├── Graphics ├── IconZusammen.png ├── IconZusammen.psd └── app_preview.png ├── Podfile ├── README.md ├── Zusammen.xcodeproj └── project.pbxproj ├── Zusammen ├── Core │ ├── Constants.swift │ ├── FileHelper.swift │ ├── Installer.swift │ ├── SourceExtension.swift │ ├── SourceExtensionList.swift │ └── StringExtensions.swift ├── GithubHeaderRemovalScript.js ├── Network │ └── SourceExtensionListLoader.swift ├── Support │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon_128x128.png │ │ │ ├── Icon_128x128@2x.png │ │ │ ├── Icon_16x16.png │ │ │ ├── Icon_16x16@2x.png │ │ │ ├── Icon_256x256.png │ │ │ ├── Icon_256x256@2x.png │ │ │ ├── Icon_32x32.png │ │ │ ├── Icon_32x32@2x.png │ │ │ ├── Icon_512x512.png │ │ │ └── Icon_512x512@2x.png │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ └── Zusammen.entitlements ├── UI │ ├── BadgeLabel.swift │ ├── ExtensionsListViewController.swift │ ├── SourceExtensionCell.swift │ └── SourceExtensionContentView.swift ├── placeholder.html └── source.json ├── ZusammenTests ├── Info.plist └── ZusammenTests.swift └── fastlane ├── Fastfile ├── Pluginfile ├── README.md └── report.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | 19 | ## Other 20 | *.xccheckout 21 | *.moved-aside 22 | *.xcuserstate 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | # CocoaPods 30 | Pods/ 31 | Podfile.lock 32 | 33 | # Carthage 34 | # 35 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 36 | # Carthage/Checkouts 37 | 38 | Carthage/Build 39 | 40 | # Clang and OCLint 41 | compile_commands.json 42 | xcodebuild.log 43 | 44 | 45 | #appcode 46 | .idea 47 | 48 | #Code Coverage in Xcode7 49 | *.gcda 50 | *.gcno -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - line_length 4 | - compiler_protocol_init 5 | - force_try 6 | opt_in_rules: 7 | - empty_count 8 | - empty_string 9 | excluded: 10 | - Pods 11 | function_body_length: 12 | warning: 300 13 | error: 500 14 | function_parameter_count: 15 | warning: 6 16 | error: 8 17 | type_body_length: 18 | warning: 300 19 | error: 500 20 | file_length: 21 | warning: 1000 22 | error: 1500 23 | ignore_comment_only_lines: true 24 | cyclomatic_complexity: 25 | warning: 15 26 | error: 25 27 | reporter: "xcode" 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem 'cocoapods' 5 | gem 'synx' 6 | gem 'xcov' 7 | gem 'jazzy' 8 | 9 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 10 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | addressable (2.6.0) 11 | public_suffix (>= 2.0.2, < 4.0) 12 | atomos (0.1.3) 13 | babosa (1.0.2) 14 | claide (1.0.3) 15 | clamp (0.6.5) 16 | cocoapods (1.7.5) 17 | activesupport (>= 4.0.2, < 5) 18 | claide (>= 1.0.2, < 2.0) 19 | cocoapods-core (= 1.7.5) 20 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 21 | cocoapods-downloader (>= 1.2.2, < 2.0) 22 | cocoapods-plugins (>= 1.0.0, < 2.0) 23 | cocoapods-search (>= 1.0.0, < 2.0) 24 | cocoapods-stats (>= 1.0.0, < 2.0) 25 | cocoapods-trunk (>= 1.3.1, < 2.0) 26 | cocoapods-try (>= 1.1.0, < 2.0) 27 | colored2 (~> 3.1) 28 | escape (~> 0.0.4) 29 | fourflusher (>= 2.3.0, < 3.0) 30 | gh_inspector (~> 1.0) 31 | molinillo (~> 0.6.6) 32 | nap (~> 1.0) 33 | ruby-macho (~> 1.4) 34 | xcodeproj (>= 1.10.0, < 2.0) 35 | cocoapods-core (1.7.5) 36 | activesupport (>= 4.0.2, < 6) 37 | fuzzy_match (~> 2.0.4) 38 | nap (~> 1.0) 39 | cocoapods-deintegrate (1.0.4) 40 | cocoapods-downloader (1.2.2) 41 | cocoapods-plugins (1.0.0) 42 | nap 43 | cocoapods-search (1.0.0) 44 | cocoapods-stats (1.1.0) 45 | cocoapods-trunk (1.3.1) 46 | nap (>= 0.8, < 2.0) 47 | netrc (~> 0.11) 48 | cocoapods-try (1.1.0) 49 | colored (1.2) 50 | colored2 (3.1.2) 51 | colorize (0.8.1) 52 | commander-fastlane (4.4.6) 53 | highline (~> 1.7.2) 54 | concurrent-ruby (1.1.5) 55 | declarative (0.0.10) 56 | declarative-option (0.1.0) 57 | digest-crc (0.4.1) 58 | domain_name (0.5.20190701) 59 | unf (>= 0.0.5, < 1.0.0) 60 | dotenv (2.7.5) 61 | emoji_regex (1.0.1) 62 | escape (0.0.4) 63 | excon (0.66.0) 64 | faraday (0.15.4) 65 | multipart-post (>= 1.2, < 3) 66 | faraday-cookie_jar (0.0.6) 67 | faraday (>= 0.7.4) 68 | http-cookie (~> 1.0.0) 69 | faraday_middleware (0.13.1) 70 | faraday (>= 0.7.4, < 1.0) 71 | fastimage (2.1.5) 72 | fastlane (2.128.1) 73 | CFPropertyList (>= 2.3, < 4.0.0) 74 | addressable (>= 2.3, < 3.0.0) 75 | babosa (>= 1.0.2, < 2.0.0) 76 | bundler (>= 1.12.0, < 3.0.0) 77 | colored 78 | commander-fastlane (>= 4.4.6, < 5.0.0) 79 | dotenv (>= 2.1.1, < 3.0.0) 80 | emoji_regex (>= 0.1, < 2.0) 81 | excon (>= 0.45.0, < 1.0.0) 82 | faraday (~> 0.9) 83 | faraday-cookie_jar (~> 0.0.6) 84 | faraday_middleware (~> 0.9) 85 | fastimage (>= 2.1.0, < 3.0.0) 86 | gh_inspector (>= 1.1.2, < 2.0.0) 87 | google-api-client (>= 0.21.2, < 0.24.0) 88 | google-cloud-storage (>= 1.15.0, < 2.0.0) 89 | highline (>= 1.7.2, < 2.0.0) 90 | json (< 3.0.0) 91 | jwt (~> 2.1.0) 92 | mini_magick (>= 4.9.4, < 5.0.0) 93 | multi_xml (~> 0.5) 94 | multipart-post (~> 2.0.0) 95 | plist (>= 3.1.0, < 4.0.0) 96 | public_suffix (~> 2.0.0) 97 | rubyzip (>= 1.2.2, < 2.0.0) 98 | security (= 0.1.3) 99 | simctl (~> 1.6.3) 100 | slack-notifier (>= 2.0.0, < 3.0.0) 101 | terminal-notifier (>= 2.0.0, < 3.0.0) 102 | terminal-table (>= 1.4.5, < 2.0.0) 103 | tty-screen (>= 0.6.3, < 1.0.0) 104 | tty-spinner (>= 0.8.0, < 1.0.0) 105 | word_wrap (~> 1.0.0) 106 | xcodeproj (>= 1.8.1, < 2.0.0) 107 | xcpretty (~> 0.3.0) 108 | xcpretty-travis-formatter (>= 0.0.3) 109 | fastlane-plugin-brew (0.1.1) 110 | fourflusher (2.3.1) 111 | fuzzy_match (2.0.4) 112 | gh_inspector (1.1.3) 113 | google-api-client (0.23.9) 114 | addressable (~> 2.5, >= 2.5.1) 115 | googleauth (>= 0.5, < 0.7.0) 116 | httpclient (>= 2.8.1, < 3.0) 117 | mime-types (~> 3.0) 118 | representable (~> 3.0) 119 | retriable (>= 2.0, < 4.0) 120 | signet (~> 0.9) 121 | google-cloud-core (1.3.0) 122 | google-cloud-env (~> 1.0) 123 | google-cloud-env (1.2.0) 124 | faraday (~> 0.11) 125 | google-cloud-storage (1.16.0) 126 | digest-crc (~> 0.4) 127 | google-api-client (~> 0.23) 128 | google-cloud-core (~> 1.2) 129 | googleauth (>= 0.6.2, < 0.10.0) 130 | googleauth (0.6.7) 131 | faraday (~> 0.12) 132 | jwt (>= 1.4, < 3.0) 133 | memoist (~> 0.16) 134 | multi_json (~> 1.11) 135 | os (>= 0.9, < 2.0) 136 | signet (~> 0.7) 137 | highline (1.7.10) 138 | http-cookie (1.0.3) 139 | domain_name (~> 0.5) 140 | httpclient (2.8.3) 141 | i18n (0.9.5) 142 | concurrent-ruby (~> 1.0) 143 | json (2.2.0) 144 | jwt (2.1.0) 145 | memoist (0.16.0) 146 | mime-types (3.2.2) 147 | mime-types-data (~> 3.2015) 148 | mime-types-data (3.2019.0331) 149 | mini_magick (4.9.5) 150 | minitest (5.11.3) 151 | molinillo (0.6.6) 152 | multi_json (1.13.1) 153 | multi_xml (0.6.0) 154 | multipart-post (2.0.0) 155 | nanaimo (0.2.6) 156 | nap (1.1.0) 157 | naturally (2.2.0) 158 | netrc (0.11.0) 159 | os (1.0.1) 160 | plist (3.5.0) 161 | public_suffix (2.0.5) 162 | representable (3.0.4) 163 | declarative (< 0.1.0) 164 | declarative-option (< 0.2.0) 165 | uber (< 0.2.0) 166 | retriable (3.1.2) 167 | rouge (2.0.7) 168 | ruby-macho (1.4.0) 169 | rubyzip (1.2.3) 170 | security (0.1.3) 171 | signet (0.11.0) 172 | addressable (~> 2.3) 173 | faraday (~> 0.9) 174 | jwt (>= 1.5, < 3.0) 175 | multi_json (~> 1.10) 176 | simctl (1.6.5) 177 | CFPropertyList 178 | naturally 179 | slack-notifier (2.3.2) 180 | synx (0.2.1) 181 | clamp (~> 0.6) 182 | colorize (~> 0.7) 183 | xcodeproj (~> 1.0) 184 | terminal-notifier (2.0.0) 185 | terminal-table (1.8.0) 186 | unicode-display_width (~> 1.1, >= 1.1.1) 187 | thread_safe (0.3.6) 188 | tty-cursor (0.7.0) 189 | tty-screen (0.7.0) 190 | tty-spinner (0.9.1) 191 | tty-cursor (~> 0.7) 192 | tzinfo (1.2.5) 193 | thread_safe (~> 0.1) 194 | uber (0.1.0) 195 | unf (0.1.4) 196 | unf_ext 197 | unf_ext (0.0.7.6) 198 | unicode-display_width (1.6.0) 199 | word_wrap (1.0.0) 200 | xcodeproj (1.12.0) 201 | CFPropertyList (>= 2.3.3, < 4.0) 202 | atomos (~> 0.1.3) 203 | claide (>= 1.0.2, < 2.0) 204 | colored2 (~> 3.1) 205 | nanaimo (~> 0.2.6) 206 | xcov (1.5.1) 207 | fastlane (>= 2.82.0, < 3.0.0) 208 | multipart-post 209 | slack-notifier 210 | terminal-table 211 | xcodeproj 212 | xcpretty (0.3.0) 213 | rouge (~> 2.0.7) 214 | xcpretty-travis-formatter (1.0.0) 215 | xcpretty (~> 0.2, >= 0.0.7) 216 | 217 | PLATFORMS 218 | ruby 219 | 220 | DEPENDENCIES 221 | cocoapods 222 | fastlane 223 | fastlane-plugin-brew 224 | synx 225 | xcov 226 | 227 | BUNDLED WITH 228 | 2.0.2 229 | -------------------------------------------------------------------------------- /Graphics/IconZusammen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Graphics/IconZusammen.png -------------------------------------------------------------------------------- /Graphics/IconZusammen.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Graphics/IconZusammen.psd -------------------------------------------------------------------------------- /Graphics/app_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Graphics/app_preview.png -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | use_frameworks! 3 | 4 | target 'Zusammen' do 5 | pod 'Alamofire', '~> 5.0.0-beta.5' 6 | end 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://github.com/insanoid/Zusammen/blob/master/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png?raw=true) 2 | ### Zusammen - All Xcode Extensions Together in One Place. 3 | 4 | - Xcode extensions are to be installed manually and Alcatraz does not work anymore. 5 | - Some extensions are now available on App Store and some need to be build manually. 6 | - Discovering of extensions is hard, one has to search for exact problems to find solutions, sometimes one might not know what they are looking for. 7 | 8 | ![App Preview](https://github.com/insanoid/Zusammen/blob/master/Graphics/app_preview.png) 9 | 10 | ### Goals 11 | 12 | - [ ] Collect all the extensions out there and store their information. 13 | - [ ] Make it easy to upload new extensions into the list (Separate it into a different repository). 14 | - [ ] Make installation process easy - App store download, downloading source and building it, download from releases and other sources. 15 | - [ ] Handle links from the web-view to open in a browser. 16 | - [ ] CI integration and release 1.0.0. 17 | - [ ] Try to install only if Xcode/Swift version matches the code version. 18 | - [ ] Provide a way to see if an extension is already installed. 19 | 20 | ### Contributions and Requests 21 | 22 | Any suggestions regarding code quality of the app, generated code's quality, Swift related improvements and pull requests are all very welcome. Please make sure you submit the pull request to the dev branch and not the master branch. 23 | 24 | ### License 25 | 26 | Copyright (c) 2019 [Karthikeya Udupa](https://karthikeya.co.uk) 27 | -------------------------------------------------------------------------------- /Zusammen.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 930DC6FE23166C750087FABA /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930DC6FD23166C750087FABA /* Constants.swift */; }; 11 | 930DC70023166D8E0087FABA /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930DC6FF23166D8E0087FABA /* FileHelper.swift */; }; 12 | 932F16DF231530DA000C3B28 /* BadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932F16DE231530DA000C3B28 /* BadgeLabel.swift */; }; 13 | 9348E6BD22FDD8ED0090F0DE /* SourceExtensionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9348E6BC22FDD8ED0090F0DE /* SourceExtensionCell.swift */; }; 14 | 934F65D82302FB04006EA517 /* placeholder.html in Resources */ = {isa = PBXBuildFile; fileRef = 934F65D72302FB04006EA517 /* placeholder.html */; }; 15 | 934F65D92302FB04006EA517 /* placeholder.html in Resources */ = {isa = PBXBuildFile; fileRef = 934F65D72302FB04006EA517 /* placeholder.html */; }; 16 | 9381365422FC2AB300BC6C0F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9381365322FC2AB300BC6C0F /* AppDelegate.swift */; }; 17 | 9381365622FC2AB300BC6C0F /* ExtensionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9381365522FC2AB300BC6C0F /* ExtensionsListViewController.swift */; }; 18 | 9381365822FC2AB600BC6C0F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9381365722FC2AB600BC6C0F /* Assets.xcassets */; }; 19 | 9381365B22FC2AB600BC6C0F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9381365922FC2AB600BC6C0F /* Main.storyboard */; }; 20 | 9381366722FC2AB700BC6C0F /* ZusammenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9381366622FC2AB700BC6C0F /* ZusammenTests.swift */; }; 21 | 9381367222FC2B1F00BC6C0F /* source.json in Resources */ = {isa = PBXBuildFile; fileRef = 9381367122FC2B1F00BC6C0F /* source.json */; }; 22 | 9381367622FC4EBA00BC6C0F /* SourceExtensionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9381367422FC4EBA00BC6C0F /* SourceExtensionList.swift */; }; 23 | 9381367722FC4EBA00BC6C0F /* SourceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9381367522FC4EBA00BC6C0F /* SourceExtension.swift */; }; 24 | 9381367A22FC735D00BC6C0F /* SourceExtensionListLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9381367922FC735D00BC6C0F /* SourceExtensionListLoader.swift */; }; 25 | 938315172315C576001A1533 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938315162315C576001A1533 /* StringExtensions.swift */; }; 26 | 93B6619E231919A1002E697B /* GithubHeaderRemovalScript.js in Resources */ = {isa = PBXBuildFile; fileRef = 93B6619D231919A1002E697B /* GithubHeaderRemovalScript.js */; }; 27 | 93B661A0231921AB002E697B /* Installer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B6619F231921AB002E697B /* Installer.swift */; }; 28 | 93BF48B723022E25008BF88C /* SourceExtensionContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF48B623022E25008BF88C /* SourceExtensionContentView.swift */; }; 29 | F6400DB7E47D8E909C1BBDCF /* Pods_Zusammen.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EF2EF2BC0E69499AC965732 /* Pods_Zusammen.framework */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | 9381366322FC2AB700BC6C0F /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 9381364822FC2AB300BC6C0F /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 9381364F22FC2AB300BC6C0F; 38 | remoteInfo = Zusammen; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 29581008347966C9FC10BB74 /* Pods-Zusammen.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Zusammen.release.xcconfig"; path = "Target Support Files/Pods-Zusammen/Pods-Zusammen.release.xcconfig"; sourceTree = ""; }; 44 | 930DC6FD23166C750087FABA /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 45 | 930DC6FF23166D8E0087FABA /* FileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = ""; }; 46 | 932F16DE231530DA000C3B28 /* BadgeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeLabel.swift; sourceTree = ""; }; 47 | 9348E6BC22FDD8ED0090F0DE /* SourceExtensionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceExtensionCell.swift; sourceTree = ""; }; 48 | 934F65D72302FB04006EA517 /* placeholder.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = placeholder.html; sourceTree = ""; }; 49 | 9381365022FC2AB300BC6C0F /* Zusammen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Zusammen.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 9381365322FC2AB300BC6C0F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 51 | 9381365522FC2AB300BC6C0F /* ExtensionsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsListViewController.swift; sourceTree = ""; }; 52 | 9381365722FC2AB600BC6C0F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | 9381365A22FC2AB600BC6C0F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | 9381365C22FC2AB600BC6C0F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 9381365D22FC2AB600BC6C0F /* Zusammen.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Zusammen.entitlements; sourceTree = ""; }; 56 | 9381366222FC2AB700BC6C0F /* ZusammenTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ZusammenTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 9381366622FC2AB700BC6C0F /* ZusammenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZusammenTests.swift; sourceTree = ""; }; 58 | 9381366822FC2AB700BC6C0F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 9381367122FC2B1F00BC6C0F /* source.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = source.json; sourceTree = ""; }; 60 | 9381367422FC4EBA00BC6C0F /* SourceExtensionList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceExtensionList.swift; sourceTree = ""; }; 61 | 9381367522FC4EBA00BC6C0F /* SourceExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceExtension.swift; sourceTree = ""; }; 62 | 9381367922FC735D00BC6C0F /* SourceExtensionListLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceExtensionListLoader.swift; sourceTree = ""; }; 63 | 938315162315C576001A1533 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 64 | 93B6619D231919A1002E697B /* GithubHeaderRemovalScript.js */ = {isa = PBXFileReference; explicitFileType = sourcecode.javascript; path = GithubHeaderRemovalScript.js; sourceTree = ""; }; 65 | 93B6619F231921AB002E697B /* Installer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Installer.swift; sourceTree = ""; }; 66 | 93BF48B623022E25008BF88C /* SourceExtensionContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceExtensionContentView.swift; sourceTree = ""; }; 67 | 9EF2EF2BC0E69499AC965732 /* Pods_Zusammen.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Zusammen.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | C6CE620CC31215EDBDB30DF9 /* Pods-Zusammen.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Zusammen.debug.xcconfig"; path = "Target Support Files/Pods-Zusammen/Pods-Zusammen.debug.xcconfig"; sourceTree = ""; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | 9381364D22FC2AB300BC6C0F /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | F6400DB7E47D8E909C1BBDCF /* Pods_Zusammen.framework in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | 9381365F22FC2AB600BC6C0F /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 934F65D52302FA8B006EA517 /* UI */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9348E6BC22FDD8ED0090F0DE /* SourceExtensionCell.swift */, 94 | 93BF48B623022E25008BF88C /* SourceExtensionContentView.swift */, 95 | 9381365522FC2AB300BC6C0F /* ExtensionsListViewController.swift */, 96 | 932F16DE231530DA000C3B28 /* BadgeLabel.swift */, 97 | ); 98 | path = UI; 99 | sourceTree = ""; 100 | }; 101 | 934F65D62302FAA0006EA517 /* Support */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 9381365322FC2AB300BC6C0F /* AppDelegate.swift */, 105 | 9381365722FC2AB600BC6C0F /* Assets.xcassets */, 106 | 9381365C22FC2AB600BC6C0F /* Info.plist */, 107 | 9381365922FC2AB600BC6C0F /* Main.storyboard */, 108 | 9381365D22FC2AB600BC6C0F /* Zusammen.entitlements */, 109 | ); 110 | path = Support; 111 | sourceTree = ""; 112 | }; 113 | 9381364722FC2AB300BC6C0F = { 114 | isa = PBXGroup; 115 | children = ( 116 | F2A1CFEECE4FDF2825241E94 /* Frameworks */, 117 | BD5616B74B7CE6048B7052BC /* Pods */, 118 | 9381365122FC2AB300BC6C0F /* Products */, 119 | 9381365222FC2AB300BC6C0F /* Zusammen */, 120 | 9381366522FC2AB700BC6C0F /* ZusammenTests */, 121 | ); 122 | sourceTree = ""; 123 | }; 124 | 9381365122FC2AB300BC6C0F /* Products */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 9381365022FC2AB300BC6C0F /* Zusammen.app */, 128 | 9381366222FC2AB700BC6C0F /* ZusammenTests.xctest */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | 9381365222FC2AB300BC6C0F /* Zusammen */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 9381367322FC4D4C00BC6C0F /* Core */, 137 | 9381367822FC61F400BC6C0F /* Network */, 138 | 934F65D62302FAA0006EA517 /* Support */, 139 | 934F65D52302FA8B006EA517 /* UI */, 140 | 934F65D72302FB04006EA517 /* placeholder.html */, 141 | 9381367122FC2B1F00BC6C0F /* source.json */, 142 | 93B6619D231919A1002E697B /* GithubHeaderRemovalScript.js */, 143 | ); 144 | path = Zusammen; 145 | sourceTree = ""; 146 | }; 147 | 9381366522FC2AB700BC6C0F /* ZusammenTests */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 9381366822FC2AB700BC6C0F /* Info.plist */, 151 | 9381366622FC2AB700BC6C0F /* ZusammenTests.swift */, 152 | ); 153 | path = ZusammenTests; 154 | sourceTree = ""; 155 | }; 156 | 9381367322FC4D4C00BC6C0F /* Core */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 9381367522FC4EBA00BC6C0F /* SourceExtension.swift */, 160 | 9381367422FC4EBA00BC6C0F /* SourceExtensionList.swift */, 161 | 938315162315C576001A1533 /* StringExtensions.swift */, 162 | 930DC6FD23166C750087FABA /* Constants.swift */, 163 | 930DC6FF23166D8E0087FABA /* FileHelper.swift */, 164 | 93B6619F231921AB002E697B /* Installer.swift */, 165 | ); 166 | path = Core; 167 | sourceTree = ""; 168 | }; 169 | 9381367822FC61F400BC6C0F /* Network */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 9381367922FC735D00BC6C0F /* SourceExtensionListLoader.swift */, 173 | ); 174 | path = Network; 175 | sourceTree = ""; 176 | }; 177 | BD5616B74B7CE6048B7052BC /* Pods */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | C6CE620CC31215EDBDB30DF9 /* Pods-Zusammen.debug.xcconfig */, 181 | 29581008347966C9FC10BB74 /* Pods-Zusammen.release.xcconfig */, 182 | ); 183 | path = Pods; 184 | sourceTree = ""; 185 | }; 186 | F2A1CFEECE4FDF2825241E94 /* Frameworks */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 9EF2EF2BC0E69499AC965732 /* Pods_Zusammen.framework */, 190 | ); 191 | name = Frameworks; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXGroup section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | 9381364F22FC2AB300BC6C0F /* Zusammen */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = 9381366B22FC2AB700BC6C0F /* Build configuration list for PBXNativeTarget "Zusammen" */; 200 | buildPhases = ( 201 | 3900AD58A9DB22ECC0B90D71 /* [CP] Check Pods Manifest.lock */, 202 | 9381364C22FC2AB300BC6C0F /* Sources */, 203 | 9381364D22FC2AB300BC6C0F /* Frameworks */, 204 | 9381364E22FC2AB300BC6C0F /* Resources */, 205 | DCECCD1261081C50ED70C01D /* [CP] Embed Pods Frameworks */, 206 | ); 207 | buildRules = ( 208 | ); 209 | dependencies = ( 210 | ); 211 | name = Zusammen; 212 | productName = Zusammen; 213 | productReference = 9381365022FC2AB300BC6C0F /* Zusammen.app */; 214 | productType = "com.apple.product-type.application"; 215 | }; 216 | 9381366122FC2AB600BC6C0F /* ZusammenTests */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = 9381366E22FC2AB700BC6C0F /* Build configuration list for PBXNativeTarget "ZusammenTests" */; 219 | buildPhases = ( 220 | 9381365E22FC2AB600BC6C0F /* Sources */, 221 | 9381365F22FC2AB600BC6C0F /* Frameworks */, 222 | 9381366022FC2AB600BC6C0F /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | 9381366422FC2AB700BC6C0F /* PBXTargetDependency */, 228 | ); 229 | name = ZusammenTests; 230 | productName = ZusammenTests; 231 | productReference = 9381366222FC2AB700BC6C0F /* ZusammenTests.xctest */; 232 | productType = "com.apple.product-type.bundle.unit-test"; 233 | }; 234 | /* End PBXNativeTarget section */ 235 | 236 | /* Begin PBXProject section */ 237 | 9381364822FC2AB300BC6C0F /* Project object */ = { 238 | isa = PBXProject; 239 | attributes = { 240 | LastSwiftUpdateCheck = 1030; 241 | LastUpgradeCheck = 1030; 242 | ORGANIZATIONNAME = "Karthikeya Udupa"; 243 | TargetAttributes = { 244 | 9381364F22FC2AB300BC6C0F = { 245 | CreatedOnToolsVersion = 10.3; 246 | }; 247 | 9381366122FC2AB600BC6C0F = { 248 | CreatedOnToolsVersion = 10.3; 249 | TestTargetID = 9381364F22FC2AB300BC6C0F; 250 | }; 251 | }; 252 | }; 253 | buildConfigurationList = 9381364B22FC2AB300BC6C0F /* Build configuration list for PBXProject "Zusammen" */; 254 | compatibilityVersion = "Xcode 9.3"; 255 | developmentRegion = en; 256 | hasScannedForEncodings = 0; 257 | knownRegions = ( 258 | en, 259 | Base, 260 | ); 261 | mainGroup = 9381364722FC2AB300BC6C0F; 262 | productRefGroup = 9381365122FC2AB300BC6C0F /* Products */; 263 | projectDirPath = ""; 264 | projectRoot = ""; 265 | targets = ( 266 | 9381364F22FC2AB300BC6C0F /* Zusammen */, 267 | 9381366122FC2AB600BC6C0F /* ZusammenTests */, 268 | ); 269 | }; 270 | /* End PBXProject section */ 271 | 272 | /* Begin PBXResourcesBuildPhase section */ 273 | 9381364E22FC2AB300BC6C0F /* Resources */ = { 274 | isa = PBXResourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 9381365822FC2AB600BC6C0F /* Assets.xcassets in Resources */, 278 | 9381367222FC2B1F00BC6C0F /* source.json in Resources */, 279 | 93B6619E231919A1002E697B /* GithubHeaderRemovalScript.js in Resources */, 280 | 934F65D82302FB04006EA517 /* placeholder.html in Resources */, 281 | 9381365B22FC2AB600BC6C0F /* Main.storyboard in Resources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | 9381366022FC2AB600BC6C0F /* Resources */ = { 286 | isa = PBXResourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 934F65D92302FB04006EA517 /* placeholder.html in Resources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXResourcesBuildPhase section */ 294 | 295 | /* Begin PBXShellScriptBuildPhase section */ 296 | 3900AD58A9DB22ECC0B90D71 /* [CP] Check Pods Manifest.lock */ = { 297 | isa = PBXShellScriptBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | ); 301 | inputFileListPaths = ( 302 | ); 303 | inputPaths = ( 304 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 305 | "${PODS_ROOT}/Manifest.lock", 306 | ); 307 | name = "[CP] Check Pods Manifest.lock"; 308 | outputFileListPaths = ( 309 | ); 310 | outputPaths = ( 311 | "$(DERIVED_FILE_DIR)/Pods-Zusammen-checkManifestLockResult.txt", 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | shellPath = /bin/sh; 315 | 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"; 316 | showEnvVarsInLog = 0; 317 | }; 318 | DCECCD1261081C50ED70C01D /* [CP] Embed Pods Frameworks */ = { 319 | isa = PBXShellScriptBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | ); 323 | inputFileListPaths = ( 324 | "${PODS_ROOT}/Target Support Files/Pods-Zusammen/Pods-Zusammen-frameworks-${CONFIGURATION}-input-files.xcfilelist", 325 | ); 326 | name = "[CP] Embed Pods Frameworks"; 327 | outputFileListPaths = ( 328 | "${PODS_ROOT}/Target Support Files/Pods-Zusammen/Pods-Zusammen-frameworks-${CONFIGURATION}-output-files.xcfilelist", 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | shellPath = /bin/sh; 332 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Zusammen/Pods-Zusammen-frameworks.sh\"\n"; 333 | showEnvVarsInLog = 0; 334 | }; 335 | /* End PBXShellScriptBuildPhase section */ 336 | 337 | /* Begin PBXSourcesBuildPhase section */ 338 | 9381364C22FC2AB300BC6C0F /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | 9381365622FC2AB300BC6C0F /* ExtensionsListViewController.swift in Sources */, 343 | 930DC6FE23166C750087FABA /* Constants.swift in Sources */, 344 | 9348E6BD22FDD8ED0090F0DE /* SourceExtensionCell.swift in Sources */, 345 | 930DC70023166D8E0087FABA /* FileHelper.swift in Sources */, 346 | 93BF48B723022E25008BF88C /* SourceExtensionContentView.swift in Sources */, 347 | 93B661A0231921AB002E697B /* Installer.swift in Sources */, 348 | 938315172315C576001A1533 /* StringExtensions.swift in Sources */, 349 | 9381367A22FC735D00BC6C0F /* SourceExtensionListLoader.swift in Sources */, 350 | 9381367622FC4EBA00BC6C0F /* SourceExtensionList.swift in Sources */, 351 | 9381365422FC2AB300BC6C0F /* AppDelegate.swift in Sources */, 352 | 932F16DF231530DA000C3B28 /* BadgeLabel.swift in Sources */, 353 | 9381367722FC4EBA00BC6C0F /* SourceExtension.swift in Sources */, 354 | ); 355 | runOnlyForDeploymentPostprocessing = 0; 356 | }; 357 | 9381365E22FC2AB600BC6C0F /* Sources */ = { 358 | isa = PBXSourcesBuildPhase; 359 | buildActionMask = 2147483647; 360 | files = ( 361 | 9381366722FC2AB700BC6C0F /* ZusammenTests.swift in Sources */, 362 | ); 363 | runOnlyForDeploymentPostprocessing = 0; 364 | }; 365 | /* End PBXSourcesBuildPhase section */ 366 | 367 | /* Begin PBXTargetDependency section */ 368 | 9381366422FC2AB700BC6C0F /* PBXTargetDependency */ = { 369 | isa = PBXTargetDependency; 370 | target = 9381364F22FC2AB300BC6C0F /* Zusammen */; 371 | targetProxy = 9381366322FC2AB700BC6C0F /* PBXContainerItemProxy */; 372 | }; 373 | /* End PBXTargetDependency section */ 374 | 375 | /* Begin PBXVariantGroup section */ 376 | 9381365922FC2AB600BC6C0F /* Main.storyboard */ = { 377 | isa = PBXVariantGroup; 378 | children = ( 379 | 9381365A22FC2AB600BC6C0F /* Base */, 380 | ); 381 | name = Main.storyboard; 382 | path = .; 383 | sourceTree = ""; 384 | }; 385 | /* End PBXVariantGroup section */ 386 | 387 | /* Begin XCBuildConfiguration section */ 388 | 9381366922FC2AB700BC6C0F /* Debug */ = { 389 | isa = XCBuildConfiguration; 390 | buildSettings = { 391 | ALWAYS_SEARCH_USER_PATHS = NO; 392 | CLANG_ANALYZER_NONNULL = YES; 393 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_ENABLE_OBJC_WEAK = YES; 399 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 400 | CLANG_WARN_BOOL_CONVERSION = YES; 401 | CLANG_WARN_COMMA = YES; 402 | CLANG_WARN_CONSTANT_CONVERSION = YES; 403 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 404 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 405 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 406 | CLANG_WARN_EMPTY_BODY = YES; 407 | CLANG_WARN_ENUM_CONVERSION = YES; 408 | CLANG_WARN_INFINITE_RECURSION = YES; 409 | CLANG_WARN_INT_CONVERSION = YES; 410 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 411 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 412 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 415 | CLANG_WARN_STRICT_PROTOTYPES = YES; 416 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 417 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 418 | CLANG_WARN_UNREACHABLE_CODE = YES; 419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 420 | CODE_SIGN_IDENTITY = "Mac Developer"; 421 | COPY_PHASE_STRIP = NO; 422 | DEBUG_INFORMATION_FORMAT = dwarf; 423 | ENABLE_STRICT_OBJC_MSGSEND = YES; 424 | ENABLE_TESTABILITY = YES; 425 | GCC_C_LANGUAGE_STANDARD = gnu11; 426 | GCC_DYNAMIC_NO_PIC = NO; 427 | GCC_NO_COMMON_BLOCKS = YES; 428 | GCC_OPTIMIZATION_LEVEL = 0; 429 | GCC_PREPROCESSOR_DEFINITIONS = ( 430 | "DEBUG=1", 431 | "$(inherited)", 432 | ); 433 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 434 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 435 | GCC_WARN_UNDECLARED_SELECTOR = YES; 436 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 437 | GCC_WARN_UNUSED_FUNCTION = YES; 438 | GCC_WARN_UNUSED_VARIABLE = YES; 439 | MACOSX_DEPLOYMENT_TARGET = 10.14; 440 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 441 | MTL_FAST_MATH = YES; 442 | ONLY_ACTIVE_ARCH = YES; 443 | SDKROOT = macosx; 444 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 446 | }; 447 | name = Debug; 448 | }; 449 | 9381366A22FC2AB700BC6C0F /* Release */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | ALWAYS_SEARCH_USER_PATHS = NO; 453 | CLANG_ANALYZER_NONNULL = YES; 454 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 455 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 456 | CLANG_CXX_LIBRARY = "libc++"; 457 | CLANG_ENABLE_MODULES = YES; 458 | CLANG_ENABLE_OBJC_ARC = YES; 459 | CLANG_ENABLE_OBJC_WEAK = YES; 460 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 461 | CLANG_WARN_BOOL_CONVERSION = YES; 462 | CLANG_WARN_COMMA = YES; 463 | CLANG_WARN_CONSTANT_CONVERSION = YES; 464 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 465 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 466 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 467 | CLANG_WARN_EMPTY_BODY = YES; 468 | CLANG_WARN_ENUM_CONVERSION = YES; 469 | CLANG_WARN_INFINITE_RECURSION = YES; 470 | CLANG_WARN_INT_CONVERSION = YES; 471 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 472 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 473 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 476 | CLANG_WARN_STRICT_PROTOTYPES = YES; 477 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 478 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 479 | CLANG_WARN_UNREACHABLE_CODE = YES; 480 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 481 | CODE_SIGN_IDENTITY = "Mac Developer"; 482 | COPY_PHASE_STRIP = NO; 483 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 484 | ENABLE_NS_ASSERTIONS = NO; 485 | ENABLE_STRICT_OBJC_MSGSEND = YES; 486 | GCC_C_LANGUAGE_STANDARD = gnu11; 487 | GCC_NO_COMMON_BLOCKS = YES; 488 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 489 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 490 | GCC_WARN_UNDECLARED_SELECTOR = YES; 491 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 492 | GCC_WARN_UNUSED_FUNCTION = YES; 493 | GCC_WARN_UNUSED_VARIABLE = YES; 494 | MACOSX_DEPLOYMENT_TARGET = 10.14; 495 | MTL_ENABLE_DEBUG_INFO = NO; 496 | MTL_FAST_MATH = YES; 497 | SDKROOT = macosx; 498 | SWIFT_COMPILATION_MODE = wholemodule; 499 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 500 | }; 501 | name = Release; 502 | }; 503 | 9381366C22FC2AB700BC6C0F /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | baseConfigurationReference = C6CE620CC31215EDBDB30DF9 /* Pods-Zusammen.debug.xcconfig */; 506 | buildSettings = { 507 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 508 | CODE_SIGN_STYLE = Automatic; 509 | COMBINE_HIDPI_IMAGES = YES; 510 | DEVELOPMENT_TEAM = UYGU8PDBPS; 511 | INFOPLIST_FILE = Zusammen/Support/Info.plist; 512 | LD_RUNPATH_SEARCH_PATHS = ( 513 | "$(inherited)", 514 | "@executable_path/../Frameworks", 515 | ); 516 | MACOSX_DEPLOYMENT_TARGET = 10.14; 517 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.Zusammen; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_VERSION = 5.0; 520 | }; 521 | name = Debug; 522 | }; 523 | 9381366D22FC2AB700BC6C0F /* Release */ = { 524 | isa = XCBuildConfiguration; 525 | baseConfigurationReference = 29581008347966C9FC10BB74 /* Pods-Zusammen.release.xcconfig */; 526 | buildSettings = { 527 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 528 | CODE_SIGN_STYLE = Automatic; 529 | COMBINE_HIDPI_IMAGES = YES; 530 | DEVELOPMENT_TEAM = UYGU8PDBPS; 531 | INFOPLIST_FILE = Zusammen/Support/Info.plist; 532 | LD_RUNPATH_SEARCH_PATHS = ( 533 | "$(inherited)", 534 | "@executable_path/../Frameworks", 535 | ); 536 | MACOSX_DEPLOYMENT_TARGET = 10.14; 537 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.Zusammen; 538 | PRODUCT_NAME = "$(TARGET_NAME)"; 539 | SWIFT_VERSION = 5.0; 540 | }; 541 | name = Release; 542 | }; 543 | 9381366F22FC2AB700BC6C0F /* Debug */ = { 544 | isa = XCBuildConfiguration; 545 | buildSettings = { 546 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 547 | BUNDLE_LOADER = "$(TEST_HOST)"; 548 | CODE_SIGN_STYLE = Automatic; 549 | COMBINE_HIDPI_IMAGES = YES; 550 | DEVELOPMENT_TEAM = UYGU8PDBPS; 551 | INFOPLIST_FILE = ZusammenTests/Info.plist; 552 | LD_RUNPATH_SEARCH_PATHS = ( 553 | "$(inherited)", 554 | "@executable_path/../Frameworks", 555 | "@loader_path/../Frameworks", 556 | ); 557 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.ZusammenTests; 558 | PRODUCT_NAME = "$(TARGET_NAME)"; 559 | SWIFT_VERSION = 5.0; 560 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Zusammen.app/Contents/MacOS/Zusammen"; 561 | }; 562 | name = Debug; 563 | }; 564 | 9381367022FC2AB700BC6C0F /* Release */ = { 565 | isa = XCBuildConfiguration; 566 | buildSettings = { 567 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 568 | BUNDLE_LOADER = "$(TEST_HOST)"; 569 | CODE_SIGN_STYLE = Automatic; 570 | COMBINE_HIDPI_IMAGES = YES; 571 | DEVELOPMENT_TEAM = UYGU8PDBPS; 572 | INFOPLIST_FILE = ZusammenTests/Info.plist; 573 | LD_RUNPATH_SEARCH_PATHS = ( 574 | "$(inherited)", 575 | "@executable_path/../Frameworks", 576 | "@loader_path/../Frameworks", 577 | ); 578 | PRODUCT_BUNDLE_IDENTIFIER = com.karthik.ZusammenTests; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | SWIFT_VERSION = 5.0; 581 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Zusammen.app/Contents/MacOS/Zusammen"; 582 | }; 583 | name = Release; 584 | }; 585 | /* End XCBuildConfiguration section */ 586 | 587 | /* Begin XCConfigurationList section */ 588 | 9381364B22FC2AB300BC6C0F /* Build configuration list for PBXProject "Zusammen" */ = { 589 | isa = XCConfigurationList; 590 | buildConfigurations = ( 591 | 9381366922FC2AB700BC6C0F /* Debug */, 592 | 9381366A22FC2AB700BC6C0F /* Release */, 593 | ); 594 | defaultConfigurationIsVisible = 0; 595 | defaultConfigurationName = Release; 596 | }; 597 | 9381366B22FC2AB700BC6C0F /* Build configuration list for PBXNativeTarget "Zusammen" */ = { 598 | isa = XCConfigurationList; 599 | buildConfigurations = ( 600 | 9381366C22FC2AB700BC6C0F /* Debug */, 601 | 9381366D22FC2AB700BC6C0F /* Release */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | 9381366E22FC2AB700BC6C0F /* Build configuration list for PBXNativeTarget "ZusammenTests" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 9381366F22FC2AB700BC6C0F /* Debug */, 610 | 9381367022FC2AB700BC6C0F /* Release */, 611 | ); 612 | defaultConfigurationIsVisible = 0; 613 | defaultConfigurationName = Release; 614 | }; 615 | /* End XCConfigurationList section */ 616 | }; 617 | rootObject = 9381364822FC2AB300BC6C0F /* Project object */; 618 | } 619 | -------------------------------------------------------------------------------- /Zusammen/Core/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 28/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Constants { 12 | // Temporary folder where we will be storing cloned repository and downloaded extensions. 13 | static let tempFolderPath = "/tmp/zusammen/" 14 | // Latest swift version for the filter segment control on the app. 15 | static let lastSwiftVersion = "5" 16 | // URL for the System Preference > Extensions panel. 17 | static let extensionPreferencesURL = "/System/Library/PreferencePanes/Extensions.prefPane" 18 | } 19 | -------------------------------------------------------------------------------- /Zusammen/Core/FileHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileHelper.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 28/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A structure to store all kinds of file system helper functions. 12 | struct FileHelper { 13 | /// Remove temporary folder with all the previously downloaded repositories and extensions. 14 | static func removeTemporaryFolder() { 15 | do { 16 | try FileManager.default.removeItem(atPath: Constants.tempFolderPath) 17 | print(" * Cleared the tmp folder for Zusammen.") 18 | } catch { 19 | print() 20 | print(" * Error: Unable to file/clear the tmp folder for Zusammen - \(error.localizedDescription).") 21 | } 22 | } 23 | 24 | /// Remove the folder at the provided path. 25 | /// 26 | /// - parameter folderPath: Path for the folder that needs to be remove. 27 | static func removeFolder(atPath: String) { 28 | do { 29 | try FileManager.default.removeItem(atPath: atPath) 30 | print(" * Cleared the tmp folder at location \(atPath).") 31 | } catch { 32 | print(" * Error: Unable to file/clear the tmp folder at \(atPath) - \(error.localizedDescription).") 33 | } 34 | } 35 | 36 | /// Download file from the given URL. 37 | /// 38 | /// - Parameters: 39 | /// - fileURL: URL of the file from the server. 40 | /// - destination: Destination folder where to place the file after downloading. 41 | /// - fileName: Name of the file. 42 | /// - completion: Completion handler the provides the result and reason for the result. 43 | static func downloadFile(fileURL: URL, destination: String, fileName: String, completion: @escaping (_ result: Bool, _ reason: String?) -> Void) { 44 | let sessionConfig = URLSessionConfiguration.default 45 | let session = URLSession(configuration: sessionConfig) 46 | let request = URLRequest(url: fileURL) 47 | 48 | let task = session.downloadTask(with: request) { tempLocalUrl, response, error in 49 | if let tempLocalUrl = tempLocalUrl, error == nil { 50 | // Successfully downloaded the request. 51 | if let statusCode = (response as? HTTPURLResponse)?.statusCode { 52 | print(" * Successfully downloaded (\(fileURL)) Status code: \(statusCode)") 53 | } 54 | 55 | // We will need to create the directory if it does not exist. 56 | do { 57 | try FileManager.default.createDirectory(at: URL(fileURLWithPath: destination), withIntermediateDirectories: true, attributes: nil) 58 | } catch { 59 | print(" * Error creating folder at the new location, probably already exists.") 60 | } 61 | 62 | // Now move the temporary file to the folder in the zusammen temp folder. 63 | do { 64 | let destinationFilePath = "\(destination)/\(fileName)" 65 | try FileManager.default.copyItem(atPath: tempLocalUrl.path, toPath: destinationFilePath) 66 | completion(true, nil) 67 | } catch { 68 | print(" * Error creating file at the new location.") 69 | completion(false, "Error creating file at the new location.") 70 | } 71 | 72 | } else { 73 | print(" * Error took place while downloading a file.") 74 | completion(false, "Error took place while downloading a file.") 75 | } 76 | } 77 | task.resume() 78 | } 79 | 80 | /// Read the file from the current bundle and return the string content inside it. 81 | /// 82 | /// - Parameters: 83 | /// - filename: Name of the file. 84 | /// - extension: Extension of the file. 85 | /// - Returns: String content in the file. 86 | /// - Throws: Throws the error when fetching file and reading the content as string. 87 | static func loadFileFromBundle(filename: String, fileExtension: String) throws -> String { 88 | guard let path = Bundle.main.path(forResource: filename, ofType: fileExtension) else { 89 | return "" 90 | } 91 | let content = try String(contentsOfFile: path) 92 | return content 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Zusammen/Core/Installer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Installer.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 30/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | 12 | /// Installation of various kind of source extension. 13 | extension SourceExtension { 14 | /// Find and return the user's application folder path. 15 | private func userApplicationPath() -> String { 16 | let dirPaths = NSSearchPathForDirectoriesInDomains(.applicationDirectory, 17 | .userDomainMask, true) 18 | return dirPaths.first! 19 | } 20 | 21 | /// An alert dialog that is shown when the app installation has completed. 22 | /// - Parameter installStatus: Status of the installation. 23 | private func installationCompleteDialog(_ installStatus: Bool) -> NSAlert { 24 | let alert: NSAlert = NSAlert() 25 | if installStatus { 26 | alert.messageText = "The application has been installed, enable the extensions in the system preference." 27 | alert.alertStyle = .informational 28 | } else { 29 | alert.messageText = "We were unable to install the application, please click on the application and then enable the extensions in the system preferences." 30 | alert.alertStyle = .warning 31 | } 32 | alert.addButton(withTitle: "Close") 33 | alert.addButton(withTitle: "Extensions Preferences") 34 | return alert 35 | } 36 | 37 | /// Show a dialog when installation of the error message is shown. 38 | /// - Parameter errorMessage: Custom error message that can be shown to the user. 39 | private func showInstallationFailureDialog(_ errorMessage: String?) { 40 | DispatchQueue.main.async { 41 | let alert: NSAlert = NSAlert() 42 | alert.messageText = errorMessage ?? "The installation was not successful. Please open the source code webpage and install manually." 43 | alert.alertStyle = .warning 44 | _ = alert.runModal() 45 | } 46 | } 47 | 48 | /// Install the source extension based on the configuration provided. 49 | /// - Parameter completion: Result of the installation. 50 | func install(_ completion: @escaping (_ result: Bool) -> Void) { 51 | // If there is no download URL then we cannot do much about it. 52 | // This condition should never be triggered as the button would be disabled in case there is no URL. 53 | guard let downloadPath = self.downloadUrl, let downloadURL = URL(string: downloadPath) else { 54 | completion(false) 55 | return 56 | } 57 | 58 | // We need a name and path for the temp folder for the extenstion. 59 | // We need to clear out the invalid characters before we use the name as the folder name. 60 | var invalidCharacters = CharacterSet(charactersIn: ":/") 61 | invalidCharacters.formUnion(.newlines) 62 | invalidCharacters.formUnion(.illegalCharacters) 63 | invalidCharacters.formUnion(.controlCharacters) 64 | invalidCharacters.formUnion(.whitespacesAndNewlines) 65 | let folderName = name.components(separatedBy: invalidCharacters).joined(separator: "") 66 | let tempFolderPath = "\(Constants.tempFolderPath)\(folderName)" 67 | 68 | // Remove the folder if it already exists so that we do not have duplication realted problems. 69 | FileHelper.removeFolder(atPath: tempFolderPath) 70 | 71 | if installationType == .appleStore { 72 | // Open the app store page on the browser, since apps might not be in the country of the user. 73 | // We do not have a sure shot way to show it in the app store app. 74 | NSWorkspace.shared.open(downloadURL) 75 | completion(true) 76 | } else if installationType == .githubSource { 77 | // Currently we are just cloning the github repo and opening the folder in finder for the user. 78 | // Installation directly is tricky as we will need to know and have the build system 79 | let input = "git clone \(downloadPath) '\(tempFolderPath)'" 80 | _ = input.runAsCommand { result, message in 81 | print(message) 82 | NSWorkspace.shared.openFile(tempFolderPath) 83 | completion(result) 84 | } 85 | } else { 86 | let fileName = downloadURL.lastPathComponent 87 | FileHelper.downloadFile(fileURL: downloadURL, destination: tempFolderPath, fileName: fileName) { successful, errorMessage in 88 | if successful == false { 89 | self.showInstallationFailureDialog(errorMessage) 90 | completion(false) 91 | return 92 | } 93 | 94 | if self.installationType == .githubRelease { 95 | let input = "unzip -o '\(tempFolderPath)/\(fileName)' -d \(self.userApplicationPath())" 96 | input.runAsCommand(completion: { _, message in 97 | print(message) 98 | let appName = fileName.components(separatedBy: ".").first! 99 | let launchStatus = NSWorkspace.shared.launchApplication(appName) 100 | DispatchQueue.main.async { 101 | let alert = self.installationCompleteDialog(launchStatus) 102 | let alertResult = alert.runModal() 103 | if alertResult == .alertSecondButtonReturn { 104 | NSWorkspace.shared.open(URL(fileURLWithPath: Constants.extensionPreferencesURL)) 105 | } 106 | completion(true) 107 | } 108 | NSWorkspace.shared.open(URL(fileURLWithPath: self.userApplicationPath(), isDirectory: true)) 109 | }) 110 | } else if self.installationType == .appFile { 111 | let input = "cp -rf '\(tempFolderPath)/\(fileName)' \(self.userApplicationPath())" 112 | input.runAsCommand(completion: { _, _ in 113 | let appName = fileName.components(separatedBy: ".").first! 114 | let launchStatus = NSWorkspace.shared.launchApplication(appName) 115 | DispatchQueue.main.async { 116 | let alert = self.installationCompleteDialog(launchStatus) 117 | let alertResult = alert.runModal() 118 | if alertResult == .alertSecondButtonReturn { 119 | NSWorkspace.shared.open(URL(fileURLWithPath: Constants.extensionPreferencesURL)) 120 | } 121 | completion(true) 122 | } 123 | NSWorkspace.shared.open(URL(fileURLWithPath: self.userApplicationPath(), isDirectory: true)) 124 | }) 125 | } else if self.installationType == .dmgFile { 126 | let input = "hdiutil attach '\(tempFolderPath)/\(fileName)'" 127 | let appName = fileName.components(separatedBy: ".").first! 128 | input.runAsCommand(completion: { _, _ in 129 | DispatchQueue.main.async { 130 | let alert: NSAlert = NSAlert() 131 | alert.messageText = "Copy the file from the mounted volume to the Applications folder." 132 | alert.alertStyle = .informational 133 | _ = alert.runModal() 134 | completion(true) 135 | } 136 | }) 137 | NSWorkspace.shared.open(URL(fileURLWithPath: "/Volumes/\(appName)", isDirectory: true)) 138 | } 139 | } 140 | } 141 | // Should never be reached as `unknown` installation type should not allowed to be installed. 142 | completion(false) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Zusammen/Core/SourceExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceExtension.swift 3 | // 4 | // Created by Karthikeya Udupa on 08/08/2019 5 | // Copyright (c) Karthik. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Installation type for the extensions. 11 | /// 12 | /// - appleStore: Is a link for apple store - it will be opened in the browser. 13 | /// - githubSource: The source code is on github - it can be cloned to the tmp folder. 14 | /// - githubRelease: A zipped version of the extension .app file is in the github. Pull and install the latest release. 15 | /// - dmgFile: Install from a DMG file. 16 | /// - appFile: Install from a direct .app file. 17 | /// - unknown: In case there is a future release type or a wrong one, app shouldn't break. 18 | enum InstallationType: String, Codable { 19 | case appleStore = "app_store_link" 20 | case githubSource = "github_source" 21 | case githubRelease = "github_release" 22 | case dmgFile = "dmg_file" 23 | case appFile = "app_file" 24 | case unknown 25 | 26 | /// Provide the installation button title based on the installation Type. 27 | /// 28 | /// - Returns: Installation button title value. 29 | func installationText() -> String { 30 | switch self { 31 | case .appleStore: 32 | return "Install (Store)" 33 | case .githubSource: 34 | return "Install (Xcode Project)" 35 | case .githubRelease, .appFile, .dmgFile: 36 | return "Install (Download Ext.)" 37 | default: 38 | return "Cannot Install" 39 | } 40 | } 41 | 42 | /// Provides a human string description for the enum type. 43 | /// 44 | /// - Returns: Description for the type. 45 | func description() -> String { 46 | switch self { 47 | case .appleStore: 48 | return "Store" 49 | case .githubSource: 50 | return "Source" 51 | case .githubRelease, .appFile, .dmgFile: 52 | return "App" 53 | default: 54 | return "Unknown" 55 | } 56 | } 57 | } 58 | 59 | /// A structure to store information about Xcode source extension. 60 | struct SourceExtension: Codable { 61 | enum CodingKeys: String, CodingKey { 62 | case name 63 | case installationType = "installation_type" 64 | case descriptionValue = "description" 65 | case readmeUrl = "readme_url" 66 | case downloadUrl = "download_url" 67 | case sourceUrl = "source_url" 68 | case swiftVersion = "swift_version" 69 | case tags 70 | } 71 | 72 | /// Name of the extension (used for searching) 73 | var name: String 74 | /// How to install this extension on the user's machine. 75 | var installationType: InstallationType 76 | /// Brief description of the extension and what it does (used for searching). 77 | var descriptionValue: String 78 | /// Source code URL, this can be nil when the app is on the store. 79 | var sourceUrl: String? 80 | /// URL to download the extension from, this is used in combination with `installationType`. 81 | var downloadUrl: String? 82 | /// Tags to categorise extensions (used for searching) 83 | var tags: [String]? 84 | /// Readme file URL, in some cases this is not present, in which case it can just be the app store page or the website. 85 | var readmeUrl: String? 86 | /// Version of Swift that was used to create this extension. 87 | /// This is important in case of `githubSource` kind of installation. As older versions can't be build. 88 | var swiftVersion: String? 89 | 90 | /// Initialise the object with the decoder (from JSON). 91 | init(from decoder: Decoder) throws { 92 | let container = try decoder.container(keyedBy: CodingKeys.self) 93 | name = try container.decode(String.self, forKey: .name) 94 | // Installation type needs to be converted from string to enum of `InstallationType`. 95 | do { 96 | installationType = try container.decode(InstallationType.self, forKey: .installationType) 97 | } catch { 98 | installationType = .unknown 99 | } 100 | descriptionValue = try container.decode(String.self, forKey: .descriptionValue) 101 | sourceUrl = try container.decodeIfPresent(String.self, forKey: .sourceUrl) 102 | readmeUrl = try container.decodeIfPresent(String.self, forKey: .readmeUrl) 103 | tags = try container.decodeIfPresent([String].self, forKey: .tags) 104 | downloadUrl = try container.decodeIfPresent(String.self, forKey: .downloadUrl) 105 | swiftVersion = try container.decodeIfPresent(String.self, forKey: .swiftVersion) 106 | } 107 | 108 | /// Is the source extension a positive match for string that is provided. 109 | /// We match the string with the name, description, and tags of the source extension. 110 | /// 111 | /// - Parameter forString: String which needs to be in the name, description, or tags. 112 | /// - Returns: Is the extension a match or nor for the provided string. 113 | func isAMatch(forString: String) -> Bool { 114 | let searchString = forString.uppercased() 115 | let tagContainsString = tags != nil ? tags!.contains(where: { (value) -> Bool in 116 | value.contains(forString) 117 | }) : true 118 | return name.uppercased().contains(searchString) || descriptionValue.uppercased().contains(searchString) || 119 | tagContainsString 120 | } 121 | 122 | /// Is the source extension tagged with the selected tag value. 123 | /// 124 | /// - Parameter withTag: Tag value to check. 125 | /// - Returns: Indicates if the tag is present in the actual list of tags. 126 | func isTagged(withTag: String) -> Bool { 127 | return tags != nil ? tags!.contains(where: { (value) -> Bool in 128 | value.contains(withTag) 129 | }) : false 130 | } 131 | 132 | /// Check if the source extension's swift version matches the string that is provided. 133 | /// 134 | /// - Parameter baseSwiftVersion: Swift version that the extension must start from. 135 | /// - Returns: Boolean indicating if the swift version of the extension contains the version provided. 136 | func swiftVersionStartsWith(baseSwiftVersion: String) -> Bool { 137 | return swiftVersion != nil ? swiftVersion!.hasPrefix(baseSwiftVersion) : false 138 | } 139 | 140 | /// Combine all the strings in the tags array into 1 single string. 141 | /// 142 | /// - Returns: A single string containing all the tags seperated by a centered dot. 143 | func combineTags() -> String { 144 | guard let tagsValue = self.tags else { 145 | return "" 146 | } 147 | return tagsValue.map { $0.uppercased() }.joined(separator: " · ") 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Zusammen/Core/SourceExtensionList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceExtensionList.swift 3 | // 4 | // Created by Karthikeya Udupa on 08/08/2019 5 | // Copyright (c) Karthik. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A structure to hold a list of source extenion list. 11 | struct SourceExtensionList: Codable { 12 | enum CodingKeys: String, CodingKey { 13 | case extensions 14 | } 15 | 16 | var extensions: [SourceExtension] 17 | 18 | init(extensions: [SourceExtension]) { 19 | self.extensions = extensions 20 | } 21 | 22 | init(from decoder: Decoder) throws { 23 | let container = try decoder.container(keyedBy: CodingKeys.self) 24 | extensions = try container.decode([SourceExtension].self, forKey: .extensions) 25 | } 26 | 27 | /// Filter the list of source extension based on the values provided. 28 | /// 29 | /// - Parameters: 30 | /// - searchString: String that needs to be searched for in the extension's details. 31 | /// - limitToLatest: Limit the result to extensions that are made only in the latest version of Swift. 32 | /// - selectedTag: Only show extensions with a certain tag. 33 | /// - Returns: Filtered list of `SourceExtension`. 34 | func filter(_ searchString: String?, _ limitToLatest: Bool = false, _ selectedTag: String? = nil) -> [SourceExtension] { 35 | let filtered = extensions.filter { (extensionValue) -> Bool in 36 | let limitInRange = limitToLatest ? extensionValue.swiftVersionStartsWith(baseSwiftVersion: Constants.lastSwiftVersion) : true 37 | let contentSearch = searchString != nil && searchString!.count > 0 ? extensionValue.isAMatch(forString: searchString!) : true 38 | let tagPresent = selectedTag != nil ? extensionValue.isTagged(withTag: selectedTag!) : true 39 | return limitInRange && contentSearch && tagPresent 40 | } 41 | // Filtered list need to be sorted alphabetically. 42 | return filtered.sorted(by: { (extensionOne, extensionTwo) -> Bool in 43 | extensionOne.name < extensionTwo.name 44 | }) 45 | } 46 | 47 | /// Fetch all the unique tags in the source extensions provided. 48 | /// 49 | /// - Parameter inExtensions: Source extensions from which tags need to be considered. 50 | /// - Returns: Unique and alphabetically sorted tags. 51 | static func uniqueTags(inExtensions: [SourceExtension]) -> [String] { 52 | let allTags = inExtensions.compactMap { $0.tags } 53 | return Array(Set(Array(allTags.joined()))).sorted() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Zusammen/Core/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtensions.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 27/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Extension to store all the possible generic string functions. 12 | extension String { 13 | /// Runs the string as a command on the shell. 14 | /// 15 | /// - Parameter completion: A completion handler that provides the response and the stdout value. 16 | func runAsCommand(completion: @escaping (_ result: Bool, _ output: String) -> Void) { 17 | let pipe = Pipe() 18 | let task = Process() 19 | task.launchPath = "/bin/sh" 20 | task.arguments = ["-c", String(format: "%@", self)] 21 | task.standardOutput = pipe 22 | let file = pipe.fileHandleForReading 23 | task.launch() 24 | 25 | if let result = String(data: file.readDataToEndOfFile(), encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) { 26 | completion(true, result) 27 | } else { 28 | let errorString = " * Error running command - Unable to initialize string from file data." 29 | print(errorString) 30 | completion(false, errorString) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Zusammen/GithubHeaderRemovalScript.js: -------------------------------------------------------------------------------- 1 | function removeItems() { 2 | var header = document.getElementsByClassName('position-relative js-header-wrapper')[0]; 3 | var pageHead = document.getElementsByClassName('pagehead')[0]; 4 | var signupPrompt = document.getElementsByClassName('signup-prompt-bg rounded-1')[0]; 5 | var fileHeader = document.getElementsByClassName('d-flex flex-items-start flex-shrink-0 pb-3 flex-column flex-md-row')[0]; 6 | var boxHeader1 = document.getElementsByClassName('Box Box--condensed d-flex flex-column flex-shrink-0')[0]; 7 | var footer = document.getElementsByClassName('footer')[0]; 8 | var boxHeader2 = document.getElementsByClassName('Box-header py-2 d-flex flex-column flex-shrink-0 flex-md-row flex-md-items-center')[0]; 9 | 10 | header.parentNode.removeChild(header); 11 | pageHead.parentNode.removeChild(pageHead); 12 | signupPrompt.parentNode.removeChild(signupPrompt); 13 | fileHeader.parentNode.removeChild(fileHeader); 14 | boxHeader1.parentNode.removeChild(boxHeader1); 15 | footer.parentNode.removeChild(footer); 16 | boxHeader2.parentNode.removeChild(boxHeader2); 17 | } 18 | removeItems(); 19 | -------------------------------------------------------------------------------- /Zusammen/Network/SourceExtensionListLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceExtensionListLoader.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 08/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Loading extensions from the source JSON file 12 | // TODO: Move the extensions to a repository and load the repository. 13 | struct SourceExtensionListLoader { 14 | /// Load all possible extensions from the JSON source. 15 | /// 16 | /// - Parameter searchString: Search string provided by the user. 17 | /// - Parameter limitToLatest: Limit the extensions which use the latest version of the swift. 18 | /// - Returns: List of `ExtensionList` from the JSON source file. 19 | /// - Throws: JSON parsing errors. 20 | static func getExtensions(_ searchString: String?, _ limitToLatest: Bool = false, _ selectedTag: String? = nil) throws -> [SourceExtension] { 21 | let extensionsListJSON = try loadJSONFile("source") 22 | let jsonData = extensionsListJSON.data(using: .utf8)! 23 | return try JSONDecoder().decode(SourceExtensionList.self, from: jsonData).filter(searchString, limitToLatest, selectedTag) 24 | } 25 | 26 | /// Load the JSON file from the bundle. 27 | /// 28 | /// - Parameter filename: Filename for the JSON file. 29 | /// - Returns: The JSON file content as string. 30 | /// - Throws: Throws the error when fetching file and reading the content as string. 31 | private static func loadJSONFile(_ filename: String) throws -> String { 32 | return try FileHelper.loadFileFromBundle(filename: filename, fileExtension: "json") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Zusammen/Support/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 08/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | func applicationDidFinishLaunching(_: Notification) { 14 | // First thing we do is clear all the temporary files created by the app previously. 15 | FileHelper.removeTemporaryFolder() 16 | } 17 | 18 | func applicationWillTerminate(_: Notification) {} 19 | } 20 | -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "Icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "Icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "Icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "Icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "Icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "Icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "Icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "Icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "Icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insanoid/Zusammen/7499b2dd7cebac5280bf0539021da567fb37aa67/Zusammen/Support/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png -------------------------------------------------------------------------------- /Zusammen/Support/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Zusammen/Support/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | All Tags 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 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 496 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 553 | 566 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | -------------------------------------------------------------------------------- /Zusammen/Support/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | NSExceptionDomains 30 | 31 | apple.com 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | NSIncludesSubdomains 36 | 37 | 38 | 39 | 40 | NSHumanReadableCopyright 41 | Copyright © 2019 Karthikeya Udupa. All rights reserved. 42 | NSMainStoryboardFile 43 | Main 44 | NSPrincipalClass 45 | NSApplication 46 | 47 | 48 | -------------------------------------------------------------------------------- /Zusammen/Support/Zusammen.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Zusammen/UI/BadgeLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeLabel.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 27/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | 12 | /// NSTextField which creates a badge with rounded corners and orange background. 13 | class BadgeLabel: NSTextField { 14 | override init(frame frameRect: NSRect) { 15 | super.init(frame: frameRect) 16 | updateUI() 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | super.init(coder: coder) 21 | updateUI() 22 | } 23 | 24 | func updateUI() { 25 | usesSingleLineMode = true 26 | wantsLayer = true 27 | layer?.cornerRadius = 3 28 | layer?.masksToBounds = true 29 | alignment = .center 30 | textColor = .white 31 | backgroundColor = .orange 32 | } 33 | 34 | /// Update the value of the label if the value is nil then hide the label. 35 | func updateValue(string: String?) { 36 | if let value = string { 37 | stringValue = "Swift \(value)" 38 | isHidden = false 39 | } else { 40 | isHidden = true 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Zusammen/UI/ExtensionsListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionsListViewController.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 08/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import WebKit 11 | 12 | class ExtensionsListViewController: NSViewController, WKUIDelegate, WKNavigationDelegate, NSSearchFieldDelegate { 13 | @IBOutlet var extensionContentView: SourceExtensionContentView! 14 | @IBOutlet var extensionListTableView: NSTableView! 15 | @IBOutlet var searchField: NSSearchField! 16 | @IBOutlet var versionSegmentControl: NSSegmentedControl! 17 | @IBOutlet var tagsComboBox: NSComboBox! 18 | 19 | var uniqueTags: [String]? 20 | 21 | var extensionsList: [SourceExtension]? 22 | public var currentExtension: SourceExtension? { 23 | didSet { updateCurrentExtensionUI(selectedExtennsion: currentExtension) } 24 | } 25 | 26 | func updateCurrentExtensionUI(selectedExtennsion: SourceExtension?) { 27 | extensionContentView.currentExtension = selectedExtennsion 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | loadInitialData() 33 | loadData() 34 | } 35 | 36 | func loadInitialData() { 37 | let allExtensions = try! SourceExtensionListLoader.getExtensions(nil) 38 | uniqueTags = SourceExtensionList.uniqueTags(inExtensions: allExtensions) 39 | tagsComboBox.addItems(withObjectValues: uniqueTags ?? []) 40 | } 41 | 42 | func loadData() { 43 | let selectedTag = tagsComboBox.stringValue == "All Tags" ? nil : tagsComboBox.stringValue 44 | extensionsList = try! SourceExtensionListLoader.getExtensions(searchField.stringValue, 45 | versionSegmentControl.selectedSegment == 1, 46 | selectedTag) 47 | 48 | if currentExtension == nil || extensionsList!.contains(where: { (extensionValue) -> Bool in 49 | currentExtension?.name == extensionValue.name 50 | }) == false { 51 | currentExtension = nil 52 | } 53 | extensionListTableView.reloadData() 54 | 55 | if currentExtension != nil { 56 | let index = extensionsList?.firstIndex(where: { (extensionVal) -> Bool in 57 | currentExtension!.name == extensionVal.name 58 | }) 59 | extensionListTableView.selectRowIndexes(IndexSet(integer: index!), byExtendingSelection: false) 60 | extensionListTableView.scrollRowToVisible(index!) 61 | } 62 | } 63 | 64 | @IBAction func searchFieldAction(_: Any) { 65 | loadData() 66 | } 67 | 68 | @IBAction func filterButtonAction(_: Any) { 69 | loadData() 70 | } 71 | } 72 | 73 | // MARK: - TableView Delegates and Datasource Methods. 74 | 75 | extension ExtensionsListViewController: NSTableViewDelegate, NSTableViewDataSource { 76 | func numberOfRows(in _: NSTableView) -> Int { 77 | if let allExtensions = self.extensionsList { 78 | return allExtensions.count 79 | } 80 | return 0 81 | } 82 | 83 | func tableView(_ tableView: NSTableView, 84 | viewFor _: NSTableColumn?, 85 | row: Int) -> NSView? { 86 | return SourceExtensionCell.view(tableView: tableView, 87 | owner: self, 88 | subject: extensionsList?[row] as AnyObject?) 89 | } 90 | 91 | func tableView(_: NSTableView, heightOfRow _: Int) -> CGFloat { 92 | return CGFloat(SourceExtensionCell.height) 93 | } 94 | 95 | func tableView(_: NSTableView, shouldSelectRow row: Int) -> Bool { 96 | if row < extensionsList?.count ?? 0 { 97 | currentExtension = extensionsList?[row] 98 | } 99 | return true 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Zusammen/UI/SourceExtensionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceExtensionCell.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 09/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | import QuartzCore 12 | 13 | /// NSTableCellView to show information about the `SourceExtension`. 14 | class SourceExtensionCell: NSTableCellView { 15 | static var identifier = NSUserInterfaceItemIdentifier(rawValue: "extensionCellIdentifier") 16 | static var height = 75 17 | 18 | @IBOutlet var titleLabel: NSTextField! 19 | @IBOutlet var taglineLabel: NSTextField! 20 | @IBOutlet var badgeLabel: BadgeLabel! 21 | @IBOutlet var thumbnailImageView: NSImageView! 22 | 23 | /// Current `SourceExtension` that need to be shown in the cell. 24 | private var currentSourceExtension: SourceExtension? 25 | 26 | class func view(tableView: NSTableView, 27 | owner: AnyObject?, 28 | subject: AnyObject?) -> NSView? { 29 | if let view = tableView.makeView(withIdentifier: SourceExtensionCell.identifier, owner: owner) as? SourceExtensionCell { 30 | if let Extension = subject as? SourceExtension { 31 | view.setCurrentExtension(currentExtension: Extension) 32 | } 33 | return view 34 | } 35 | return nil 36 | } 37 | 38 | private func setCurrentExtension(currentExtension: SourceExtension) { 39 | currentSourceExtension = currentExtension 40 | updateUI() 41 | } 42 | 43 | override func awakeFromNib() { 44 | super.awakeFromNib() 45 | taglineLabel.textColor = .gray 46 | titleLabel.textColor = .black 47 | } 48 | 49 | func updateUI() { 50 | guard let extensionValue = self.currentSourceExtension else { 51 | return 52 | } 53 | titleLabel.textColor = .textColor 54 | taglineLabel.textColor = .textColor 55 | 56 | taglineLabel.alphaValue = 0.7 57 | 58 | let titleString = NSMutableAttributedString(string: extensionValue.name) 59 | if let installationDescription = currentSourceExtension?.installationType.description() { 60 | let installationInformation = " · \(installationDescription.uppercased())" 61 | let font = NSFont(name: "Verdana-Bold", size: 9) 62 | let subtleTextAttribute: [NSAttributedString.Key: Any] = [.font: font!, 63 | .foregroundColor: NSColor.systemGray] 64 | let attributedString = NSAttributedString(string: installationInformation, attributes: subtleTextAttribute) 65 | titleString.append(attributedString) 66 | } 67 | titleLabel.attributedStringValue = titleString 68 | taglineLabel.stringValue = extensionValue.descriptionValue 69 | badgeLabel.updateValue(string: extensionValue.swiftVersion) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Zusammen/UI/SourceExtensionContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionContentView.swift 3 | // Zusammen 4 | // 5 | // Created by Karthikeya Udupa on 13/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | import WebKit 12 | 13 | /// The right-side view for the app to showcase information about the `SourceExtension`. 14 | class SourceExtensionContentView: NSView { 15 | @IBOutlet var titleLabel: NSTextField! 16 | @IBOutlet var taglineLabel: NSTextField! 17 | @IBOutlet var tagsLabel: NSTextField! 18 | @IBOutlet var webView: WKWebView! 19 | @IBOutlet var installButton: NSButton! 20 | @IBOutlet var githubButton: NSButton! 21 | @IBOutlet var swiftVersionLabel: BadgeLabel! 22 | 23 | // This loading webview shows temporarily loading indicator while switching between two plugins. 24 | // Since we are removing content using JavaScript it takes time to render. 25 | // TODO: Replace the loading view with an actual `NSView`. 26 | var loadingWebView: WKWebView? 27 | 28 | /// Current extension that needs to be displayed in the view. 29 | var currentExtension: SourceExtension? { 30 | didSet { updateCurrentExtensionUI(selectedExtennsion: currentExtension) } 31 | } 32 | 33 | override init(frame frameRect: NSRect) { 34 | super.init(frame: frameRect) 35 | 36 | resetView() 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) { 40 | super.init(coder: aDecoder) 41 | } 42 | 43 | override func viewWillMove(toWindow newWindow: NSWindow?) { 44 | super.viewWillMove(toWindow: newWindow) 45 | 46 | if newWindow == nil { 47 | webView.uiDelegate = nil 48 | webView.navigationDelegate = nil 49 | } else { 50 | webView.uiDelegate = self 51 | webView.navigationDelegate = self 52 | } 53 | } 54 | 55 | // Reset the view to all blank values. 56 | func resetView() { 57 | installButton.isEnabled = false 58 | githubButton.isEnabled = false 59 | titleLabel.stringValue = "" 60 | taglineLabel.stringValue = "" 61 | loadDefaultWebpage(currentWebView: webView) 62 | hideLoadingWebView() 63 | swiftVersionLabel.updateValue(string: nil) 64 | } 65 | 66 | /// Update the view with the new selected `SourceExtension`'s details. 67 | /// 68 | /// - Parameter selectedExtennsion: `SourceExtension` that needs to be loaded into the view. 69 | func updateCurrentExtensionUI(selectedExtennsion: SourceExtension?) { 70 | // If there was no source extension selected, then hide the whole view. 71 | guard let currentExtension = selectedExtennsion else { 72 | alphaValue = 0.0 73 | resetView() 74 | return 75 | } 76 | 77 | alphaValue = 1.0 78 | hideLoadingWebView() 79 | 80 | titleLabel.stringValue = currentExtension.name 81 | taglineLabel.stringValue = currentExtension.descriptionValue 82 | swiftVersionLabel.updateValue(string: currentExtension.swiftVersion) 83 | 84 | // Toggle the button if it cannot be installed or there is no source code. 85 | installButton.isEnabled = currentExtension.downloadUrl != nil 86 | githubButton.isEnabled = currentExtension.installationType != .unknown 87 | installButton.title = currentExtension.installationType.installationText() 88 | tagsLabel.stringValue = currentExtension.combineTags() 89 | 90 | if let contentPath = currentExtension.readmeUrl, let contentURL = URL(string: contentPath) { 91 | openContentPage(path: contentURL) 92 | } else { 93 | // TODO: load a placeholder version of the page. 94 | } 95 | } 96 | 97 | // Install the currently selected addon on to your machine. 98 | /// 99 | /// - Parameter _: Button 100 | @IBAction func installAction(_: Any) { 101 | guard let currentExtensionValue = currentExtension else { 102 | return 103 | } 104 | 105 | installButton.isEnabled = false 106 | currentExtensionValue.install { _ in 107 | DispatchQueue.main.async { 108 | self.installButton.isEnabled = true 109 | } 110 | } 111 | } 112 | 113 | /// Open source file for the project (A URL on the source control repo). 114 | /// 115 | /// - Parameter _: Button 116 | @IBAction func openSourceAction(_: Any) { 117 | guard let sourceUrl = currentExtension?.sourceUrl, let url = URL(string: sourceUrl) else { 118 | return 119 | } 120 | NSWorkspace.shared.open(url) 121 | } 122 | 123 | /// Open the system preference panel with the extensions section option. 124 | /// 125 | /// - Parameter _: Button. 126 | @IBAction func openExtensionsSystemPreferencePanel(_: Any) { 127 | NSWorkspace.shared.open(URL(fileURLWithPath: Constants.extensionPreferencesURL)) 128 | } 129 | } 130 | 131 | /// All loading view methods in a single extension. 132 | extension SourceExtensionContentView { 133 | /// Create a new web view and show the loading placeholder page. 134 | func showLoadingWebView() { 135 | loadingWebView = WKWebView(frame: NSRect(x: 0, y: 0, width: frame.width, height: frame.height)) 136 | loadingWebView?.uiDelegate = self 137 | loadingWebView?.navigationDelegate = self 138 | loadingWebView!.heightAnchor.constraint(equalTo: webView.heightAnchor, multiplier: 1.0) 139 | loadingWebView!.topAnchor.constraint(equalTo: webView.topAnchor) 140 | addSubview(loadingWebView!) 141 | } 142 | 143 | /// Hide the loading web view that was created to cover the whole view. 144 | func hideLoadingWebView() { 145 | if loadingWebView != nil { 146 | loadingWebView?.removeFromSuperview() 147 | loadingWebView = nil 148 | } 149 | } 150 | 151 | /// Load the default webpage in the webview. 152 | func loadDefaultWebpage(currentWebView: WKWebView) { 153 | let url = Bundle.main.url(forResource: "placeholder", withExtension: "html")! 154 | currentWebView.loadFileURL(url, allowingReadAccessTo: url) 155 | let request = URLRequest(url: url) 156 | currentWebView.load(request) 157 | } 158 | } 159 | 160 | /// Webview related functions grouped together in a single extension. 161 | extension SourceExtensionContentView: WKUIDelegate, WKNavigationDelegate { 162 | func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { 163 | defer { 164 | if webView == self.webView { 165 | hideLoadingWebView() 166 | } 167 | } 168 | 169 | guard let url = webView.url, url.host == "github.com" else { 170 | return 171 | } 172 | // This script removes the header from github and just keeps the readme for the user to read. 173 | let script = try! FileHelper.loadFileFromBundle(filename: "GithubHeaderRemovalScript", fileExtension: "js") 174 | webView.evaluateJavaScript(script) 175 | } 176 | 177 | func webView(_ webView: WKWebView, didFail _: WKNavigation!, withError _: Error) { 178 | if webView == self.webView { 179 | hideLoadingWebView() 180 | } 181 | } 182 | 183 | func webView(_: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) { 184 | // Any new page that is triggered by clicking links inside the webview should be opened in the browser. 185 | guard navigationAction.navigationType == .other || navigationAction.navigationType == .reload else { 186 | decisionHandler(.cancel) 187 | let url = URL(string: navigationAction.request.url!.absoluteString)! 188 | NSWorkspace.shared.open(url) 189 | return 190 | } 191 | decisionHandler(.allow) 192 | } 193 | 194 | /// Open the content at the provided the URL in the default web view. 195 | /// 196 | /// - Parameter path: Path that needs to be loaded in the web view. 197 | func openContentPage(path: URL) { 198 | showLoadingWebView() 199 | loadDefaultWebpage(currentWebView: loadingWebView!) 200 | webView.load(URLRequest(url: path)) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Zusammen/placeholder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 100 | 101 | 102 | 103 |
104 |
105 |

Zusammen

106 |
107 |
108 |
109 | 110 | 111 |
112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /Zusammen/source.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensions": [{ 3 | "name": "xTextHandler", 4 | "download_url": "https://itunes.apple.com/app/id1163761963", 5 | "description": "Xcode Source Editor Extension based tools to improve the text editing experience of Xcode 8 and provide extensions with simple code.", 6 | "tags": ["formatter"], 7 | "installation_type": "app_store_link", 8 | "readme_url": "https://github.com/cyanzhong/xTextHandler/blob/master/README.md", 9 | "source_url": "https://github.com/cyanzhong/xTextHandler", 10 | "swift_version": "3" 11 | }, 12 | { 13 | "name": "quickType", 14 | "download_url": "https://itunes.apple.com/us/app/paste-json-as-code-quicktype/id1330801220?mt=12", 15 | "description": "quicktype infers types from sample JSON data, then outputs strongly typed models and serializers for working with that data in Swift, Objective-C, C++, Java and more. This extension adds native quicktype support to Xcode 9.", 16 | "tags": ["json", "code-generation", "codeable"], 17 | "source_url": "https://github.com/quicktype/quicktype-xcode", 18 | "readme_url": "https://github.com/quicktype/quicktype-xcode/blob/master/README.md", 19 | "installation_type": "app_store_link" 20 | }, 21 | { 22 | "name": "Import", 23 | "download_url": "https://itunes.apple.com/ie/app/import/id1167060791?mt=12", 24 | "description": "Xcode extension for adding imports from anywhere in the code.", 25 | "tags": ["headers"], 26 | "source_url": "https://github.com/markohlebar/Import", 27 | "readme_url": "https://github.com/markohlebar/Import/blob/master/README.md", 28 | "installation_type": "app_store_link", 29 | "swift_version": "4" 30 | }, 31 | { 32 | "name": "Swift Initializer Generator", 33 | "download_url": "https://github.com/Bouke/SwiftInitializerGenerator", 34 | "description": "Xcode Extension to generate a Swift initializer based on the lines you've selected.", 35 | "tags": ["formatter"], 36 | "source_url": "https://github.com/Bouke/SwiftInitializerGenerator", 37 | "readme_url": "https://github.com/Bouke/SwiftInitializerGenerator/blob/master/README.md", 38 | "installation_type": "github_source", 39 | "swift_version": "5" 40 | }, 41 | { 42 | "name": "XcodeWay", 43 | "download_url": "https://github.com/onmyway133/XcodeWay/releases/latest/download/XcodeWayApp.zip", 44 | "description": "Xcode Extension that helps navigating to many xocde places easier.", 45 | "tags": ["xcode-tools", "shortcuts"], 46 | "source_url": "https://github.com/onmyway133/XcodeWay", 47 | "readme_url": "https://github.com/onmyway133/XcodeWay/blob/master/README.md", 48 | "installation_type": "github_release", 49 | "swift_version": "5" 50 | }, 51 | { 52 | "name": "SwiftAI", 53 | "download_url": "https://github.com/hhfa008/SwiftAI", 54 | "description": "SwiftAI can generate Model class from JSON now. Codable and HandyJSON is supported.", 55 | "tags": ["code-generation", "codeable", "json"], 56 | "source_url": "https://github.com/hhfa008/SwiftAI", 57 | "readme_url": "https://github.com/hhfa008/SwiftAI/blob/master/README.md", 58 | "installation_type": "github_source", 59 | "swift_version": "5" 60 | }, 61 | { 62 | "name": "Jump", 63 | "download_url": "https://github.com/deszip/Jump", 64 | "description": "XCode source editor extension for quick moving cursor between lines.", 65 | "tags": ["xcode-tools", "lines"], 66 | "source_url": "https://github.com/deszip/Jump", 67 | "readme_url": "https://github.com/deszip/Jump/blob/master/README.md", 68 | "installation_type": "github_source", 69 | "swift_version": "3" 70 | }, 71 | { 72 | "name": "Strimmer", 73 | "download_url": "https://github.com/squarefrog/strimmer", 74 | "description": "Quickly strips all trailing whitespace from the current file.", 75 | "tags": ["formatter", "whitespace"], 76 | "source_url": "https://github.com/squarefrog/strimmer", 77 | "readme_url": "https://github.com/squarefrog/strimmer/blob/master/README.md", 78 | "installation_type": "github_source", 79 | "swift_version": "3" 80 | }, 81 | { 82 | "name": "Xcode Color Sense 2", 83 | "download_url": "https://github.com/onmyway133/XcodeColorSense2/releases/latest/download/XcodeColorSense2.zip", 84 | "description": "Shows the color for a particular hex or rgb color in the source code.", 85 | "tags": ["xcode-tools", "color"], 86 | "source_url": "https://github.com/onmyway133/XcodeColorSense2", 87 | "readme_url": "https://github.com/onmyway133/XcodeColorSense2/blob/master/README.md", 88 | "installation_type": "github_release", 89 | "swift_version": "3" 90 | }, 91 | { 92 | "name": "Xcode Equatable Generator", 93 | "download_url": "https://github.com/sergdort/XcodeEquatableGenerator", 94 | "description": "Shows the color for a particular hex or rgb color in the source code.", 95 | "tags": ["code-generation", "equatable"], 96 | "source_url": "https://github.com/sergdort/XcodeEquatableGenerator", 97 | "readme_url": "https://github.com/sergdort/XcodeEquatableGenerator/blob/master/README.md", 98 | "installation_type": "github_source", 99 | "swift_version": "3" 100 | }, 101 | { 102 | "name": "Alignment", 103 | "download_url": "https://itunes.apple.com/us/app/alignment-for-xcode/id1168397789?ls=1&mt=12", 104 | "description": "Source editor extension to align your assignment statement.", 105 | "tags": ["formatter"], 106 | "source_url": "https://github.com/tid-kijyun/XcodeSourceEditorExtension-Alignment", 107 | "readme_url": "https://github.com/tid-kijyun/XcodeSourceEditorExtension-Alignment/blob/master/README.md", 108 | "installation_type": "app_store_link", 109 | "swift_version": "4.2" 110 | }, 111 | { 112 | "name": "Clean Closure", 113 | "download_url": "https://github.com/BalestraPatrick/CleanClosureXcode", 114 | "description": "Simplify the syntax of closures in your Swift code by removing the useless ().", 115 | "tags": ["formatter"], 116 | "source_url": "https://github.com/BalestraPatrick/CleanClosureXcode", 117 | "readme_url": "https://github.com/BalestraPatrick/CleanClosureXcode/blob/master/README.md", 118 | "installation_type": "github_source", 119 | "swift_version": "3" 120 | }, 121 | { 122 | "name": "SwiftLint for Xcode", 123 | "download_url": "https://github.com/norio-nomura/SwiftLintForXcode", 124 | "description": "SwiftLint for Xcode is a Xcode Extension that was created to run SwiftLint.", 125 | "tags": ["formatter", "lint"], 126 | "source_url": "https://github.com/norio-nomura/SwiftLintForXcode", 127 | "readme_url": "https://github.com/norio-nomura/SwiftLintForXcode/blob/master/README.md", 128 | "installation_type": "github_source", 129 | "swift_version": "4" 130 | }, 131 | { 132 | "name": "Mark", 133 | "download_url": "https://github.com/velyan/Mark/releases/latest/download/Mark.zip", 134 | "description": "Xcode extension for automatic generation of MARK comments.", 135 | "tags": ["formatter", "comment"], 136 | "source_url": "https://github.com/velyan/Mark", 137 | "readme_url": "https://github.com/velyan/Mark/blob/master/README.md", 138 | "installation_type": "github_release", 139 | "swift_version": "3" 140 | }, 141 | { 142 | "name": "XShared", 143 | "download_url": "https://github.com/Otbivnoe/XShared/releases/latest/download/Shared.app.zip", 144 | "description": "Allows you copying the code with special formatting quotes for social (Slack, Telegram).", 145 | "tags": ["xcode-tools", "share", "social-media"], 146 | "source_url": "https://github.com/Otbivnoe/XShared", 147 | "readme_url": "https://github.com/Otbivnoe/XShared/blob/master/README.md", 148 | "installation_type": "github_release", 149 | "swift_version": "4" 150 | }, 151 | { 152 | "name": "Quick Add", 153 | "download_url": "https://github.com/funky-monkey/QuickAdd/releases/latest/download/", 154 | "description": "Quickly add a method implementation with comment from selected text.", 155 | "tags": ["code-generation"], 156 | "source_url": "https://github.com/funky-monkey/QuickAdd", 157 | "readme_url": "https://github.com/funky-monkey/QuickAdd/blob/master/README.md", 158 | "installation_type": "github_source", 159 | "swift_version": "3" 160 | }, 161 | { 162 | "name": "GenerateSwiftInit", 163 | "download_url": "https://github.com/bkobilansky/GenerateSwiftInit", 164 | "description": "Generates an init function based on properties of a type.", 165 | "tags": ["code-generation"], 166 | "source_url": "https://github.com/bkobilansky/GenerateSwiftInit", 167 | "readme_url": "https://github.com/bkobilansky/GenerateSwiftInit/blob/master/README.md", 168 | "installation_type": "github_source", 169 | "swift_version": "3" 170 | }, 171 | { 172 | "name": "Xcode Editor Plus", 173 | "download_url": "https://github.com/wangshengjia/XcodeEditorPlus", 174 | "description": "Convenient editor shortcuts to Xcode using Xcode Source Editor Extension, inspired from AppCode.", 175 | "tags": ["xcode-tools", "shortcuts"], 176 | "source_url": "https://github.com/wangshengjia/XcodeEditorPlus", 177 | "readme_url": "https://github.com/wangshengjia/XcodeEditorPlus/blob/master/README.md", 178 | "installation_type": "github_source", 179 | "swift_version": "3" 180 | }, 181 | { 182 | "name": "Localizer", 183 | "download_url": "https://github.com/esttorhe/Localizer", 184 | "description": "Localizer is a pretty simple and naive string localizer.", 185 | "tags": ["xcode-tools", "localize"], 186 | "source_url": "https://github.com/esttorhe/Localizer", 187 | "readme_url": "https://github.com/esttorhe/Localizer/blob/master/README.md", 188 | "installation_type": "github_source", 189 | "swift_version": "3" 190 | }, 191 | { 192 | "name": "Xcode Search", 193 | "download_url": "https://github.com/skyline75489/Xcode-Search", 194 | "description": "Source Editor Extension that searches external source (Google, StackOverflow, etc).", 195 | "tags": ["xcode-tools", "search"], 196 | "source_url": "https://github.com/skyline75489/Xcode-Search", 197 | "readme_url": "https://github.com/skyline75489/Xcode-Search/blob/master/README.md", 198 | "installation_type": "github_source", 199 | "swift_version": "3" 200 | }, 201 | { 202 | "name": "CwlWhitespace", 203 | "download_url": "https://github.com/mattgallagher/CwlWhitespace", 204 | "description": "A whitespace policing source command extension for Xcode 8.", 205 | "tags": ["formatter", "whitespace"], 206 | "source_url": "https://github.com/mattgallagher/CwlWhitespace", 207 | "readme_url": "https://github.com/mattgallagher/CwlWhitespace/blob/master/README.md", 208 | "installation_type": "github_source", 209 | "swift_version": "4.2" 210 | }, 211 | { 212 | "name": "Snowonder", 213 | "download_url": "https://github.com/Karetski/Snowonder/releases/latest/download/Snowonder.app.zip", 214 | "description": "Convenient formatting operations for Import Declarations.", 215 | "tags": ["formatter", "imports"], 216 | "source_url": "https://github.com/Karetski/Snowonder", 217 | "readme_url": "https://github.com/Karetski/Snowonder/blob/master/README.md", 218 | "installation_type": "github_release", 219 | "swift_version": "3" 220 | }, 221 | { 222 | "name": "XAlign", 223 | "download_url": "https://github.com/qfish/XAlign/releases/latest/download/XAlign.app.zip", 224 | "description": "Source Editor extension to align regular code. It can align anything by using custom alignment patterns.", 225 | "tags": ["formatter", "alignment"], 226 | "source_url": "https://github.com/qfish/XAlign", 227 | "readme_url": "https://github.com/qfish/XAlign/blob/master/README.md", 228 | "installation_type": "github_release" 229 | }, 230 | { 231 | "name": "xcsort", 232 | "download_url": "https://itunes.apple.com/app/xcsort/id1153337296", 233 | "description": "Source Editor extension to align regular code. It can align anything by using custom alignment patterns.", 234 | "tags": ["formatter", "alignment"], 235 | "readme_url": "http://apps.brrm.ru/xcsort", 236 | "installation_type": "app_store_link" 237 | }, 238 | { 239 | "name": "SwiftFormat", 240 | "download_url": "https://github.com/nicklockwood/SwiftFormat/tree/master/EditorExtension/SwiftFormat%20for%20Xcode.app", 241 | "description": "Source Editor extension to align regular code. It can align anything by using custom alignment patterns.", 242 | "tags": ["formatter", "code-formatting"], 243 | "source_url": "https://github.com/nicklockwood/SwiftFormat", 244 | "readme_url": "https://github.com/nicklockwood/SwiftFormat/blob/master/README.md", 245 | "swift_version": "5", 246 | "installation_type": "app_file" 247 | }, 248 | { 249 | "name": "Swimat", 250 | "download_url": "https://github.com/Jintin/Swimat/releases/latest/download/Swimat.app.zip", 251 | "description": "Swimat is an Xcode plug-in to format your Swift code.", 252 | "tags": ["formatter", "code-formatting"], 253 | "source_url": "https://github.com/Jintin/Swimat", 254 | "readme_url": "https://github.com/Jintin/Swimat/blob/master/README.md", 255 | "swift_version": "5", 256 | "installation_type": "github_release" 257 | }, 258 | { 259 | "name": "PPImportArrangerExtension", 260 | "download_url": "https://github.com/VernonVan/PPImportArrangerExtension/releases/latest/download/PPImportArrangerExtension.app.zip", 261 | "description": "Sort imports in the source files.", 262 | "tags": ["formatter", "import"], 263 | "source_url": "https://github.com/VernonVan/PPImportArrangerExtension", 264 | "readme_url": "https://github.com/VernonVan/PPImportArrangerExtension/blob/master/README.md", 265 | "installation_type": "github_release" 266 | }, 267 | { 268 | "name": "Lines Sorter", 269 | "download_url": "https://github.com/V8tr/LinesSorter-Xcode-Extension/releases/download/v0.1/LinesSorter-v0.1.zip", 270 | "description": "Makes it easy to keep your import statements and long code lists organized, uniform and easy-to-read.", 271 | "tags": ["formatter", "import"], 272 | "source_url": "https://github.com/V8tr/LinesSorter-Xcode-Extension", 273 | "readme_url": "https://github.com/V8tr/LinesSorter-Xcode-Extension/blob/master/README.md", 274 | "installation_type": "github_release", 275 | "swift_version": "4" 276 | }, 277 | { 278 | "name": "AccessControlKitty", 279 | "download_url": "https://itunes.apple.com/us/app/accesscontrolkitty/id1450391666", 280 | "description": "Change the access control level of Swift code selection.", 281 | "tags": ["formatter", "utility"], 282 | "source_url": "https://github.com/zoejessica/accesscontrolkitty", 283 | "readme_url": "https://github.com/zoejessica/accesscontrolkitty/blob/master/README.md", 284 | "installation_type": "app_store_link", 285 | "swift_version": "4" 286 | }, 287 | { 288 | "name": "TrickerX", 289 | "download_url": "https://github.com/wleii/TrickerX/raw/master/app/TrickerX.dmg", 290 | "description": "Help you make Swift Codable CodingKeys automatically.", 291 | "tags": ["code-generation", "codeable"], 292 | "source_url": "https://github.com/wleii/TrickerX", 293 | "readme_url": "https://github.com/wleii/TrickerX/blob/master/README.md", 294 | "installation_type": "dmg_file", 295 | "swift_version": "4" 296 | }, 297 | { 298 | "name": "Swift Mock Generator", 299 | "download_url": "https://github.com/seanhenry/SwiftMockGeneratorForXcode/releases/latest/download/Swift.Mock.Generator.for.Xcode.dmg", 300 | "description": "Generate spy, stub, dummy, and partial spy classes automatically.", 301 | "tags": ["code-generation", "mock"], 302 | "source_url": "https://github.com/seanhenry/SwiftMockGeneratorForXcode", 303 | "readme_url": "https://github.com/seanhenry/SwiftMockGeneratorForXcode/blob/master/README.md", 304 | "installation_type": "dmg_link", 305 | "swift_version": "5" 306 | }, 307 | { 308 | "name": "JSON-to-Swift-Converter", 309 | "download_url": "https://github.com/mrlegowatch/JSON-to-Swift-Converter", 310 | "description": "Convert JSON format to Swift code.", 311 | "tags": ["code-generation", "JSON-to-Swift"], 312 | "source_url": "https://github.com/mrlegowatch/JSON-to-Swift-Converter", 313 | "readme_url": "https://github.com/mrlegowatch/JSON-to-Swift-Converter/blob/master/README.md", 314 | "installation_type": "github_source", 315 | "swift_version": "4" 316 | }, 317 | { 318 | "name": "JSON2Swift", 319 | "download_url": "https://itunes.apple.com/us/app/json2swift/id1208964041", 320 | "description": "Convert JSON format to Swift code.", 321 | "tags": ["code-generation", "JSON-to-Swift"], 322 | "readme_url": "https://itunes.apple.com/us/app/json2swift/id1208964041", 323 | "installation_type": "app_store_link" 324 | }, { 325 | "name": "CodeGenerator", 326 | "download_url": "https://github.com/WANGjieJacques/CodeGenerator/", 327 | "description": "Generate mock and equatable.", 328 | "tags": ["code-generation", "equatable", "mock"], 329 | "source_url": "https://github.com/WANGjieJacques/CodeGenerator/", 330 | "readme_url": "https://github.com/WANGjieJacques/CodeGenerator/blob/master/README.md", 331 | "installation_type": "github_source", 332 | "swift_version": "4" 333 | }, 334 | { 335 | "name": "Jumpy", 336 | "download_url": "https://github.com/eddiekaiger/Jumpy", 337 | "description": "Extension for jumping across multiple lines of code.", 338 | "tags": ["utility"], 339 | "source_url": "https://github.com/eddiekaiger/Jumpy", 340 | "readme_url": "https://github.com/eddiekaiger/Jumpy/blob/master/README.md", 341 | "installation_type": "github_source", 342 | "swift_version": "4" 343 | } 344 | ] 345 | } 346 | -------------------------------------------------------------------------------- /ZusammenTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | -------------------------------------------------------------------------------- /ZusammenTests/ZusammenTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZusammenTests.swift 3 | // ZusammenTests 4 | // 5 | // Created by Karthikeya Udupa on 08/08/2019. 6 | // Copyright © 2019 Karthikeya Udupa. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Zusammen 11 | 12 | class ZusammenTests: XCTestCase { 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDown() { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() { 27 | // This is an example of a performance test case. 28 | measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | default_platform(:mac) 2 | 3 | # Fastlane runs all built in actions from one directory above where the Fastfile lives 4 | # so make sure all the paths are relative in that regard. 5 | 6 | 7 | before_all do 8 | update_fastlane 9 | end 10 | 11 | desc "Initalise the project." 12 | lane :setup do 13 | cocoapods 14 | brew(command:"install swiftlint") 15 | brew(command:"install swiftformat") 16 | end 17 | 18 | desc "Does a static analysis of the project. Configure the options in .swiftlint.yml" 19 | lane :lint do 20 | swiftlint(mode: :autocorrect, config_file: '.swiftlint.yml') 21 | swiftlint(mode: :lint, strict: false, config_file: '.swiftlint.yml') 22 | end 23 | 24 | desc "Run swiftformat and format all the files." 25 | lane :format do 26 | exec("swiftformat ../") 27 | end 28 | 29 | desc "Synx to format the folder structure to be idendical between file-system and xcode project." 30 | lane :synx do 31 | exec("synx -p ../Zusammen.xcodeproj") 32 | end 33 | 34 | desc "Generate the documentation for the project." 35 | lane :doc do 36 | jazzy 37 | end 38 | 39 | desc "Run checks for liniting, formatting, folder structure - all in one go." 40 | lane :check do 41 | synx 42 | format 43 | lint 44 | end 45 | 46 | desc "Generates coverage for the project using xcov." 47 | lane :cov do 48 | xcov(workspace: "Zusammen.xcworkspace", scheme: "Zusammen", output_directory: "buo;d") 49 | end 50 | 51 | desc "Validates the pod file and find if there are any updates." 52 | lane :podcheck do 53 | UI.message("Running cocoapods outdated...") 54 | log, s = Open3.capture2("pod outdated") 55 | UI.message(log.partition("Updating spec repo `master`\n").last) 56 | outdated_packages = log.partition('Analyzing dependencies').last 57 | end 58 | 59 | desc "Bump the version number of the app, only the minor version." 60 | lane :bump_version do 61 | increment_version_number(bump_type: "minor") 62 | end 63 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-brew' 6 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ### setup 19 | ``` 20 | fastlane setup 21 | ``` 22 | Initalise the project. 23 | ### lint 24 | ``` 25 | fastlane lint 26 | ``` 27 | Does a static analysis of the project. Configure the options in .swiftlint.yml 28 | ### format 29 | ``` 30 | fastlane format 31 | ``` 32 | Run swiftformat and format all the files. 33 | ### synx 34 | ``` 35 | fastlane synx 36 | ``` 37 | Synx to format the folder structure to be idendical between file-system and xcode project. 38 | ### doc 39 | ``` 40 | fastlane doc 41 | ``` 42 | Generate the documentation for the project. 43 | ### check 44 | ``` 45 | fastlane check 46 | ``` 47 | Run checks for liniting, formatting, folder structure - all in one go. 48 | ### cov 49 | ``` 50 | fastlane cov 51 | ``` 52 | Generates coverage for the project using xcov. 53 | ### podcheck 54 | ``` 55 | fastlane podcheck 56 | ``` 57 | Validates the pod file and find if there are any updates. 58 | ### bump_version 59 | ``` 60 | fastlane bump_version 61 | ``` 62 | Bump the version number of the app, only the minor version. 63 | 64 | ---- 65 | 66 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 67 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 68 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 69 | -------------------------------------------------------------------------------- /fastlane/report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------