├── .gitignore ├── .swift-version ├── .travis.yml ├── CHANGELOG.md ├── GithubPilot.podspec ├── GithubPilot.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── GithubPilot.xcscheme │ └── GithubPilotTests.xcscheme ├── GithubPilot.xcworkspace └── contents.xcworkspacedata ├── GithubPilot ├── GithubPilot.h ├── Info.plist └── README.md ├── GithubPilotTests ├── FakeData │ ├── repo.json │ ├── user_me.json │ ├── user_mietzmithut.json │ ├── user_repos.json │ └── users.json ├── GithubPilotTests-Bridging-Header.h ├── GithubPilotTests.swift ├── Info.plist ├── StarGazersTest.swift ├── TestHelper.swift └── UserTests.swift ├── LICENCE ├── Podfile ├── Podfile.lock ├── Pods ├── Alamofire │ ├── LICENSE │ ├── README.md │ └── Source │ │ ├── AFError.swift │ │ ├── Alamofire.swift │ │ ├── DispatchQueue+Alamofire.swift │ │ ├── MultipartFormData.swift │ │ ├── NetworkReachabilityManager.swift │ │ ├── Notifications.swift │ │ ├── ParameterEncoding.swift │ │ ├── Request.swift │ │ ├── Response.swift │ │ ├── ResponseSerialization.swift │ │ ├── Result.swift │ │ ├── ServerTrustPolicy.swift │ │ ├── SessionDelegate.swift │ │ ├── SessionManager.swift │ │ ├── TaskDelegate.swift │ │ ├── Timeline.swift │ │ └── Validation.swift ├── Manifest.lock ├── Nocilla │ ├── LICENSE │ ├── Nocilla │ │ ├── Categories │ │ │ ├── NSData+Nocilla.h │ │ │ ├── NSData+Nocilla.m │ │ │ ├── NSString+Nocilla.h │ │ │ └── NSString+Nocilla.m │ │ ├── DSL │ │ │ ├── LSHTTPRequestDSLRepresentation.h │ │ │ ├── LSHTTPRequestDSLRepresentation.m │ │ │ ├── LSStubRequestDSL.h │ │ │ ├── LSStubRequestDSL.m │ │ │ ├── LSStubResponseDSL.h │ │ │ └── LSStubResponseDSL.m │ │ ├── Diff │ │ │ ├── LSHTTPRequestDiff.h │ │ │ └── LSHTTPRequestDiff.m │ │ ├── Hooks │ │ │ ├── ASIHTTPRequest │ │ │ │ ├── ASIHTTPRequestStub.h │ │ │ │ ├── ASIHTTPRequestStub.m │ │ │ │ ├── LSASIHTTPRequestAdapter.h │ │ │ │ ├── LSASIHTTPRequestAdapter.m │ │ │ │ ├── LSASIHTTPRequestHook.h │ │ │ │ └── LSASIHTTPRequestHook.m │ │ │ ├── LSHTTPClientHook.h │ │ │ ├── LSHTTPClientHook.m │ │ │ ├── NSURLRequest │ │ │ │ ├── LSHTTPStubURLProtocol.h │ │ │ │ ├── LSHTTPStubURLProtocol.m │ │ │ │ ├── LSNSURLHook.h │ │ │ │ ├── LSNSURLHook.m │ │ │ │ ├── NSURLRequest+DSL.h │ │ │ │ ├── NSURLRequest+DSL.m │ │ │ │ ├── NSURLRequest+LSHTTPRequest.h │ │ │ │ └── NSURLRequest+LSHTTPRequest.m │ │ │ └── NSURLSession │ │ │ │ ├── LSNSURLSessionHook.h │ │ │ │ └── LSNSURLSessionHook.m │ │ ├── LSNocilla.h │ │ ├── LSNocilla.m │ │ ├── Matchers │ │ │ ├── LSDataMatcher.h │ │ │ ├── LSDataMatcher.m │ │ │ ├── LSMatcheable.h │ │ │ ├── LSMatcher.h │ │ │ ├── LSMatcher.m │ │ │ ├── LSRegexMatcher.h │ │ │ ├── LSRegexMatcher.m │ │ │ ├── LSStringMatcher.h │ │ │ ├── LSStringMatcher.m │ │ │ ├── NSData+Matcheable.h │ │ │ ├── NSData+Matcheable.m │ │ │ ├── NSRegularExpression+Matcheable.h │ │ │ ├── NSRegularExpression+Matcheable.m │ │ │ ├── NSString+Matcheable.h │ │ │ └── NSString+Matcheable.m │ │ ├── Model │ │ │ ├── LSHTTPBody.h │ │ │ ├── LSHTTPRequest.h │ │ │ └── LSHTTPResponse.h │ │ ├── Nocilla.h │ │ └── Stubs │ │ │ ├── LSStubRequest.h │ │ │ ├── LSStubRequest.m │ │ │ ├── LSStubResponse.h │ │ │ └── LSStubResponse.m │ └── README.md ├── Pods.xcodeproj │ └── project.pbxproj └── Target Support Files │ ├── Alamofire │ ├── Alamofire-dummy.m │ ├── Alamofire-prefix.pch │ ├── Alamofire-umbrella.h │ ├── Alamofire.modulemap │ ├── Alamofire.xcconfig │ └── Info.plist │ ├── Nocilla │ ├── Info.plist │ ├── Nocilla-dummy.m │ ├── Nocilla-prefix.pch │ ├── Nocilla-umbrella.h │ ├── Nocilla.modulemap │ └── Nocilla.xcconfig │ ├── Pods-GithubPilot │ ├── Info.plist │ ├── Pods-GithubPilot-acknowledgements.markdown │ ├── Pods-GithubPilot-acknowledgements.plist │ ├── Pods-GithubPilot-dummy.m │ ├── Pods-GithubPilot-resources.sh │ ├── Pods-GithubPilot-umbrella.h │ ├── Pods-GithubPilot.debug.xcconfig │ ├── Pods-GithubPilot.modulemap │ └── Pods-GithubPilot.release.xcconfig │ └── Pods-GithubPilotTests │ ├── Info.plist │ ├── Pods-GithubPilotTests-acknowledgements.markdown │ ├── Pods-GithubPilotTests-acknowledgements.plist │ ├── Pods-GithubPilotTests-dummy.m │ ├── Pods-GithubPilotTests-frameworks.sh │ ├── Pods-GithubPilotTests-resources.sh │ ├── Pods-GithubPilotTests-umbrella.h │ ├── Pods-GithubPilotTests.debug.xcconfig │ ├── Pods-GithubPilotTests.modulemap │ └── Pods-GithubPilotTests.release.xcconfig ├── README.md └── Sources ├── Constants.swift ├── Github.swift ├── GithubClient.swift ├── GithubRequest.swift ├── GithubSerializers.swift ├── Helpers ├── Character+Extensions.swift ├── Dictionray+Extensions.swift └── Helper.swift ├── Models ├── GithubEvent.swift ├── GithubRepo.swift └── GithubUser.swift ├── OAuth.swift └── Routes ├── AuthenticationRoutes.swift ├── EventsRoutes.swift ├── ReposRoutes.swift ├── SearchRoutes.swift ├── StarsRoutes.swift └── UsersRoutes.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://github.com/pinterest/PINRemoteImage/blob/master/.travis.yml 2 | # https://github.com/Alamofire/Alamofire/blob/master/.travis.yml 3 | language: objective-c 4 | osx_image: xcode8 5 | env: 6 | global: 7 | - LC_CTYPE=en_US.UTF-8 8 | - LANG=en_US.UTF-8 9 | matrix: 10 | - SCHEME="GithubPilot" 11 | - IOS_SDK=iphonesimulator 12 | - DESTINATION="platform=iOS Simulator,name=iPhone 7, OS=latest" 13 | 14 | before_install: 15 | - gem install cocoapods --no-rdoc --no-ri --no-document --quiet 16 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 17 | 18 | script: 19 | - set -o pipefail 20 | - xcodebuild -version 21 | - xctool -workspace GithubPilot.xcworkspace -scheme "$SCHEME" -sdk "$IOS_SDK" -destination "$DESTINATION" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO 22 | # - xctool test -workspace GithubPilot.xcworkspace -scheme GithubPilotTests -sdk iphonesimulator9.2 ONLY_ACTIVE_ARCH=NO 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.3 - GithubPilot, Brand New](https://github.com/jindulys/GithubPilot/releases/tag/1.0.2) (2016-03-25) 2 | 3 | #### Add 4 | * Search API 5 | 6 | 7 | ## [1.0.2 - GithubPilot, Brand New](https://github.com/jindulys/GithubPilot/releases/tag/1.0.2) (2016-03-15) 8 | 9 | #### Add 10 | * Fetch full Information for an Array of API Users. 11 | 12 | #### Fix 13 | * Got rid of `precondition` to avoid crashing from SDK. 14 | * Optimized the use of Structure. 15 | -------------------------------------------------------------------------------- /GithubPilot.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.platform = :ios 3 | s.ios.deployment_target = '9.0' 4 | s.name = "GithubPilot" 5 | s.version = "1.1.11" 6 | s.summary = "Github API V3 Swifty Wrapper" 7 | s.description = <<-DESC 8 | A swift implementaion of Github API V3, make query to Github easier. 9 | DESC 10 | 11 | s.homepage = "https://github.com/jindulys/GithubPilot" 12 | s.license = { :type => "MIT", :file => "LICENCE" } 13 | 14 | 15 | s.author = { "yansong li" => "liyansong.edw@gmail.com" } 16 | 17 | s.source = { :git => "https://github.com/jindulys/GithubPilot.git", :tag => s.version } 18 | 19 | s.source_files = "Sources/**/*.*" 20 | s.requires_arc = true 21 | s.dependency 'Alamofire', '~> 4.0.0' 22 | s.ios.framework = "UIKit" 23 | end 24 | -------------------------------------------------------------------------------- /GithubPilot.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GithubPilot.xcodeproj/xcshareddata/xcschemes/GithubPilot.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /GithubPilot.xcodeproj/xcshareddata/xcschemes/GithubPilotTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /GithubPilot.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /GithubPilot/GithubPilot.h: -------------------------------------------------------------------------------- 1 | // 2 | // GithubPilot.h 3 | // GithubPilot 4 | // 5 | // Created by yansong li on 2016-02-20. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for GithubPilot. 12 | FOUNDATION_EXPORT double GithubPilotVersionNumber; 13 | 14 | //! Project version string for GithubPilot. 15 | FOUNDATION_EXPORT const unsigned char GithubPilotVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /GithubPilot/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | NSAppTransportSecurity 12 | 13 | NSAllowsArbitraryLoads 14 | 15 | 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | FMWK 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | $(CURRENT_PROJECT_VERSION) 28 | NSPrincipalClass 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /GithubPilot/README.md: -------------------------------------------------------------------------------- 1 | # GithubPilot 2 | Github API V3 Swifty Wrapper 3 | 4 | To install 5 | 6 | pod 'GithubPilot', '~>0.0.1' 7 | 8 | # IN PROGRESS, STAY TUNED 9 | -------------------------------------------------------------------------------- /GithubPilotTests/FakeData/repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 10824973, 3 | "name": "Test", 4 | "full_name": "mietzmithut/Test", 5 | "owner": { 6 | "login": "mietzmithut", 7 | "id": 4672699, 8 | "avatar_url": "https://avatars.githubusercontent.com/u/4672699?v=3", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/mietzmithut", 11 | "html_url": "https://github.com/mietzmithut", 12 | "followers_url": "https://api.github.com/users/mietzmithut/followers", 13 | "following_url": "https://api.github.com/users/mietzmithut/following{/other_user}", 14 | "gists_url": "https://api.github.com/users/mietzmithut/gists{/gist_id}", 15 | "starred_url": "https://api.github.com/users/mietzmithut/starred{/owner}{/repo}", 16 | "subscriptions_url": "https://api.github.com/users/mietzmithut/subscriptions", 17 | "organizations_url": "https://api.github.com/users/mietzmithut/orgs", 18 | "repos_url": "https://api.github.com/users/mietzmithut/repos", 19 | "events_url": "https://api.github.com/users/mietzmithut/events{/privacy}", 20 | "received_events_url": "https://api.github.com/users/mietzmithut/received_events", 21 | "type": "User", 22 | "site_admin": false 23 | }, 24 | "private": false, 25 | "html_url": "https://github.com/mietzmithut/Test", 26 | "description": "", 27 | "fork": false, 28 | "url": "https://api.github.com/repos/mietzmithut/Test", 29 | "forks_url": "https://api.github.com/repos/mietzmithut/Test/forks", 30 | "keys_url": "https://api.github.com/repos/mietzmithut/Test/keys{/key_id}", 31 | "collaborators_url": "https://api.github.com/repos/mietzmithut/Test/collaborators{/collaborator}", 32 | "teams_url": "https://api.github.com/repos/mietzmithut/Test/teams", 33 | "hooks_url": "https://api.github.com/repos/mietzmithut/Test/hooks", 34 | "issue_events_url": "https://api.github.com/repos/mietzmithut/Test/issues/events{/number}", 35 | "events_url": "https://api.github.com/repos/mietzmithut/Test/events", 36 | "assignees_url": "https://api.github.com/repos/mietzmithut/Test/assignees{/user}", 37 | "branches_url": "https://api.github.com/repos/mietzmithut/Test/branches{/branch}", 38 | "tags_url": "https://api.github.com/repos/mietzmithut/Test/tags", 39 | "blobs_url": "https://api.github.com/repos/mietzmithut/Test/git/blobs{/sha}", 40 | "git_tags_url": "https://api.github.com/repos/mietzmithut/Test/git/tags{/sha}", 41 | "git_refs_url": "https://api.github.com/repos/mietzmithut/Test/git/refs{/sha}", 42 | "trees_url": "https://api.github.com/repos/mietzmithut/Test/git/trees{/sha}", 43 | "statuses_url": "https://api.github.com/repos/mietzmithut/Test/statuses/{sha}", 44 | "languages_url": "https://api.github.com/repos/mietzmithut/Test/languages", 45 | "stargazers_url": "https://api.github.com/repos/mietzmithut/Test/stargazers", 46 | "contributors_url": "https://api.github.com/repos/mietzmithut/Test/contributors", 47 | "subscribers_url": "https://api.github.com/repos/mietzmithut/Test/subscribers", 48 | "subscription_url": "https://api.github.com/repos/mietzmithut/Test/subscription", 49 | "commits_url": "https://api.github.com/repos/mietzmithut/Test/commits{/sha}", 50 | "git_commits_url": "https://api.github.com/repos/mietzmithut/Test/git/commits{/sha}", 51 | "comments_url": "https://api.github.com/repos/mietzmithut/Test/comments{/number}", 52 | "issue_comment_url": "https://api.github.com/repos/mietzmithut/Test/issues/comments/{number}", 53 | "contents_url": "https://api.github.com/repos/mietzmithut/Test/contents/{+path}", 54 | "compare_url": "https://api.github.com/repos/mietzmithut/Test/compare/{base}...{head}", 55 | "merges_url": "https://api.github.com/repos/mietzmithut/Test/merges", 56 | "archive_url": "https://api.github.com/repos/mietzmithut/Test/{archive_format}{/ref}", 57 | "downloads_url": "https://api.github.com/repos/mietzmithut/Test/downloads", 58 | "issues_url": "https://api.github.com/repos/mietzmithut/Test/issues{/number}", 59 | "pulls_url": "https://api.github.com/repos/mietzmithut/Test/pulls{/number}", 60 | "milestones_url": "https://api.github.com/repos/mietzmithut/Test/milestones{/number}", 61 | "notifications_url": "https://api.github.com/repos/mietzmithut/Test/notifications{?since,all,participating}", 62 | "labels_url": "https://api.github.com/repos/mietzmithut/Test/labels{/name}", 63 | "releases_url": "https://api.github.com/repos/mietzmithut/Test/releases{/id}", 64 | "created_at": "2013-06-20T17:05:03Z", 65 | "updated_at": "2014-06-13T21:10:35Z", 66 | "pushed_at": "2013-06-20T20:04:46Z", 67 | "git_url": "git://github.com/mietzmithut/Test.git", 68 | "ssh_url": "git@github.com:mietzmithut/Test.git", 69 | "clone_url": "https://github.com/mietzmithut/Test.git", 70 | "svn_url": "https://github.com/mietzmithut/Test", 71 | "homepage": null, 72 | "size": 132, 73 | "stargazers_count": 0, 74 | "watchers_count": 0, 75 | "language": "Ruby", 76 | "has_issues": true, 77 | "has_downloads": true, 78 | "has_wiki": true, 79 | "has_pages": false, 80 | "forks_count": 0, 81 | "mirror_url": null, 82 | "open_issues_count": 0, 83 | "forks": 0, 84 | "open_issues": 0, 85 | "watchers": 0, 86 | "default_branch": "master", 87 | "network_count": 0, 88 | "subscribers_count": 2 89 | } -------------------------------------------------------------------------------- /GithubPilotTests/FakeData/user_me.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "pietbrauer", 3 | "id": 759730, 4 | "avatar_url": "https://avatars.githubusercontent.com/u/759730?v=3", 5 | "gravatar_id": "", 6 | "url": "https://api.github.com/users/pietbrauer", 7 | "html_url": "https://github.com/pietbrauer", 8 | "followers_url": "https://api.github.com/users/pietbrauer/followers", 9 | "following_url": "https://api.github.com/users/pietbrauer/following{/other_user}", 10 | "gists_url": "https://api.github.com/users/pietbrauer/gists{/gist_id}", 11 | "starred_url": "https://api.github.com/users/pietbrauer/starred{/owner}{/repo}", 12 | "subscriptions_url": "https://api.github.com/users/pietbrauer/subscriptions", 13 | "organizations_url": "https://api.github.com/users/pietbrauer/orgs", 14 | "repos_url": "https://api.github.com/users/pietbrauer/repos", 15 | "events_url": "https://api.github.com/users/pietbrauer/events{/privacy}", 16 | "received_events_url": "https://api.github.com/users/pietbrauer/received_events", 17 | "type": "User", 18 | "site_admin": false, 19 | "name": "Piet Brauer", 20 | "company": "XING AG", 21 | "blog": "xing.to/PietBrauer", 22 | "location": "Hamburg", 23 | "email": null, 24 | "hireable": true, 25 | "bio": null, 26 | "public_repos": 6, 27 | "public_gists": 10, 28 | "followers": 41, 29 | "following": 19, 30 | "created_at": "2011-04-29T20:58:36Z", 31 | "updated_at": "2015-01-12T19:42:23Z", 32 | "private_gists": 7, 33 | "total_private_repos": 4, 34 | "owned_private_repos": 4, 35 | "disk_usage": 49064, 36 | "collaborators": 2, 37 | "plan": { 38 | "name": "micro", 39 | "space": 614400, 40 | "collaborators": 0, 41 | "private_repos": 5 42 | } 43 | } -------------------------------------------------------------------------------- /GithubPilotTests/FakeData/user_mietzmithut.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "mietzmithut", 3 | "id": 4672699, 4 | "avatar_url": "https://avatars.githubusercontent.com/u/4672699?v=3", 5 | "gravatar_id": "", 6 | "url": "https://api.github.com/users/mietzmithut", 7 | "html_url": "https://github.com/mietzmithut", 8 | "followers_url": "https://api.github.com/users/mietzmithut/followers", 9 | "following_url": "https://api.github.com/users/mietzmithut/following{/other_user}", 10 | "gists_url": "https://api.github.com/users/mietzmithut/gists{/gist_id}", 11 | "starred_url": "https://api.github.com/users/mietzmithut/starred{/owner}{/repo}", 12 | "subscriptions_url": "https://api.github.com/users/mietzmithut/subscriptions", 13 | "organizations_url": "https://api.github.com/users/mietzmithut/orgs", 14 | "repos_url": "https://api.github.com/users/mietzmithut/repos", 15 | "events_url": "https://api.github.com/users/mietzmithut/events{/privacy}", 16 | "received_events_url": "https://api.github.com/users/mietzmithut/received_events", 17 | "type": "User", 18 | "site_admin": false, 19 | "name": "Julia Kallenberg", 20 | "company": "", 21 | "blog": "", 22 | "location": "Hamburg", 23 | "email": "", 24 | "hireable": false, 25 | "bio": null, 26 | "public_repos": 7, 27 | "public_gists": 0, 28 | "followers": 7, 29 | "following": 8, 30 | "created_at": "2013-06-11T18:02:51Z", 31 | "updated_at": "2014-12-22T18:53:41Z" 32 | } -------------------------------------------------------------------------------- /GithubPilotTests/FakeData/user_repos.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 10824973, 4 | "name": "Test", 5 | "full_name": "mietzmithut/Test", 6 | "owner": { 7 | "login": "mietzmithut", 8 | "id": 4672699, 9 | "avatar_url": "https://avatars.githubusercontent.com/u/4672699?v=3", 10 | "gravatar_id": "", 11 | "url": "https://api.github.com/users/mietzmithut", 12 | "html_url": "https://github.com/mietzmithut", 13 | "followers_url": "https://api.github.com/users/mietzmithut/followers", 14 | "following_url": "https://api.github.com/users/mietzmithut/following{/other_user}", 15 | "gists_url": "https://api.github.com/users/mietzmithut/gists{/gist_id}", 16 | "starred_url": "https://api.github.com/users/mietzmithut/starred{/owner}{/repo}", 17 | "subscriptions_url": "https://api.github.com/users/mietzmithut/subscriptions", 18 | "organizations_url": "https://api.github.com/users/mietzmithut/orgs", 19 | "repos_url": "https://api.github.com/users/mietzmithut/repos", 20 | "events_url": "https://api.github.com/users/mietzmithut/events{/privacy}", 21 | "received_events_url": "https://api.github.com/users/mietzmithut/received_events", 22 | "type": "User", 23 | "site_admin": false 24 | }, 25 | "private": false, 26 | "html_url": "https://github.com/mietzmithut/Test", 27 | "description": "", 28 | "fork": false, 29 | "url": "https://api.github.com/repos/mietzmithut/Test", 30 | "forks_url": "https://api.github.com/repos/mietzmithut/Test/forks", 31 | "keys_url": "https://api.github.com/repos/mietzmithut/Test/keys{/key_id}", 32 | "collaborators_url": "https://api.github.com/repos/mietzmithut/Test/collaborators{/collaborator}", 33 | "teams_url": "https://api.github.com/repos/mietzmithut/Test/teams", 34 | "hooks_url": "https://api.github.com/repos/mietzmithut/Test/hooks", 35 | "issue_events_url": "https://api.github.com/repos/mietzmithut/Test/issues/events{/number}", 36 | "events_url": "https://api.github.com/repos/mietzmithut/Test/events", 37 | "assignees_url": "https://api.github.com/repos/mietzmithut/Test/assignees{/user}", 38 | "branches_url": "https://api.github.com/repos/mietzmithut/Test/branches{/branch}", 39 | "tags_url": "https://api.github.com/repos/mietzmithut/Test/tags", 40 | "blobs_url": "https://api.github.com/repos/mietzmithut/Test/git/blobs{/sha}", 41 | "git_tags_url": "https://api.github.com/repos/mietzmithut/Test/git/tags{/sha}", 42 | "git_refs_url": "https://api.github.com/repos/mietzmithut/Test/git/refs{/sha}", 43 | "trees_url": "https://api.github.com/repos/mietzmithut/Test/git/trees{/sha}", 44 | "statuses_url": "https://api.github.com/repos/mietzmithut/Test/statuses/{sha}", 45 | "languages_url": "https://api.github.com/repos/mietzmithut/Test/languages", 46 | "stargazers_url": "https://api.github.com/repos/mietzmithut/Test/stargazers", 47 | "contributors_url": "https://api.github.com/repos/mietzmithut/Test/contributors", 48 | "subscribers_url": "https://api.github.com/repos/mietzmithut/Test/subscribers", 49 | "subscription_url": "https://api.github.com/repos/mietzmithut/Test/subscription", 50 | "commits_url": "https://api.github.com/repos/mietzmithut/Test/commits{/sha}", 51 | "git_commits_url": "https://api.github.com/repos/mietzmithut/Test/git/commits{/sha}", 52 | "comments_url": "https://api.github.com/repos/mietzmithut/Test/comments{/number}", 53 | "issue_comment_url": "https://api.github.com/repos/mietzmithut/Test/issues/comments/{number}", 54 | "contents_url": "https://api.github.com/repos/mietzmithut/Test/contents/{+path}", 55 | "compare_url": "https://api.github.com/repos/mietzmithut/Test/compare/{base}...{head}", 56 | "merges_url": "https://api.github.com/repos/mietzmithut/Test/merges", 57 | "archive_url": "https://api.github.com/repos/mietzmithut/Test/{archive_format}{/ref}", 58 | "downloads_url": "https://api.github.com/repos/mietzmithut/Test/downloads", 59 | "issues_url": "https://api.github.com/repos/mietzmithut/Test/issues{/number}", 60 | "pulls_url": "https://api.github.com/repos/mietzmithut/Test/pulls{/number}", 61 | "milestones_url": "https://api.github.com/repos/mietzmithut/Test/milestones{/number}", 62 | "notifications_url": "https://api.github.com/repos/mietzmithut/Test/notifications{?since,all,participating}", 63 | "labels_url": "https://api.github.com/repos/mietzmithut/Test/labels{/name}", 64 | "releases_url": "https://api.github.com/repos/mietzmithut/Test/releases{/id}", 65 | "created_at": "2013-06-20T17:05:03Z", 66 | "updated_at": "2014-06-13T21:10:35Z", 67 | "pushed_at": "2013-06-20T20:04:46Z", 68 | "git_url": "git://github.com/mietzmithut/Test.git", 69 | "ssh_url": "git@github.com:mietzmithut/Test.git", 70 | "clone_url": "https://github.com/mietzmithut/Test.git", 71 | "svn_url": "https://github.com/mietzmithut/Test", 72 | "homepage": null, 73 | "size": 132, 74 | "stargazers_count": 0, 75 | "watchers_count": 0, 76 | "language": "Ruby", 77 | "has_issues": true, 78 | "has_downloads": true, 79 | "has_wiki": true, 80 | "has_pages": false, 81 | "forks_count": 0, 82 | "mirror_url": null, 83 | "open_issues_count": 0, 84 | "forks": 0, 85 | "open_issues": 0, 86 | "watchers": 0, 87 | "default_branch": "master", 88 | "permissions": { 89 | "admin": false, 90 | "push": true, 91 | "pull": true 92 | } 93 | } 94 | ] -------------------------------------------------------------------------------- /GithubPilotTests/FakeData/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "login": "pietbrauer", 4 | "id": 759730, 5 | "avatar_url": "https://avatars.githubusercontent.com/u/759730?v=3", 6 | "gravatar_id": "", 7 | "url": "https://api.github.com/users/pietbrauer", 8 | "html_url": "https://github.com/pietbrauer", 9 | "followers_url": "https://api.github.com/users/pietbrauer/followers", 10 | "following_url": "https://api.github.com/users/pietbrauer/following{/other_user}", 11 | "gists_url": "https://api.github.com/users/pietbrauer/gists{/gist_id}", 12 | "starred_url": "https://api.github.com/users/pietbrauer/starred{/owner}{/repo}", 13 | "subscriptions_url": "https://api.github.com/users/pietbrauer/subscriptions", 14 | "organizations_url": "https://api.github.com/users/pietbrauer/orgs", 15 | "repos_url": "https://api.github.com/users/pietbrauer/repos", 16 | "events_url": "https://api.github.com/users/pietbrauer/events{/privacy}", 17 | "received_events_url": "https://api.github.com/users/pietbrauer/received_events", 18 | "type": "User", 19 | "site_admin": false, 20 | "name": "Piet Brauer", 21 | "company": "XING AG", 22 | "blog": "xing.to/PietBrauer", 23 | "location": "Hamburg", 24 | "email": null, 25 | "hireable": true, 26 | "bio": null, 27 | "public_repos": 6, 28 | "public_gists": 10, 29 | "followers": 41, 30 | "following": 19, 31 | "created_at": "2011-04-29T20:58:36Z", 32 | "updated_at": "2015-01-12T19:42:23Z", 33 | "private_gists": 7, 34 | "total_private_repos": 4, 35 | "owned_private_repos": 4, 36 | "disk_usage": 49064, 37 | "collaborators": 2, 38 | "plan": { 39 | "name": "micro", 40 | "space": 614400, 41 | "collaborators": 0, 42 | "private_repos": 5 43 | } 44 | } 45 | ] -------------------------------------------------------------------------------- /GithubPilotTests/GithubPilotTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /GithubPilotTests/GithubPilotTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GithubPilotTests.swift 3 | // GithubPilotTests 4 | // 5 | // Created by yansong li on 2016-02-20. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import GithubPilot 11 | 12 | class GithubPilotTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | func testZeroBaseUnicodeScalarValue() { 37 | let testString = "9" 38 | let testCharacter = testString.characters[testString.startIndex] 39 | XCTAssert(testCharacter.zeroCharacterBasedunicodeScalarCodePoint() == 9) 40 | 41 | let testStringZero = "0" 42 | let testCharacterZero = testStringZero.characters[testString.startIndex] 43 | XCTAssert(testCharacterZero.zeroCharacterBasedunicodeScalarCodePoint() == 0) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /GithubPilotTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | NSAppTransportSecurity 14 | 15 | NSAllowsArbitraryLoads 16 | 17 | 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | BNDL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | 29 | 30 | -------------------------------------------------------------------------------- /GithubPilotTests/StarGazersTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StarGazersTest.swift 3 | // GithubPilot 4 | // 5 | // Created by yansong li on 2016-02-24. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import XCTest 12 | @testable import GithubPilot 13 | import Alamofire 14 | 15 | class StarGazersTests: XCTestCase { 16 | var testClient: GithubNetWorkClient! 17 | var testStarsRoutes: StarsRoutes! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | 22 | let configuration = URLSessionConfiguration.default 23 | configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders 24 | let manager = Alamofire.SessionManager(configuration:configuration) 25 | manager.startRequestsImmediately = false 26 | testClient = GithubNetWorkClient(manager: manager, 27 | baseHosts: ["api": "https://api.github.com"]) 28 | testStarsRoutes = StarsRoutes(client: testClient) 29 | } 30 | 31 | override func tearDown() { 32 | super.tearDown() 33 | } 34 | 35 | func testWrongParameterStarGazerRequest() { 36 | 37 | let username = "jindulys" 38 | 39 | let expectation = self.expectation(description: "WrongParameter") 40 | testStarsRoutes.getAllStargazersFor(repo: "Hackerlala", owner: username) { 41 | result, error in 42 | if let error = error { 43 | XCTAssertEqual(error, "Bad Request - Code: 404 : Not Found") 44 | expectation.fulfill() 45 | } else { 46 | XCTAssert(false, "Should be error") 47 | expectation.fulfill() 48 | } 49 | } 50 | waitForExpectations(timeout: 5) { (error) in 51 | XCTAssertNil(error, "\(error)") 52 | } 53 | } 54 | 55 | func testStarGazerCountRequest() { 56 | 57 | let username = "jindulys" 58 | 59 | let expectation = self.expectation(description: "StarGazerCount") 60 | testStarsRoutes.getAllStargazersFor(repo: "HackerRankSolutions", owner: username) { 61 | result, error in 62 | if let _ = error { 63 | XCTAssert(false, "Failed Test") 64 | expectation.fulfill() 65 | } else { 66 | if let followers = result { 67 | XCTAssert(followers.count > 100) 68 | } 69 | expectation.fulfill() 70 | } 71 | } 72 | waitForExpectations(timeout: 20) { (error) in 73 | XCTAssertNil(error, "\(error)") 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /GithubPilotTests/TestHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestHelper.swift 3 | // GithubPilot 4 | // 5 | // Created by yansong li on 2016-02-20. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | 10 | // Get From: https://github.com/nerdishbynature/octokit.swift 11 | import Foundation 12 | 13 | internal class Helper { 14 | internal class func stringFromFile(_ name: String) -> String? { 15 | let bundle = Bundle(for: self) 16 | let path = bundle.path(forResource: name, ofType: "json") 17 | if let path = path { 18 | let string = try? String(contentsOfFile: path, encoding: String.Encoding.utf8) 19 | return string 20 | } 21 | return nil 22 | } 23 | 24 | internal class func JSONDataFromFile(_ name: String) -> Data? { 25 | let bundle = Bundle(for: self) 26 | let path = bundle.path(forResource: name, ofType: "json")! 27 | let data = try? Data(contentsOf: URL(fileURLWithPath: path)) 28 | return data 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /GithubPilotTests/UserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserTests.swift 3 | // GithubPilot 4 | // 5 | // Created by yansong li on 2016-02-20. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import XCTest 12 | @testable import GithubPilot 13 | import Alamofire 14 | 15 | class UserTests: XCTestCase { 16 | var testClient: GithubNetWorkClient! 17 | var testUserRoutes: UsersRoutes! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | 22 | let configuration = URLSessionConfiguration.default 23 | configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders 24 | let manager = Alamofire.SessionManager(configuration:configuration) 25 | manager.startRequestsImmediately = false 26 | testClient = GithubNetWorkClient(manager: manager, 27 | baseHosts: ["api": "https://api.github.com"]) 28 | testUserRoutes = UsersRoutes(client: testClient) 29 | } 30 | 31 | override func tearDown() { 32 | super.tearDown() 33 | } 34 | 35 | func testReadingUserURLRequest() { 36 | 37 | let username = "jindulys" 38 | 39 | let expectation = self.expectation(description: "\(username)") 40 | testUserRoutes.getUser(username: username).response({ (result, error) -> Void in 41 | if let user = result { 42 | print(user.name) 43 | print(user.htmlURL) 44 | XCTAssertEqual(user.login, username) 45 | expectation.fulfill() 46 | } 47 | 48 | if let rerror = error { 49 | XCTAssert(false, "Error \(rerror.description)") 50 | expectation.fulfill() 51 | } 52 | }) 53 | 54 | waitForExpectations(timeout: 5) { (error) in 55 | XCTAssertNil(error, "\(error)") 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 YANSONG LI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '8.0' 3 | # Uncomment this line if you're using Swift 4 | use_frameworks! 5 | 6 | target 'GithubPilot' do 7 | pod 'Alamofire', '~> 4.0' 8 | end 9 | 10 | target 'GithubPilotTests' do 11 | pod 'Nocilla' 12 | end 13 | 14 | post_install do |installer| 15 | installer.pods_project.targets.each do |target| 16 | target.build_configurations.each do |config| 17 | config.build_settings['SWIFT_VERSION'] = '3.0' 18 | config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = 'YES' 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.0.1) 3 | - Nocilla (0.11.0) 4 | 5 | DEPENDENCIES: 6 | - Alamofire (~> 4.0) 7 | - Nocilla 8 | 9 | SPEC CHECKSUMS: 10 | Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 11 | Nocilla: 7af7a386071150cc8aa5da4da97d060f049dd61c 12 | 13 | PODFILE CHECKSUM: 1151873ca50cd5df9c5a22014903d6e8551e4145 14 | 15 | COCOAPODS: 1.1.0.rc.2 16 | -------------------------------------------------------------------------------- /Pods/Alamofire/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/DispatchQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | extension DispatchQueue { 29 | static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) } 30 | static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) } 31 | static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) } 32 | static var background: DispatchQueue { return DispatchQueue.global(qos: .background) } 33 | 34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { 35 | asyncAfter(deadline: .now() + delay, execute: closure) 36 | } 37 | 38 | func syncResult(_ closure: () -> T) -> T { 39 | var result: T! 40 | sync { result = closure() } 41 | return result 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Notification.Name { 28 | /// Used as a namespace for all `URLSessionTask` related notifications. 29 | public struct Task { 30 | /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. 31 | public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") 32 | 33 | /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. 34 | public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") 35 | 36 | /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. 37 | public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") 38 | 39 | /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. 40 | public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") 41 | } 42 | } 43 | 44 | // MARK: - 45 | 46 | extension Notification { 47 | /// Used as a namespace for all `Notification` user info dictionary keys. 48 | public struct Key { 49 | /// User info dictionary key representing the `URLSessionTask` associated with the notification. 50 | public static let Task = "org.alamofire.notification.key.task" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Used to represent whether a request was successful or encountered an error. 28 | /// 29 | /// - success: The request and all post processing operations were successful resulting in the serialization of the 30 | /// provided associated value. 31 | /// 32 | /// - failure: The request encountered an error resulting in a failure. The associated values are the original data 33 | /// provided by the server as well as the error that caused the failure. 34 | public enum Result { 35 | case success(Value) 36 | case failure(Error) 37 | 38 | /// Returns `true` if the result is a success, `false` otherwise. 39 | public var isSuccess: Bool { 40 | switch self { 41 | case .success: 42 | return true 43 | case .failure: 44 | return false 45 | } 46 | } 47 | 48 | /// Returns `true` if the result is a failure, `false` otherwise. 49 | public var isFailure: Bool { 50 | return !isSuccess 51 | } 52 | 53 | /// Returns the associated value if the result is a success, `nil` otherwise. 54 | public var value: Value? { 55 | switch self { 56 | case .success(let value): 57 | return value 58 | case .failure: 59 | return nil 60 | } 61 | } 62 | 63 | /// Returns the associated error value if the result is a failure, `nil` otherwise. 64 | public var error: Error? { 65 | switch self { 66 | case .success: 67 | return nil 68 | case .failure(let error): 69 | return error 70 | } 71 | } 72 | } 73 | 74 | // MARK: - CustomStringConvertible 75 | 76 | extension Result: CustomStringConvertible { 77 | /// The textual representation used when written to an output stream, which includes whether the result was a 78 | /// success or failure. 79 | public var description: String { 80 | switch self { 81 | case .success: 82 | return "SUCCESS" 83 | case .failure: 84 | return "FAILURE" 85 | } 86 | } 87 | } 88 | 89 | // MARK: - CustomDebugStringConvertible 90 | 91 | extension Result: CustomDebugStringConvertible { 92 | /// The debug textual representation used when written to an output stream, which includes whether the result was a 93 | /// success or failure in addition to the value or error. 94 | public var debugDescription: String { 95 | switch self { 96 | case .success(let value): 97 | return "SUCCESS: \(value)" 98 | case .failure(let error): 99 | return "FAILURE: \(error)" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Timeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timeline.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Responsible for computing the timing metrics for the complete lifecycle of a `Request`. 28 | public struct Timeline { 29 | /// The time the request was initialized. 30 | public let requestStartTime: CFAbsoluteTime 31 | 32 | /// The time the first bytes were received from or sent to the server. 33 | public let initialResponseTime: CFAbsoluteTime 34 | 35 | /// The time when the request was completed. 36 | public let requestCompletedTime: CFAbsoluteTime 37 | 38 | /// The time when the response serialization was completed. 39 | public let serializationCompletedTime: CFAbsoluteTime 40 | 41 | /// The time interval in seconds from the time the request started to the initial response from the server. 42 | public let latency: TimeInterval 43 | 44 | /// The time interval in seconds from the time the request started to the time the request completed. 45 | public let requestDuration: TimeInterval 46 | 47 | /// The time interval in seconds from the time the request completed to the time response serialization completed. 48 | public let serializationDuration: TimeInterval 49 | 50 | /// The time interval in seconds from the time the request started to the time response serialization completed. 51 | public let totalDuration: TimeInterval 52 | 53 | /// Creates a new `Timeline` instance with the specified request times. 54 | /// 55 | /// - parameter requestStartTime: The time the request was initialized. Defaults to `0.0`. 56 | /// - parameter initialResponseTime: The time the first bytes were received from or sent to the server. 57 | /// Defaults to `0.0`. 58 | /// - parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`. 59 | /// - parameter serializationCompletedTime: The time when the response serialization was completed. Defaults 60 | /// to `0.0`. 61 | /// 62 | /// - returns: The new `Timeline` instance. 63 | public init( 64 | requestStartTime: CFAbsoluteTime = 0.0, 65 | initialResponseTime: CFAbsoluteTime = 0.0, 66 | requestCompletedTime: CFAbsoluteTime = 0.0, 67 | serializationCompletedTime: CFAbsoluteTime = 0.0) 68 | { 69 | self.requestStartTime = requestStartTime 70 | self.initialResponseTime = initialResponseTime 71 | self.requestCompletedTime = requestCompletedTime 72 | self.serializationCompletedTime = serializationCompletedTime 73 | 74 | self.latency = initialResponseTime - requestStartTime 75 | self.requestDuration = requestCompletedTime - requestStartTime 76 | self.serializationDuration = serializationCompletedTime - requestCompletedTime 77 | self.totalDuration = serializationCompletedTime - requestStartTime 78 | } 79 | } 80 | 81 | // MARK: - CustomStringConvertible 82 | 83 | extension Timeline: CustomStringConvertible { 84 | /// The textual representation used when written to an output stream, which includes the latency, the request 85 | /// duration and the total duration. 86 | public var description: String { 87 | let latency = String(format: "%.3f", self.latency) 88 | let requestDuration = String(format: "%.3f", self.requestDuration) 89 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 90 | let totalDuration = String(format: "%.3f", self.totalDuration) 91 | 92 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 93 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 94 | let timings = [ 95 | "\"Latency\": " + latency + " secs", 96 | "\"Request Duration\": " + requestDuration + " secs", 97 | "\"Serialization Duration\": " + serializationDuration + " secs", 98 | "\"Total Duration\": " + totalDuration + " secs" 99 | ] 100 | 101 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 102 | } 103 | } 104 | 105 | // MARK: - CustomDebugStringConvertible 106 | 107 | extension Timeline: CustomDebugStringConvertible { 108 | /// The textual representation used when written to an output stream, which includes the request start time, the 109 | /// initial response time, the request completed time, the serialization completed time, the latency, the request 110 | /// duration and the total duration. 111 | public var debugDescription: String { 112 | let requestStartTime = String(format: "%.3f", self.requestStartTime) 113 | let initialResponseTime = String(format: "%.3f", self.initialResponseTime) 114 | let requestCompletedTime = String(format: "%.3f", self.requestCompletedTime) 115 | let serializationCompletedTime = String(format: "%.3f", self.serializationCompletedTime) 116 | let latency = String(format: "%.3f", self.latency) 117 | let requestDuration = String(format: "%.3f", self.requestDuration) 118 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 119 | let totalDuration = String(format: "%.3f", self.totalDuration) 120 | 121 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 122 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 123 | let timings = [ 124 | "\"Request Start Time\": " + requestStartTime, 125 | "\"Initial Response Time\": " + initialResponseTime, 126 | "\"Request Completed Time\": " + requestCompletedTime, 127 | "\"Serialization Completed Time\": " + serializationCompletedTime, 128 | "\"Latency\": " + latency + " secs", 129 | "\"Request Duration\": " + requestDuration + " secs", 130 | "\"Serialization Duration\": " + serializationDuration + " secs", 131 | "\"Total Duration\": " + totalDuration + " secs" 132 | ] 133 | 134 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.0.1) 3 | - Nocilla (0.11.0) 4 | 5 | DEPENDENCIES: 6 | - Alamofire (~> 4.0) 7 | - Nocilla 8 | 9 | SPEC CHECKSUMS: 10 | Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 11 | Nocilla: 7af7a386071150cc8aa5da4da97d060f049dd61c 12 | 13 | PODFILE CHECKSUM: 1151873ca50cd5df9c5a22014903d6e8551e4145 14 | 15 | COCOAPODS: 1.1.0.rc.2 16 | -------------------------------------------------------------------------------- /Pods/Nocilla/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Luis Solano Bonet 2 | MIT License 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Categories/NSData+Nocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPBody.h" 3 | 4 | @interface NSData (Nocilla) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Categories/NSData+Nocilla.m: -------------------------------------------------------------------------------- 1 | #import "NSData+Nocilla.h" 2 | 3 | @implementation NSData (Nocilla) 4 | 5 | - (NSData *)data { 6 | return self; 7 | } 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Categories/NSString+Nocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPBody.h" 3 | 4 | @interface NSString (Nocilla) 5 | 6 | - (NSRegularExpression *)regex; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Categories/NSString+Nocilla.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Nocilla.h" 2 | 3 | @implementation NSString (Nocilla) 4 | 5 | - (NSRegularExpression *)regex { 6 | NSError *error = nil; 7 | NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:self options:0 error:&error]; 8 | if (error) { 9 | [NSException raise:NSInvalidArgumentException format:@"Invalid regex pattern: %@\nError: %@", self, error]; 10 | } 11 | return regex; 12 | } 13 | 14 | - (NSData *)data { 15 | return [self dataUsingEncoding:NSUTF8StringEncoding]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/DSL/LSHTTPRequestDSLRepresentation.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface LSHTTPRequestDSLRepresentation : NSObject 5 | - (id)initWithRequest:(id)request; 6 | @end 7 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/DSL/LSHTTPRequestDSLRepresentation.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPRequestDSLRepresentation.h" 2 | 3 | @interface LSHTTPRequestDSLRepresentation () 4 | @property (nonatomic, strong) id request; 5 | @end 6 | 7 | @implementation LSHTTPRequestDSLRepresentation 8 | - (id)initWithRequest:(id)request { 9 | self = [super init]; 10 | if (self) { 11 | _request = request; 12 | } 13 | return self; 14 | } 15 | 16 | - (NSString *)description { 17 | NSMutableString *result = [NSMutableString stringWithFormat:@"stubRequest(@\"%@\", @\"%@\")", self.request.method, [self.request.url absoluteString]]; 18 | if (self.request.headers.count) { 19 | [result appendString:@".\nwithHeaders(@{ "]; 20 | NSMutableArray *headerElements = [NSMutableArray arrayWithCapacity:self.request.headers.count]; 21 | 22 | NSArray *descriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"" ascending:YES]]; 23 | NSArray * sortedHeaders = [[self.request.headers allKeys] sortedArrayUsingDescriptors:descriptors]; 24 | 25 | for (NSString * header in sortedHeaders) { 26 | NSString *value = [self.request.headers objectForKey:header]; 27 | [headerElements addObject:[NSString stringWithFormat:@"@\"%@\": @\"%@\"", header, value]]; 28 | } 29 | [result appendString:[headerElements componentsJoinedByString:@", "]]; 30 | [result appendString:@" })"]; 31 | } 32 | if (self.request.body.length) { 33 | NSString *escapedBody = [[NSString alloc] initWithData:self.request.body encoding:NSUTF8StringEncoding]; 34 | escapedBody = [escapedBody stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 35 | [result appendFormat:@".\nwithBody(@\"%@\")", escapedBody]; 36 | } 37 | return [NSString stringWithFormat:@"%@;", result]; 38 | } 39 | @end 40 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/DSL/LSStubRequestDSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "NSString+Matcheable.h" 3 | #import "NSRegularExpression+Matcheable.h" 4 | #import "NSData+Matcheable.h" 5 | 6 | @class LSStubRequestDSL; 7 | @class LSStubResponseDSL; 8 | @class LSStubRequest; 9 | 10 | @protocol LSHTTPBody; 11 | 12 | typedef LSStubRequestDSL *(^WithHeaderMethod)(NSString *, NSString *); 13 | typedef LSStubRequestDSL *(^WithHeadersMethod)(NSDictionary *); 14 | typedef LSStubRequestDSL *(^AndBodyMethod)(id); 15 | typedef LSStubResponseDSL *(^AndReturnMethod)(NSInteger); 16 | typedef LSStubResponseDSL *(^AndReturnRawResponseMethod)(NSData *rawResponseData); 17 | typedef void (^AndFailWithErrorMethod)(NSError *error); 18 | 19 | @interface LSStubRequestDSL : NSObject 20 | - (id)initWithRequest:(LSStubRequest *)request; 21 | 22 | @property (nonatomic, strong, readonly) WithHeaderMethod withHeader; 23 | @property (nonatomic, strong, readonly) WithHeadersMethod withHeaders; 24 | @property (nonatomic, strong, readonly) AndBodyMethod withBody; 25 | @property (nonatomic, strong, readonly) AndReturnMethod andReturn; 26 | @property (nonatomic, strong, readonly) AndReturnRawResponseMethod andReturnRawResponse; 27 | @property (nonatomic, strong, readonly) AndFailWithErrorMethod andFailWithError; 28 | 29 | @end 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | LSStubRequestDSL * stubRequest(NSString *method, id url); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/DSL/LSStubRequestDSL.m: -------------------------------------------------------------------------------- 1 | #import "LSStubRequestDSL.h" 2 | #import "LSStubResponseDSL.h" 3 | #import "LSStubRequest.h" 4 | #import "LSNocilla.h" 5 | 6 | @interface LSStubRequestDSL () 7 | @property (nonatomic, strong) LSStubRequest *request; 8 | @end 9 | 10 | @implementation LSStubRequestDSL 11 | 12 | - (id)initWithRequest:(LSStubRequest *)request { 13 | self = [super init]; 14 | if (self) { 15 | _request = request; 16 | } 17 | return self; 18 | } 19 | - (WithHeadersMethod)withHeaders { 20 | return ^(NSDictionary *headers) { 21 | for (NSString *header in headers) { 22 | NSString *value = [headers objectForKey:header]; 23 | [self.request setHeader:header value:value]; 24 | } 25 | return self; 26 | }; 27 | } 28 | 29 | - (WithHeaderMethod)withHeader { 30 | return ^(NSString * header, NSString * value) { 31 | [self.request setHeader:header value:value]; 32 | return self; 33 | }; 34 | } 35 | 36 | - (AndBodyMethod)withBody { 37 | return ^(id body) { 38 | self.request.body = body.matcher; 39 | return self; 40 | }; 41 | } 42 | 43 | - (AndReturnMethod)andReturn { 44 | return ^(NSInteger statusCode) { 45 | self.request.response = [[LSStubResponse alloc] initWithStatusCode:statusCode]; 46 | LSStubResponseDSL *responseDSL = [[LSStubResponseDSL alloc] initWithResponse:self.request.response]; 47 | return responseDSL; 48 | }; 49 | } 50 | 51 | - (AndReturnRawResponseMethod)andReturnRawResponse { 52 | return ^(NSData *rawResponseData) { 53 | self.request.response = [[LSStubResponse alloc] initWithRawResponse:rawResponseData]; 54 | LSStubResponseDSL *responseDSL = [[LSStubResponseDSL alloc] initWithResponse:self.request.response]; 55 | return responseDSL; 56 | }; 57 | } 58 | 59 | - (AndFailWithErrorMethod)andFailWithError { 60 | return ^(NSError *error) { 61 | self.request.response = [[LSStubResponse alloc] initWithError:error]; 62 | }; 63 | } 64 | 65 | @end 66 | 67 | LSStubRequestDSL * stubRequest(NSString *method, id url) { 68 | LSStubRequest *request = [[LSStubRequest alloc] initWithMethod:method urlMatcher:url.matcher]; 69 | LSStubRequestDSL *dsl = [[LSStubRequestDSL alloc] initWithRequest:request]; 70 | [[LSNocilla sharedInstance] addStubbedRequest:request]; 71 | return dsl; 72 | } 73 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/DSL/LSStubResponseDSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class LSStubResponse; 4 | @class LSStubResponseDSL; 5 | 6 | @protocol LSHTTPBody; 7 | 8 | typedef LSStubResponseDSL *(^ResponseWithBodyMethod)(id); 9 | typedef LSStubResponseDSL *(^ResponseWithHeaderMethod)(NSString *, NSString *); 10 | typedef LSStubResponseDSL *(^ResponseWithHeadersMethod)(NSDictionary *); 11 | 12 | @interface LSStubResponseDSL : NSObject 13 | - (id)initWithResponse:(LSStubResponse *)response; 14 | 15 | @property (nonatomic, strong, readonly) ResponseWithHeaderMethod withHeader; 16 | @property (nonatomic, strong, readonly) ResponseWithHeadersMethod withHeaders; 17 | @property (nonatomic, strong, readonly) ResponseWithBodyMethod withBody; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/DSL/LSStubResponseDSL.m: -------------------------------------------------------------------------------- 1 | #import "LSStubResponseDSL.h" 2 | #import "LSStubResponse.h" 3 | #import "LSHTTPBody.h" 4 | 5 | @interface LSStubResponseDSL () 6 | @property (nonatomic, strong) LSStubResponse *response; 7 | @end 8 | 9 | @implementation LSStubResponseDSL 10 | - (id)initWithResponse:(LSStubResponse *)response { 11 | self = [super init]; 12 | if (self) { 13 | _response = response; 14 | } 15 | return self; 16 | } 17 | - (ResponseWithHeaderMethod)withHeader { 18 | return ^(NSString * header, NSString * value) { 19 | [self.response setHeader:header value:value]; 20 | return self; 21 | }; 22 | } 23 | 24 | - (ResponseWithHeadersMethod)withHeaders; { 25 | return ^(NSDictionary *headers) { 26 | for (NSString *header in headers) { 27 | NSString *value = [headers objectForKey:header]; 28 | [self.response setHeader:header value:value]; 29 | } 30 | return self; 31 | }; 32 | } 33 | 34 | - (ResponseWithBodyMethod)withBody { 35 | return ^(id body) { 36 | self.response.body = [body data]; 37 | return self; 38 | }; 39 | } 40 | @end 41 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Diff/LSHTTPRequestDiff.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface LSHTTPRequestDiff : NSObject 5 | @property (nonatomic, assign, readonly, getter = isEmpty) BOOL empty; 6 | 7 | - (id)initWithRequest:(id)oneRequest andRequest:(id)anotherRequest; 8 | @end 9 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Diff/LSHTTPRequestDiff.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPRequestDiff.h" 2 | 3 | @interface LSHTTPRequestDiff () 4 | @property (nonatomic, strong) idoneRequest; 5 | @property (nonatomic, strong) idanotherRequest; 6 | 7 | - (BOOL)isMethodDifferent; 8 | - (BOOL)isUrlDifferent; 9 | - (BOOL)areHeadersDifferent; 10 | - (BOOL)isBodyDifferent; 11 | 12 | - (void)appendMethodDiff:(NSMutableString *)diff; 13 | - (void)appendUrlDiff:(NSMutableString *)diff; 14 | - (void)appendHeadersDiff:(NSMutableString *)diff; 15 | - (void)appendBodyDiff:(NSMutableString *)diff; 16 | @end 17 | 18 | @implementation LSHTTPRequestDiff 19 | - (id)initWithRequest:(id)oneRequest andRequest:(id)anotherRequest { 20 | self = [super init]; 21 | if (self) { 22 | _oneRequest = oneRequest; 23 | _anotherRequest = anotherRequest; 24 | } 25 | return self; 26 | } 27 | 28 | - (BOOL)isEmpty { 29 | if ([self isMethodDifferent] || 30 | [self isUrlDifferent] || 31 | [self areHeadersDifferent] || 32 | [self isBodyDifferent]) { 33 | return NO; 34 | } 35 | return YES; 36 | } 37 | 38 | - (NSString *)description { 39 | NSMutableString *diff = [@"" mutableCopy]; 40 | if ([self isMethodDifferent]) { 41 | [self appendMethodDiff:diff]; 42 | } 43 | if ([self isUrlDifferent]) { 44 | [self appendUrlDiff:diff]; 45 | } 46 | if([self areHeadersDifferent]) { 47 | [self appendHeadersDiff:diff]; 48 | } 49 | if([self isBodyDifferent]) { 50 | [self appendBodyDiff:diff]; 51 | } 52 | return [NSString stringWithString:diff]; 53 | } 54 | 55 | #pragma mark - Private Methods 56 | - (BOOL)isMethodDifferent { 57 | return ![self.oneRequest.method isEqualToString:self.anotherRequest.method]; 58 | } 59 | 60 | - (BOOL)isUrlDifferent { 61 | return ![self.oneRequest.url isEqual:self.anotherRequest.url]; 62 | } 63 | 64 | - (BOOL)areHeadersDifferent { 65 | return ![self.oneRequest.headers isEqual:self.anotherRequest.headers]; 66 | } 67 | 68 | - (BOOL)isBodyDifferent { 69 | return (((self.oneRequest.body) && (![self.oneRequest.body isEqual:self.anotherRequest.body])) || 70 | ((self.anotherRequest.body) && (![self.anotherRequest.body isEqual:self.oneRequest.body]))); 71 | } 72 | 73 | - (void)appendMethodDiff:(NSMutableString *)diff { 74 | [diff appendFormat:@"- Method: %@\n+ Method: %@\n", self.oneRequest.method, self.anotherRequest.method]; 75 | } 76 | 77 | - (void)appendUrlDiff:(NSMutableString *)diff { 78 | [diff appendFormat:@"- URL: %@\n+ URL: %@\n", [self.oneRequest.url absoluteString], [self.anotherRequest.url absoluteString]]; 79 | } 80 | 81 | - (void)appendHeadersDiff:(NSMutableString *)diff { 82 | [diff appendString:@" Headers:\n"]; 83 | NSSet *headersInOneButNotInTheOther = [self.oneRequest.headers keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { 84 | return ![self.anotherRequest.headers objectForKey:key] || ![obj isEqual:[self.anotherRequest.headers objectForKey:key]]; 85 | }]; 86 | NSSet *headersInTheOtherButNotInOne = [self.anotherRequest.headers keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { 87 | return ![self.oneRequest.headers objectForKey:key] || ![obj isEqual:[self.oneRequest.headers objectForKey:key]]; 88 | }]; 89 | 90 | NSArray *descriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"" ascending:YES]]; 91 | NSArray * sortedHeadersInOneButNotInTheOther = [headersInOneButNotInTheOther sortedArrayUsingDescriptors:descriptors]; 92 | NSArray * sortedHeadersInTheOtherButNotInOne = [headersInTheOtherButNotInOne sortedArrayUsingDescriptors:descriptors]; 93 | for (NSString *header in sortedHeadersInOneButNotInTheOther) { 94 | NSString *value = [self.oneRequest.headers objectForKey:header]; 95 | [diff appendFormat:@"-\t\"%@\": \"%@\"\n", header, value]; 96 | 97 | } 98 | for (NSString *header in sortedHeadersInTheOtherButNotInOne) { 99 | NSString *value = [self.anotherRequest.headers objectForKey:header]; 100 | [diff appendFormat:@"+\t\"%@\": \"%@\"\n", header, value]; 101 | } 102 | } 103 | 104 | - (void)appendBodyDiff:(NSMutableString *)diff { 105 | NSString *oneBody = [[NSString alloc] initWithData:self.oneRequest.body encoding:NSUTF8StringEncoding]; 106 | if (oneBody.length) { 107 | [diff appendFormat:@"- Body: \"%@\"\n", oneBody]; 108 | } 109 | NSString *anotherBody = [[NSString alloc] initWithData:self.anotherRequest.body encoding:NSUTF8StringEncoding]; 110 | if (anotherBody.length) { 111 | [diff appendFormat:@"+ Body: \"%@\"\n", anotherBody]; 112 | } 113 | } 114 | @end 115 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/ASIHTTPRequest/ASIHTTPRequestStub.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ASIHTTPRequestStub : NSObject 4 | - (int)stub_responseStatusCode; 5 | - (NSData *)stub_responseData; 6 | - (NSDictionary *)stub_responseHeaders; 7 | - (void)stub_startRequest; 8 | @end 9 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/ASIHTTPRequest/ASIHTTPRequestStub.m: -------------------------------------------------------------------------------- 1 | #import "ASIHTTPRequestStub.h" 2 | #import "LSStubResponse.h" 3 | #import "LSNocilla.h" 4 | #import "LSASIHTTPRequestAdapter.h" 5 | #import 6 | 7 | @interface ASIHTTPRequestStub () 8 | @property (nonatomic, strong) LSStubResponse *stubResponse; 9 | @end 10 | 11 | @interface ASIHTTPRequestStub (Private) 12 | - (void)failWithError:(NSError *)error; 13 | - (void)requestFinished; 14 | - (void)markAsFinished; 15 | @end 16 | 17 | static void const * ASIHTTPRequestStubResponseKey = &ASIHTTPRequestStubResponseKey; 18 | 19 | @implementation ASIHTTPRequestStub 20 | 21 | - (void)setStubResponse:(LSStubResponse *)stubResponse { 22 | objc_setAssociatedObject(self, ASIHTTPRequestStubResponseKey, stubResponse, OBJC_ASSOCIATION_RETAIN); 23 | } 24 | 25 | - (LSStubResponse *)stubResponse { 26 | return objc_getAssociatedObject(self, ASIHTTPRequestStubResponseKey); 27 | } 28 | 29 | - (int)stub_responseStatusCode { 30 | return (int)self.stubResponse.statusCode; 31 | } 32 | 33 | - (NSData *)stub_responseData { 34 | return self.stubResponse.body; 35 | } 36 | 37 | - (NSDictionary *)stub_responseHeaders { 38 | return self.stubResponse.headers; 39 | } 40 | 41 | - (void)stub_startRequest { 42 | self.stubResponse = [[LSNocilla sharedInstance] responseForRequest:[[LSASIHTTPRequestAdapter alloc] initWithASIHTTPRequest:(id)self]]; 43 | 44 | if (self.stubResponse.shouldFail) { 45 | [self failWithError:self.stubResponse.error]; 46 | } else { 47 | [self requestFinished]; 48 | } 49 | [self markAsFinished]; 50 | } 51 | 52 | @end -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestAdapter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @class ASIHTTPRequest; 5 | 6 | @interface LSASIHTTPRequestAdapter : NSObject 7 | 8 | - (instancetype)initWithASIHTTPRequest:(ASIHTTPRequest *)request; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestAdapter.m: -------------------------------------------------------------------------------- 1 | #import "LSASIHTTPRequestAdapter.h" 2 | 3 | @interface ASIHTTPRequest 4 | 5 | @property (nonatomic, strong, readonly) NSURL *url; 6 | @property (nonatomic, strong, readonly) NSString *requestMethod; 7 | @property (nonatomic, strong, readonly) NSDictionary *requestHeaders; 8 | @property (nonatomic, strong, readonly) NSData *postBody; 9 | 10 | @end 11 | 12 | @interface LSASIHTTPRequestAdapter () 13 | @property (nonatomic, strong) ASIHTTPRequest *request; 14 | @end 15 | 16 | @implementation LSASIHTTPRequestAdapter 17 | 18 | - (instancetype)initWithASIHTTPRequest:(ASIHTTPRequest *)request { 19 | self = [super init]; 20 | if (self) { 21 | _request = request; 22 | } 23 | return self; 24 | } 25 | 26 | - (NSURL *)url { 27 | return self.request.url; 28 | } 29 | 30 | - (NSString *)method { 31 | return self.request.requestMethod; 32 | } 33 | 34 | - (NSDictionary *)headers { 35 | return self.request.requestHeaders; 36 | } 37 | 38 | - (NSData *)body { 39 | return self.request.postBody; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestHook.h: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @interface LSASIHTTPRequestHook : LSHTTPClientHook 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestHook.m: -------------------------------------------------------------------------------- 1 | #import "LSASIHTTPRequestHook.h" 2 | #import "ASIHTTPRequestStub.h" 3 | #import 4 | 5 | @implementation LSASIHTTPRequestHook 6 | 7 | - (void)load { 8 | if (!NSClassFromString(@"ASIHTTPRequest")) return; 9 | [self swizzleASIHTTPRequest]; 10 | } 11 | 12 | - (void)unload { 13 | if (!NSClassFromString(@"ASIHTTPRequest")) return; 14 | [self swizzleASIHTTPRequest]; 15 | } 16 | 17 | #pragma mark - Internal Methods 18 | 19 | - (void)swizzleASIHTTPRequest { 20 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseStatusCode") withSelector:@selector(stub_responseStatusCode)]; 21 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseData") withSelector:@selector(stub_responseData)]; 22 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseHeaders") withSelector:@selector(stub_responseHeaders)]; 23 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"startRequest") withSelector:@selector(stub_startRequest)]; 24 | [self addMethodToASIHTTPRequest:NSSelectorFromString(@"stubResponse")]; 25 | [self addMethodToASIHTTPRequest:NSSelectorFromString(@"setStubResponse:")]; 26 | } 27 | 28 | - (void)swizzleASIHTTPSelector:(SEL)original withSelector:(SEL)stub { 29 | Class asiHttpRequest = NSClassFromString(@"ASIHTTPRequest"); 30 | Method originalMethod = class_getInstanceMethod(asiHttpRequest, original); 31 | Method stubMethod = class_getInstanceMethod([ASIHTTPRequestStub class], stub); 32 | if (!originalMethod || !stubMethod) { 33 | [self fail]; 34 | } 35 | method_exchangeImplementations(originalMethod, stubMethod); 36 | } 37 | 38 | - (void)addMethodToASIHTTPRequest:(SEL)newMethod { 39 | Method method = class_getInstanceMethod([ASIHTTPRequestStub class], newMethod); 40 | const char *types = method_getTypeEncoding(method); 41 | class_addMethod(NSClassFromString(@"ASIHTTPRequest"), newMethod, class_getMethodImplementation([ASIHTTPRequestStub class], newMethod), types); 42 | } 43 | 44 | - (void)fail { 45 | [NSException raise:NSInternalInconsistencyException format:@"Couldn't load ASIHTTPRequest hook."]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/LSHTTPClientHook.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSHTTPClientHook : NSObject 4 | - (void)load; 5 | - (void)unload; 6 | @end 7 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/LSHTTPClientHook.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @implementation LSHTTPClientHook 4 | - (void)load { 5 | [NSException raise:NSInternalInconsistencyException 6 | format:@"Method '%@' not implemented. Subclass '%@' and override it", NSStringFromSelector(_cmd), NSStringFromClass([self class])]; 7 | } 8 | 9 | - (void)unload { 10 | [NSException raise:NSInternalInconsistencyException 11 | format:@"Method '%@' not implemented. Subclass '%@' and override it", NSStringFromSelector(_cmd), NSStringFromClass([self class])]; 12 | } 13 | @end 14 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSHTTPStubURLProtocol : NSURLProtocol 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPStubURLProtocol.h" 2 | #import "LSNocilla.h" 3 | #import "NSURLRequest+LSHTTPRequest.h" 4 | #import "LSStubRequest.h" 5 | #import "NSURLRequest+DSL.h" 6 | 7 | @implementation LSHTTPStubURLProtocol 8 | 9 | + (BOOL)canInitWithRequest:(NSURLRequest *)request { 10 | return [@[ @"http", @"https" ] containsObject:request.URL.scheme]; 11 | } 12 | 13 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { 14 | return request; 15 | } 16 | + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { 17 | return NO; 18 | } 19 | 20 | - (void)startLoading { 21 | NSURLRequest* request = [self request]; 22 | id client = [self client]; 23 | 24 | LSStubResponse* stubbedResponse = [[LSNocilla sharedInstance] responseForRequest:request]; 25 | 26 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 27 | [cookieStorage setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:stubbedResponse.headers forURL:request.url] 28 | forURL:request.URL mainDocumentURL:request.URL]; 29 | 30 | if (stubbedResponse.shouldFail) { 31 | [client URLProtocol:self didFailWithError:stubbedResponse.error]; 32 | } else { 33 | NSHTTPURLResponse* urlResponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL 34 | statusCode:stubbedResponse.statusCode 35 | HTTPVersion:nil 36 | headerFields:stubbedResponse.headers]; 37 | 38 | if (stubbedResponse.statusCode < 300 || stubbedResponse.statusCode > 399 39 | || stubbedResponse.statusCode == 304 || stubbedResponse.statusCode == 305 ) { 40 | NSData *body = stubbedResponse.body; 41 | 42 | [client URLProtocol:self didReceiveResponse:urlResponse 43 | cacheStoragePolicy:NSURLCacheStorageNotAllowed]; 44 | [client URLProtocol:self didLoadData:body]; 45 | [client URLProtocolDidFinishLoading:self]; 46 | } else { 47 | 48 | NSURL *newURL = [NSURL URLWithString:[stubbedResponse.headers objectForKey:@"Location"] relativeToURL:request.URL]; 49 | NSMutableURLRequest *redirectRequest = [NSMutableURLRequest requestWithURL:newURL]; 50 | 51 | [redirectRequest setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[cookieStorage cookiesForURL:newURL]]]; 52 | 53 | [client URLProtocol:self 54 | wasRedirectedToRequest:redirectRequest 55 | redirectResponse:urlResponse]; 56 | // According to: https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Listings/CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_m.html 57 | // needs to abort the original request 58 | [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; 59 | 60 | } 61 | } 62 | } 63 | 64 | - (void)stopLoading { 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/LSNSURLHook.h: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @interface LSNSURLHook : LSHTTPClientHook 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/LSNSURLHook.m: -------------------------------------------------------------------------------- 1 | #import "LSNSURLHook.h" 2 | #import "LSHTTPStubURLProtocol.h" 3 | 4 | @implementation LSNSURLHook 5 | 6 | - (void)load { 7 | [NSURLProtocol registerClass:[LSHTTPStubURLProtocol class]]; 8 | } 9 | 10 | - (void)unload { 11 | [NSURLProtocol unregisterClass:[LSHTTPStubURLProtocol class]]; 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+DSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSURLRequest (DSL) 4 | - (NSString *)toNocillaDSL; 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+DSL.m: -------------------------------------------------------------------------------- 1 | #import "NSURLRequest+DSL.h" 2 | #import "LSHTTPRequestDSLRepresentation.h" 3 | #import "NSURLRequest+LSHTTPRequest.h" 4 | 5 | @implementation NSURLRequest (DSL) 6 | - (NSString *)toNocillaDSL { 7 | return [[[LSHTTPRequestDSLRepresentation alloc] initWithRequest:self] description]; 8 | } 9 | @end 10 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+LSHTTPRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface NSURLRequest (LSHTTPRequest) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLRequest/NSURLRequest+LSHTTPRequest.m: -------------------------------------------------------------------------------- 1 | #import "NSURLRequest+LSHTTPRequest.h" 2 | 3 | @implementation NSURLRequest (LSHTTPRequest) 4 | 5 | - (NSURL*)url { 6 | return self.URL; 7 | } 8 | 9 | - (NSString *)method { 10 | return self.HTTPMethod; 11 | } 12 | 13 | - (NSDictionary *)headers { 14 | return self.allHTTPHeaderFields; 15 | } 16 | 17 | - (NSData *)body { 18 | if (self.HTTPBodyStream) { 19 | NSInputStream *stream = self.HTTPBodyStream; 20 | NSMutableData *data = [NSMutableData data]; 21 | [stream open]; 22 | size_t bufferSize = 4096; 23 | uint8_t *buffer = malloc(bufferSize); 24 | if (buffer == NULL) { 25 | [NSException raise:@"NocillaMallocFailure" format:@"Could not allocate %zu bytes to read HTTPBodyStream", bufferSize]; 26 | } 27 | while ([stream hasBytesAvailable]) { 28 | NSInteger bytesRead = [stream read:buffer maxLength:bufferSize]; 29 | if (bytesRead > 0) { 30 | NSData *readData = [NSData dataWithBytes:buffer length:bytesRead]; 31 | [data appendData:readData]; 32 | } else if (bytesRead < 0) { 33 | [NSException raise:@"NocillaStreamReadError" format:@"An error occurred while reading HTTPBodyStream (%ld)", (long)bytesRead]; 34 | } else if (bytesRead == 0) { 35 | break; 36 | } 37 | } 38 | free(buffer); 39 | [stream close]; 40 | 41 | return data; 42 | } 43 | 44 | return self.HTTPBody; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h: -------------------------------------------------------------------------------- 1 | // 2 | // LSNSURLSessionHook.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 08/01/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "LSHTTPClientHook.h" 12 | 13 | @interface LSNSURLSessionHook : LSHTTPClientHook 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m: -------------------------------------------------------------------------------- 1 | // 2 | // LSNSURLSessionHook.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 08/01/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "LSNSURLSessionHook.h" 10 | #import "LSHTTPStubURLProtocol.h" 11 | #import 12 | 13 | @implementation LSNSURLSessionHook 14 | 15 | - (void)load { 16 | Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); 17 | [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; 18 | } 19 | 20 | - (void)unload { 21 | Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); 22 | [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; 23 | } 24 | 25 | - (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)stub { 26 | 27 | Method originalMethod = class_getInstanceMethod(original, selector); 28 | Method stubMethod = class_getInstanceMethod(stub, selector); 29 | if (!originalMethod || !stubMethod) { 30 | [NSException raise:NSInternalInconsistencyException format:@"Couldn't load NSURLSession hook."]; 31 | } 32 | method_exchangeImplementations(originalMethod, stubMethod); 33 | } 34 | 35 | - (NSArray *)protocolClasses { 36 | return @[[LSHTTPStubURLProtocol class]]; 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/LSNocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Nocilla.h" 3 | 4 | @class LSStubRequest; 5 | @class LSStubResponse; 6 | @class LSHTTPClientHook; 7 | @protocol LSHTTPRequest; 8 | 9 | extern NSString * const LSUnexpectedRequest; 10 | 11 | @interface LSNocilla : NSObject 12 | + (LSNocilla *)sharedInstance; 13 | 14 | @property (nonatomic, strong, readonly) NSArray *stubbedRequests; 15 | @property (nonatomic, assign, readonly, getter = isStarted) BOOL started; 16 | 17 | - (void)start; 18 | - (void)stop; 19 | - (void)addStubbedRequest:(LSStubRequest *)request; 20 | - (void)clearStubs; 21 | 22 | - (void)registerHook:(LSHTTPClientHook *)hook; 23 | 24 | - (LSStubResponse *)responseForRequest:(id)request; 25 | @end 26 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/LSNocilla.m: -------------------------------------------------------------------------------- 1 | #import "LSNocilla.h" 2 | #import "LSNSURLHook.h" 3 | #import "LSStubRequest.h" 4 | #import "LSHTTPRequestDSLRepresentation.h" 5 | #import "LSASIHTTPRequestHook.h" 6 | #import "LSNSURLSessionHook.h" 7 | #import "LSASIHTTPRequestHook.h" 8 | 9 | NSString * const LSUnexpectedRequest = @"Unexpected Request"; 10 | 11 | @interface LSNocilla () 12 | @property (nonatomic, strong) NSMutableArray *mutableRequests; 13 | @property (nonatomic, strong) NSMutableArray *hooks; 14 | @property (nonatomic, assign, getter = isStarted) BOOL started; 15 | 16 | - (void)loadHooks; 17 | - (void)unloadHooks; 18 | @end 19 | 20 | static LSNocilla *sharedInstace = nil; 21 | 22 | @implementation LSNocilla 23 | 24 | + (LSNocilla *)sharedInstance { 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | sharedInstace = [[self alloc] init]; 28 | }); 29 | return sharedInstace; 30 | } 31 | 32 | - (id)init { 33 | self = [super init]; 34 | if (self) { 35 | _mutableRequests = [NSMutableArray array]; 36 | _hooks = [NSMutableArray array]; 37 | [self registerHook:[[LSNSURLHook alloc] init]]; 38 | if (NSClassFromString(@"NSURLSession") != nil) { 39 | [self registerHook:[[LSNSURLSessionHook alloc] init]]; 40 | } 41 | [self registerHook:[[LSASIHTTPRequestHook alloc] init]]; 42 | } 43 | return self; 44 | } 45 | 46 | - (NSArray *)stubbedRequests { 47 | return [NSArray arrayWithArray:self.mutableRequests]; 48 | } 49 | 50 | - (void)start { 51 | if (!self.isStarted){ 52 | [self loadHooks]; 53 | self.started = YES; 54 | } 55 | } 56 | 57 | - (void)stop { 58 | [self unloadHooks]; 59 | [self clearStubs]; 60 | self.started = NO; 61 | } 62 | 63 | - (void)addStubbedRequest:(LSStubRequest *)request { 64 | NSUInteger index = [self.mutableRequests indexOfObject:request]; 65 | 66 | if (index == NSNotFound) { 67 | [self.mutableRequests addObject:request]; 68 | return; 69 | } 70 | 71 | [self.mutableRequests replaceObjectAtIndex:index withObject:request]; 72 | } 73 | 74 | - (void)clearStubs { 75 | [self.mutableRequests removeAllObjects]; 76 | } 77 | 78 | - (LSStubResponse *)responseForRequest:(id)actualRequest { 79 | NSArray* requests = [LSNocilla sharedInstance].stubbedRequests; 80 | 81 | for(LSStubRequest *someStubbedRequest in requests) { 82 | if ([someStubbedRequest matchesRequest:actualRequest]) { 83 | return someStubbedRequest.response; 84 | } 85 | } 86 | [NSException raise:@"NocillaUnexpectedRequest" format:@"An unexpected HTTP request was fired.\n\nUse this snippet to stub the request:\n%@\n", [[[LSHTTPRequestDSLRepresentation alloc] initWithRequest:actualRequest] description]]; 87 | 88 | return nil; 89 | } 90 | 91 | - (void)registerHook:(LSHTTPClientHook *)hook { 92 | if (![self hookWasRegistered:hook]) { 93 | [[self hooks] addObject:hook]; 94 | } 95 | } 96 | 97 | - (BOOL)hookWasRegistered:(LSHTTPClientHook *)aHook { 98 | for (LSHTTPClientHook *hook in self.hooks) { 99 | if ([hook isMemberOfClass: [aHook class]]) { 100 | return YES; 101 | } 102 | } 103 | return NO; 104 | } 105 | #pragma mark - Private 106 | - (void)loadHooks { 107 | for (LSHTTPClientHook *hook in self.hooks) { 108 | [hook load]; 109 | } 110 | } 111 | 112 | - (void)unloadHooks { 113 | for (LSHTTPClientHook *hook in self.hooks) { 114 | [hook unload]; 115 | } 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSDataMatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // LSDataMatcher.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LSMatcher.h" 11 | 12 | @interface LSDataMatcher : LSMatcher 13 | 14 | - (instancetype)initWithData:(NSData *)data; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSDataMatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // LSDataMatcher.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "LSDataMatcher.h" 10 | 11 | @interface LSDataMatcher () 12 | 13 | @property (nonatomic, copy) NSData *data; 14 | 15 | @end 16 | 17 | @implementation LSDataMatcher 18 | 19 | - (instancetype)initWithData:(NSData *)data { 20 | self = [super init]; 21 | 22 | if (self) { 23 | _data = data; 24 | } 25 | return self; 26 | } 27 | 28 | - (BOOL)matchesData:(NSData *)data { 29 | return [self.data isEqualToData:data]; 30 | } 31 | 32 | 33 | #pragma mark - Equality 34 | 35 | - (BOOL)isEqual:(id)object { 36 | if (self == object) { 37 | return YES; 38 | } 39 | 40 | if (![object isKindOfClass:[LSDataMatcher class]]) { 41 | return NO; 42 | } 43 | 44 | return [self.data isEqual:((LSDataMatcher *)object).data]; 45 | } 46 | 47 | - (NSUInteger)hash { 48 | return self.data.hash; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSMatcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class LSMatcher; 4 | 5 | @protocol LSMatcheable 6 | 7 | - (LSMatcher *)matcher; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSMatcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSMatcher : NSObject 4 | 5 | - (BOOL)matches:(NSString *)string; 6 | 7 | - (BOOL)matchesData:(NSData *)data; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSMatcher.h" 2 | 3 | @implementation LSMatcher 4 | 5 | - (BOOL)matches:(NSString *)string { 6 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher matches:] is an abstract method" userInfo:nil]; 7 | } 8 | 9 | - (BOOL)matchesData:(NSData *)data { 10 | return [self matches:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; 11 | } 12 | 13 | 14 | #pragma mark - Equality 15 | 16 | - (BOOL)isEqual:(id)object { 17 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher isEqual:] is an abstract method" userInfo:nil]; 18 | } 19 | 20 | - (NSUInteger)hash { 21 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher hash] an abstract method" userInfo:nil]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSRegexMatcher.h: -------------------------------------------------------------------------------- 1 | #import "LSMatcher.h" 2 | 3 | @interface LSRegexMatcher : LSMatcher 4 | 5 | - (instancetype)initWithRegex:(NSRegularExpression *)regex; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSRegexMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSRegexMatcher.h" 2 | 3 | @interface LSRegexMatcher () 4 | @property (nonatomic, strong) NSRegularExpression *regex; 5 | @end 6 | 7 | @implementation LSRegexMatcher 8 | 9 | - (instancetype)initWithRegex:(NSRegularExpression *)regex { 10 | self = [super init]; 11 | if (self) { 12 | _regex = regex; 13 | } 14 | return self; 15 | } 16 | 17 | - (BOOL)matches:(NSString *)string { 18 | return [self.regex numberOfMatchesInString:string options:0 range:NSMakeRange(0, string.length)] > 0; 19 | } 20 | 21 | 22 | #pragma mark - Equality 23 | 24 | - (BOOL)isEqual:(id)object { 25 | if (self == object) { 26 | return YES; 27 | } 28 | 29 | if (![object isKindOfClass:[LSRegexMatcher class]]) { 30 | return NO; 31 | } 32 | 33 | return [self.regex isEqual:((LSRegexMatcher *)object).regex]; 34 | } 35 | 36 | - (NSUInteger)hash { 37 | return self.regex.hash; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSStringMatcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcher.h" 3 | 4 | @interface LSStringMatcher : LSMatcher 5 | 6 | - (instancetype)initWithString:(NSString *)string; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/LSStringMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSStringMatcher.h" 2 | 3 | @interface LSStringMatcher () 4 | 5 | @property (nonatomic, copy) NSString *string; 6 | 7 | @end 8 | 9 | @implementation LSStringMatcher 10 | 11 | - (instancetype)initWithString:(NSString *)string { 12 | self = [super init]; 13 | if (self) { 14 | _string = string; 15 | } 16 | return self; 17 | } 18 | 19 | - (BOOL)matches:(NSString *)string { 20 | return [self.string isEqualToString:string]; 21 | } 22 | 23 | 24 | #pragma mark - Equality 25 | 26 | - (BOOL)isEqual:(id)object { 27 | if (self == object) { 28 | return YES; 29 | } 30 | 31 | if (![object isKindOfClass:[LSStringMatcher class]]) { 32 | return NO; 33 | } 34 | 35 | return [self.string isEqualToString:((LSStringMatcher *)object).string]; 36 | } 37 | 38 | - (NSUInteger)hash { 39 | return self.string.hash; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/NSData+Matcheable.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Matcheable.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LSMatcheable.h" 11 | 12 | @interface NSData (Matcheable) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/NSData+Matcheable.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Matcheable.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "NSData+Matcheable.h" 10 | #import "LSDataMatcher.h" 11 | 12 | @implementation NSData (Matcheable) 13 | 14 | - (LSMatcher *)matcher { 15 | return [[LSDataMatcher alloc] initWithData:self]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/NSRegularExpression+Matcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcheable.h" 3 | 4 | @interface NSRegularExpression (Matcheable) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/NSRegularExpression+Matcheable.m: -------------------------------------------------------------------------------- 1 | #import "NSRegularExpression+Matcheable.h" 2 | #import "LSRegexMatcher.h" 3 | 4 | @implementation NSRegularExpression (Matcheable) 5 | 6 | - (LSMatcher *)matcher { 7 | return [[LSRegexMatcher alloc] initWithRegex:self]; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/NSString+Matcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcheable.h" 3 | 4 | @interface NSString (Matcheable) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Matchers/NSString+Matcheable.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Matcheable.h" 2 | #import "LSStringMatcher.h" 3 | 4 | @implementation NSString (Matcheable) 5 | 6 | - (LSMatcher *)matcher { 7 | return [[LSStringMatcher alloc] initWithString:self]; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Model/LSHTTPBody.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPBody 4 | - (NSData *)data; 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Model/LSHTTPRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPRequest 4 | 5 | @property (nonatomic, strong, readonly) NSURL *url; 6 | @property (nonatomic, strong, readonly) NSString *method; 7 | @property (nonatomic, strong, readonly) NSDictionary *headers; 8 | @property (nonatomic, strong, readonly) NSData *body; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Model/LSHTTPResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPResponse 4 | @property (nonatomic, assign, readonly) NSInteger statusCode; 5 | @property (nonatomic, strong, readonly) NSDictionary *headers; 6 | @property (nonatomic, strong, readonly) NSData *body; 7 | @end 8 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Nocilla.h: -------------------------------------------------------------------------------- 1 | // 2 | // Nocilla.h 3 | // Nocilla 4 | // 5 | // Created by Robert Böhnke on 26/03/15. 6 | // Copyright (c) 2015 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Nocilla. 12 | FOUNDATION_EXPORT double NocillaVersionNumber; 13 | 14 | //! Project version string for Nocilla. 15 | FOUNDATION_EXPORT const unsigned char NocillaVersionString[]; 16 | 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Stubs/LSStubRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSStubResponse.h" 3 | #import "LSHTTPRequest.h" 4 | 5 | 6 | @class LSMatcher; 7 | @class LSStubRequest; 8 | @class LSStubResponse; 9 | 10 | @interface LSStubRequest : NSObject 11 | @property (nonatomic, strong, readonly) NSString *method; 12 | @property (nonatomic, strong, readonly) LSMatcher *urlMatcher; 13 | @property (nonatomic, strong, readonly) NSDictionary *headers; 14 | @property (nonatomic, strong, readwrite) LSMatcher *body; 15 | 16 | @property (nonatomic, strong) LSStubResponse *response; 17 | 18 | - (instancetype)initWithMethod:(NSString *)method url:(NSString *)url; 19 | - (instancetype)initWithMethod:(NSString *)method urlMatcher:(LSMatcher *)urlMatcher; 20 | 21 | - (void)setHeader:(NSString *)header value:(NSString *)value; 22 | 23 | - (BOOL)matchesRequest:(id)request; 24 | @end 25 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Stubs/LSStubRequest.m: -------------------------------------------------------------------------------- 1 | #import "LSStubRequest.h" 2 | #import "LSMatcher.h" 3 | #import "NSString+Matcheable.h" 4 | 5 | @interface LSStubRequest () 6 | @property (nonatomic, strong, readwrite) NSString *method; 7 | @property (nonatomic, strong, readwrite) LSMatcher *urlMatcher; 8 | @property (nonatomic, strong, readwrite) NSMutableDictionary *mutableHeaders; 9 | 10 | -(BOOL)matchesMethod:(id)request; 11 | -(BOOL)matchesURL:(id)request; 12 | -(BOOL)matchesHeaders:(id)request; 13 | -(BOOL)matchesBody:(id)request; 14 | @end 15 | 16 | @implementation LSStubRequest 17 | 18 | - (instancetype)initWithMethod:(NSString *)method url:(NSString *)url { 19 | return [self initWithMethod:method urlMatcher:[url matcher]]; 20 | } 21 | 22 | - (instancetype)initWithMethod:(NSString *)method urlMatcher:(LSMatcher *)urlMatcher; { 23 | self = [super init]; 24 | if (self) { 25 | self.method = method; 26 | self.urlMatcher = urlMatcher; 27 | self.mutableHeaders = [NSMutableDictionary dictionary]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)setHeader:(NSString *)header value:(NSString *)value { 33 | [self.mutableHeaders setValue:value forKey:header]; 34 | } 35 | 36 | - (NSDictionary *)headers { 37 | return [NSDictionary dictionaryWithDictionary:self.mutableHeaders];; 38 | } 39 | 40 | - (NSString *)description { 41 | return [NSString stringWithFormat:@"StubRequest:\nMethod: %@\nURL: %@\nHeaders: %@\nBody: %@\nResponse: %@", 42 | self.method, 43 | self.urlMatcher, 44 | self.headers, 45 | self.body, 46 | self.response]; 47 | } 48 | 49 | - (LSStubResponse *)response { 50 | if (!_response) { 51 | _response = [[LSStubResponse alloc] initDefaultResponse]; 52 | } 53 | return _response; 54 | 55 | } 56 | 57 | - (BOOL)matchesRequest:(id)request { 58 | if ([self matchesMethod:request] 59 | && [self matchesURL:request] 60 | && [self matchesHeaders:request] 61 | && [self matchesBody:request] 62 | ) { 63 | return YES; 64 | } 65 | return NO; 66 | } 67 | 68 | -(BOOL)matchesMethod:(id)request { 69 | if (!self.method || [self.method isEqualToString:request.method]) { 70 | return YES; 71 | } 72 | return NO; 73 | } 74 | 75 | -(BOOL)matchesURL:(id)request { 76 | return [self.urlMatcher matches:[request.url absoluteString]]; 77 | } 78 | 79 | -(BOOL)matchesHeaders:(id)request { 80 | for (NSString *header in self.headers) { 81 | if (![[request.headers objectForKey:header] isEqualToString:[self.headers objectForKey:header]]) { 82 | return NO; 83 | } 84 | } 85 | return YES; 86 | } 87 | 88 | -(BOOL)matchesBody:(id)request { 89 | NSData *reqBody = request.body; 90 | if (!self.body || [self.body matchesData:reqBody]) { 91 | return YES; 92 | } 93 | return NO; 94 | } 95 | 96 | 97 | #pragma mark - Equality 98 | 99 | - (BOOL)isEqual:(id)object { 100 | if (self == object) { 101 | return YES; 102 | } 103 | 104 | if (![object isKindOfClass:[LSStubRequest class]]) { 105 | return NO; 106 | } 107 | 108 | return [self isEqualToStubRequest:object]; 109 | } 110 | 111 | - (BOOL)isEqualToStubRequest:(LSStubRequest *)stubRequest { 112 | if (!stubRequest) { 113 | return NO; 114 | } 115 | 116 | BOOL methodEqual = [self.method isEqualToString:stubRequest.method]; 117 | BOOL urlMatcherEqual = [self.urlMatcher isEqual:stubRequest.urlMatcher]; 118 | BOOL headersEqual = [self.headers isEqual:stubRequest.headers]; 119 | BOOL bodyEqual = (self.body == nil && stubRequest.body == nil) || [self.body isEqual:stubRequest.body]; 120 | 121 | return methodEqual && urlMatcherEqual && headersEqual && bodyEqual; 122 | } 123 | 124 | - (NSUInteger)hash { 125 | return self.method.hash ^ self.urlMatcher.hash ^ self.headers.hash ^ self.body.hash; 126 | } 127 | 128 | @end 129 | 130 | 131 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Stubs/LSStubResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPResponse.h" 3 | 4 | @interface LSStubResponse : NSObject 5 | 6 | @property (nonatomic, assign, readonly) NSInteger statusCode; 7 | @property (nonatomic, strong) NSData *body; 8 | @property (nonatomic, strong, readonly) NSDictionary *headers; 9 | 10 | @property (nonatomic, assign, readonly) BOOL shouldFail; 11 | @property (nonatomic, strong, readonly) NSError *error; 12 | 13 | - (id)initWithError:(NSError *)error; 14 | - (id)initWithStatusCode:(NSInteger)statusCode; 15 | - (id)initWithRawResponse:(NSData *)rawResponseData; 16 | - (id)initDefaultResponse; 17 | - (void)setHeader:(NSString *)header value:(NSString *)value; 18 | @end 19 | -------------------------------------------------------------------------------- /Pods/Nocilla/Nocilla/Stubs/LSStubResponse.m: -------------------------------------------------------------------------------- 1 | #import "LSStubResponse.h" 2 | 3 | @interface LSStubResponse () 4 | @property (nonatomic, assign, readwrite) NSInteger statusCode; 5 | @property (nonatomic, strong) NSMutableDictionary *mutableHeaders; 6 | @property (nonatomic, assign) UInt64 offset; 7 | @property (nonatomic, assign, getter = isDone) BOOL done; 8 | @property (nonatomic, assign) BOOL shouldFail; 9 | @property (nonatomic, strong) NSError *error; 10 | @end 11 | 12 | @implementation LSStubResponse 13 | 14 | #pragma Initializers 15 | - (id)initDefaultResponse { 16 | self = [super init]; 17 | if (self) { 18 | self.shouldFail = NO; 19 | 20 | self.statusCode = 200; 21 | self.mutableHeaders = [NSMutableDictionary dictionary]; 22 | self.body = [@"" dataUsingEncoding:NSUTF8StringEncoding]; 23 | } 24 | return self; 25 | } 26 | 27 | 28 | - (id)initWithError:(NSError *)error { 29 | self = [super init]; 30 | if (self) { 31 | self.shouldFail = YES; 32 | self.error = error; 33 | } 34 | return self; 35 | } 36 | 37 | -(id)initWithStatusCode:(NSInteger)statusCode { 38 | self = [super init]; 39 | if (self) { 40 | self.shouldFail = NO; 41 | self.statusCode = statusCode; 42 | self.mutableHeaders = [NSMutableDictionary dictionary]; 43 | self.body = [@"" dataUsingEncoding:NSUTF8StringEncoding]; 44 | } 45 | return self; 46 | } 47 | 48 | - (id)initWithRawResponse:(NSData *)rawResponseData { 49 | self = [self initDefaultResponse]; 50 | if (self) { 51 | CFHTTPMessageRef httpMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, FALSE); 52 | if (httpMessage) { 53 | CFHTTPMessageAppendBytes(httpMessage, [rawResponseData bytes], [rawResponseData length]); 54 | 55 | self.body = rawResponseData; // By default 56 | 57 | if (CFHTTPMessageIsHeaderComplete(httpMessage)) { 58 | self.statusCode = (NSInteger)CFHTTPMessageGetResponseStatusCode(httpMessage); 59 | self.mutableHeaders = [NSMutableDictionary dictionaryWithDictionary:(__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(httpMessage)]; 60 | self.body = (__bridge_transfer NSData *)CFHTTPMessageCopyBody(httpMessage); 61 | } 62 | CFRelease(httpMessage); 63 | } 64 | } 65 | return self; 66 | } 67 | 68 | - (void)setHeader:(NSString *)header value:(NSString *)value { 69 | [self.mutableHeaders setValue:value forKey:header]; 70 | } 71 | - (NSDictionary *)headers { 72 | return [NSDictionary dictionaryWithDictionary:self.mutableHeaders]; 73 | } 74 | 75 | - (NSString *)description { 76 | return [NSString stringWithFormat:@"StubRequest:\nStatus Code: %ld\nHeaders: %@\nBody: %@", 77 | (long)self.statusCode, 78 | self.mutableHeaders, 79 | self.body]; 80 | } 81 | @end 82 | -------------------------------------------------------------------------------- /Pods/Nocilla/README.md: -------------------------------------------------------------------------------- 1 | # Nocilla [![CI Status](http://img.shields.io/travis/luisobo/Nocilla.svg?style=flat&branch=master)](https://travis-ci.org/luisobo/Nocilla)[![Version](https://img.shields.io/cocoapods/v/Nocilla.svg?style=flat)](http://cocoadocs.org/docsets/Nocilla)[![License](https://img.shields.io/cocoapods/l/Nocilla.svg?style=flat)](http://cocoadocs.org/docsets/Nocilla)[![Platform](https://img.shields.io/cocoapods/p/Nocilla.svg?style=flat)](http://cocoadocs.org/docsets/Nocilla) 2 | 3 | Stunning HTTP stubbing for iOS and OS X. Testing HTTP requests has never been easier. 4 | 5 | This library was inspired by [WebMock](https://github.com/bblimke/webmock) and it's using [this approach](http://www.infinite-loop.dk/blog/2011/09/using-nsurlprotocol-for-injecting-test-data/) to stub the requests. 6 | 7 | ## Features 8 | * Stub HTTP and HTTPS requests in your unit tests. 9 | * Supports NSURLConnection, NSURLSession and ASIHTTPRequest. 10 | * Awesome DSL that will improve the readability and maintainability of your tests. 11 | * Match requests with regular expressions. 12 | * Stub requests with errors. 13 | * Tested. 14 | * Fast. 15 | * Extendable to support more HTTP libraries. 16 | 17 | ## Installation 18 | ### As a [CocoaPod](http://cocoapods.org/) 19 | Just add this to your Podfile 20 | ```ruby 21 | pod 'Nocilla' 22 | ``` 23 | 24 | ### Other approaches 25 | * You should be able to add Nocilla to you source tree. If you are using git, consider using a `git submodule` 26 | 27 | ## Usage 28 | _Yes, the following code is valid Objective-C, or at least, it should be_ 29 | 30 | The following examples are described using [Kiwi](https://github.com/kiwi-bdd/Kiwi) 31 | 32 | ### Common parts 33 | Until Nocilla can hook directly into Kiwi, you will have to include the following snippet in the specs you want to use Nocilla: 34 | 35 | ```objc 36 | #import "Kiwi.h" 37 | #import "Nocilla.h" 38 | SPEC_BEGIN(ExampleSpec) 39 | beforeAll(^{ 40 | [[LSNocilla sharedInstance] start]; 41 | }); 42 | afterAll(^{ 43 | [[LSNocilla sharedInstance] stop]; 44 | }); 45 | afterEach(^{ 46 | [[LSNocilla sharedInstance] clearStubs]; 47 | }); 48 | 49 | it(@"should do something", ^{ 50 | // Stub here! 51 | }); 52 | SPEC_END 53 | ``` 54 | 55 | ### Stubbing requests 56 | #### Stubbing a simple request 57 | It will return the default response, which is a 200 and an empty body. 58 | 59 | ```objc 60 | stubRequest(@"GET", @"http://www.google.com"); 61 | ``` 62 | 63 | #### Stubbing requests with regular expressions 64 | ```objc 65 | stubRequest(@"GET", @"^http://(.*?)\\.example\\.com/v1/dogs\\.json".regex); 66 | ``` 67 | 68 | 69 | #### Stubbing a request with a particular header 70 | 71 | ```objc 72 | stubRequest(@"GET", @"https://api.example.com"). 73 | withHeader(@"Accept", @"application/json"); 74 | ``` 75 | 76 | #### Stubbing a request with multiple headers 77 | 78 | Using the `withHeaders` method makes sense with the Objective-C literals, but it accepts an NSDictionary. 79 | 80 | ```objc 81 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 82 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}); 83 | ``` 84 | 85 | #### Stubbing a request with a particular body 86 | 87 | ```objc 88 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 89 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 90 | withBody(@"{\"name\":\"foo\"}"); 91 | ``` 92 | 93 | You can also use `NSData` for the request body: 94 | 95 | ```objc 96 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 97 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 98 | withBody([@"foo" dataUsingEncoding:NSUTF8StringEncoding]); 99 | ``` 100 | 101 | It even works with regular expressions! 102 | 103 | ```objc 104 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 105 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 106 | withBody(@"^The body start with this".regex); 107 | ``` 108 | 109 | #### Returning a specific status code 110 | ```objc 111 | stubRequest(@"GET", @"http://www.google.com").andReturn(404); 112 | ``` 113 | 114 | #### Returning a specific status code and header 115 | The same approch here, you can use `withHeader` or `withHeaders` 116 | 117 | ```objc 118 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 119 | andReturn(201). 120 | withHeaders(@{@"Content-Type": @"application/json"}); 121 | ``` 122 | 123 | #### Returning a specific status code, headers and body 124 | ```objc 125 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 126 | andReturn(201). 127 | withHeaders(@{@"Content-Type": @"application/json"}). 128 | withBody(@"{\"ok\":true}"); 129 | ``` 130 | 131 | You can also use `NSData` for the response body: 132 | 133 | ```objc 134 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 135 | andReturn(201). 136 | withHeaders(@{@"Content-Type": @"application/json"}). 137 | withBody([@"bar" dataUsingEncoding:NSUTF8StringEncoding]); 138 | ``` 139 | 140 | #### Returning raw responses recorded with `curl -is` 141 | `curl -is http://api.example.com/dogs.json > /tmp/example_curl_-is_output.txt` 142 | 143 | ```objc 144 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 145 | andReturnRawResponse([NSData dataWithContentsOfFile:@"/tmp/example_curl_-is_output.txt"]); 146 | ``` 147 | 148 | #### All together 149 | ```objc 150 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 151 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 152 | withBody(@"{\"name\":\"foo\"}"). 153 | andReturn(201). 154 | withHeaders(@{@"Content-Type": @"application/json"}). 155 | withBody(@"{\"ok\":true}"); 156 | ``` 157 | 158 | #### Making a request fail 159 | This will call the failure handler (callback, delegate... whatever your HTTP client uses) with the specified error. 160 | 161 | ```objc 162 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 163 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 164 | withBody(@"{\"name\":\"foo\"}"). 165 | andFailWithError([NSError errorWithDomain:@"foo" code:123 userInfo:nil]); 166 | ``` 167 | 168 | #### Replacing a request stub 169 | 170 | If you need to change the response of a single request, simply re-stub the request: 171 | 172 | ```objc 173 | stubRequest(@"POST", @"https://api.example.com/authorize/"). 174 | andReturn(401); 175 | 176 | // Some test expectation... 177 | 178 | stubRequest(@"POST", @"https://api.example.com/authorize/"). 179 | andReturn(200); 180 | ``` 181 | 182 | ### Unexpected requests 183 | If some request is made but it wasn't stubbed, Nocilla won't let that request hit the real world. In that case your test should fail. 184 | At this moment Nocilla will raise an exception with a meaningful message about the error and how to solve it, including a snippet of code on how to stub the unexpected request. 185 | 186 | ### Testing asynchronous requests 187 | When testing asynchrounous requests your request will be sent on a different thread from the one on which your test is executed. It is important to keep this in mind, and design your test in such a way that is has enough time to finish. For instance ```tearDown()``` when using ```XCTest``` and ```afterEach()``` when using [Quick](https://github.com/Quick/Quick) and [Nimble](https://github.com/Quick/Nimble) will cause the request never to complete. 188 | 189 | 190 | ## Who uses Nocilla. 191 | 192 | ### Submit a PR to add your company here! 193 | 194 | - [MessageBird](https://www.messagebird.com) 195 | - [Groupon](http://www.groupon.com) 196 | - [Pixable](http://www.pixable.com) 197 | - [Jackthreads](https://www.jackthreads.com) 198 | - [ShopKeep](http://www.shopkeep.com) 199 | - [Venmo](https://www.venmo.com) 200 | - [Lighthouse](http://www.lighthouselabs.co.uk) 201 | - [GE Digital](http://www.ge.com/digital/) 202 | 203 | ## Other alternatives 204 | * [ILTesting](https://github.com/InfiniteLoopDK/ILTesting) 205 | * [OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs) 206 | 207 | ## Contributing 208 | 209 | 1. Fork it 210 | 2. Create your feature branch 211 | 3. Commit your changes 212 | 4. Push to the branch 213 | 5. Create new Pull Request 214 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Alamofire : NSObject 3 | @end 4 | @implementation PodsDummy_Alamofire 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double AlamofireVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.modulemap: -------------------------------------------------------------------------------- 1 | framework module Alamofire { 2 | umbrella header "Alamofire-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Alamofire 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Nocilla/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.11.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Nocilla/Nocilla-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Nocilla : NSObject 3 | @end 4 | @implementation PodsDummy_Nocilla 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Nocilla/Nocilla-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Nocilla/Nocilla-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "NSData+Nocilla.h" 4 | #import "NSString+Nocilla.h" 5 | #import "LSStubRequestDSL.h" 6 | #import "LSStubResponseDSL.h" 7 | #import "LSNocilla.h" 8 | #import "LSMatcheable.h" 9 | #import "LSMatcher.h" 10 | #import "NSData+Matcheable.h" 11 | #import "NSRegularExpression+Matcheable.h" 12 | #import "NSString+Matcheable.h" 13 | #import "LSHTTPBody.h" 14 | #import "Nocilla.h" 15 | 16 | FOUNDATION_EXPORT double NocillaVersionNumber; 17 | FOUNDATION_EXPORT const unsigned char NocillaVersionString[]; 18 | 19 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Nocilla/Nocilla.modulemap: -------------------------------------------------------------------------------- 1 | framework module Nocilla { 2 | umbrella header "Nocilla-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Nocilla/Nocilla.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Nocilla 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_LDFLAGS = -framework "CFNetwork" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | Generated by CocoaPods - https://cocoapods.org 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | Alamofire 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Generated by CocoaPods - https://cocoapods.org 47 | Title 48 | 49 | Type 50 | PSGroupSpecifier 51 | 52 | 53 | StringsTable 54 | Acknowledgements 55 | Title 56 | Acknowledgements 57 | 58 | 59 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_GithubPilot : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_GithubPilot 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | *) 22 | TARGET_DEVICE_ARGS="--target-device mac" 23 | ;; 24 | esac 25 | 26 | realpath() { 27 | DIRECTORY="$(cd "${1%/*}" && pwd)" 28 | FILENAME="${1##*/}" 29 | echo "$DIRECTORY/$FILENAME" 30 | } 31 | 32 | install_resource() 33 | { 34 | if [[ "$1" = /* ]] ; then 35 | RESOURCE_PATH="$1" 36 | else 37 | RESOURCE_PATH="${PODS_ROOT}/$1" 38 | fi 39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 40 | cat << EOM 41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 42 | EOM 43 | exit 1 44 | fi 45 | case $RESOURCE_PATH in 46 | *.storyboard) 47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 49 | ;; 50 | *.xib) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.framework) 55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 59 | ;; 60 | *.xcdatamodel) 61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 63 | ;; 64 | *.xcdatamodeld) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 67 | ;; 68 | *.xcmappingmodel) 69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 71 | ;; 72 | *.xcassets) 73 | ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH") 74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 75 | ;; 76 | *) 77 | echo "$RESOURCE_PATH" 78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 79 | ;; 80 | esac 81 | } 82 | 83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | fi 89 | rm -f "$RESOURCES_TO_COPY" 90 | 91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 92 | then 93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 95 | while read line; do 96 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then 97 | XCASSET_FILES+=("$line") 98 | fi 99 | done <<<"$OTHER_XCASSETS" 100 | 101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 102 | fi 103 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double Pods_GithubPilotVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char Pods_GithubPilotVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_GithubPilot { 2 | umbrella header "Pods-GithubPilot-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilot/Pods-GithubPilot.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Nocilla 5 | 6 | Copyright (c) 2012 Luis Solano Bonet 7 | MIT License 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to 14 | permit persons to whom the Software is furnished to do so, subject to 15 | the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | ## Alamofire 29 | 30 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | THE SOFTWARE. 49 | 50 | Generated by CocoaPods - https://cocoapods.org 51 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2012 Luis Solano Bonet 18 | MIT License 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining 21 | a copy of this software and associated documentation files (the 22 | "Software"), to deal in the Software without restriction, including 23 | without limitation the rights to use, copy, modify, merge, publish, 24 | distribute, sublicense, and/or sell copies of the Software, and to 25 | permit persons to whom the Software is furnished to do so, subject to 26 | the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be 29 | included in all copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 32 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 33 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 34 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 35 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 36 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 37 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 38 | License 39 | MIT 40 | Title 41 | Nocilla 42 | Type 43 | PSGroupSpecifier 44 | 45 | 46 | FooterText 47 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 48 | 49 | Permission is hereby granted, free of charge, to any person obtaining a copy 50 | of this software and associated documentation files (the "Software"), to deal 51 | in the Software without restriction, including without limitation the rights 52 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 53 | copies of the Software, and to permit persons to whom the Software is 54 | furnished to do so, subject to the following conditions: 55 | 56 | The above copyright notice and this permission notice shall be included in 57 | all copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 60 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 61 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 62 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 63 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 64 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 65 | THE SOFTWARE. 66 | 67 | License 68 | MIT 69 | Title 70 | Alamofire 71 | Type 72 | PSGroupSpecifier 73 | 74 | 75 | FooterText 76 | Generated by CocoaPods - https://cocoapods.org 77 | Title 78 | 79 | Type 80 | PSGroupSpecifier 81 | 82 | 83 | StringsTable 84 | Acknowledgements 85 | Title 86 | Acknowledgements 87 | 88 | 89 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_GithubPilotTests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_GithubPilotTests 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\"" 63 | /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1" 64 | fi 65 | } 66 | 67 | # Strip invalid architectures 68 | strip_invalid_archs() { 69 | binary="$1" 70 | # Get architectures for current file 71 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 72 | stripped="" 73 | for arch in $archs; do 74 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 75 | # Strip non-valid architectures in-place 76 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 77 | stripped="$stripped $arch" 78 | fi 79 | done 80 | if [[ "$stripped" ]]; then 81 | echo "Stripped $binary of architectures:$stripped" 82 | fi 83 | } 84 | 85 | 86 | if [[ "$CONFIGURATION" == "Debug" ]]; then 87 | install_framework "$BUILT_PRODUCTS_DIR/Nocilla/Nocilla.framework" 88 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 89 | fi 90 | if [[ "$CONFIGURATION" == "Release" ]]; then 91 | install_framework "$BUILT_PRODUCTS_DIR/Nocilla/Nocilla.framework" 92 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 93 | fi 94 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | *) 22 | TARGET_DEVICE_ARGS="--target-device mac" 23 | ;; 24 | esac 25 | 26 | realpath() { 27 | DIRECTORY="$(cd "${1%/*}" && pwd)" 28 | FILENAME="${1##*/}" 29 | echo "$DIRECTORY/$FILENAME" 30 | } 31 | 32 | install_resource() 33 | { 34 | if [[ "$1" = /* ]] ; then 35 | RESOURCE_PATH="$1" 36 | else 37 | RESOURCE_PATH="${PODS_ROOT}/$1" 38 | fi 39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 40 | cat << EOM 41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 42 | EOM 43 | exit 1 44 | fi 45 | case $RESOURCE_PATH in 46 | *.storyboard) 47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 49 | ;; 50 | *.xib) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.framework) 55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 59 | ;; 60 | *.xcdatamodel) 61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 63 | ;; 64 | *.xcdatamodeld) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 67 | ;; 68 | *.xcmappingmodel) 69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 71 | ;; 72 | *.xcassets) 73 | ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH") 74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 75 | ;; 76 | *) 77 | echo "$RESOURCE_PATH" 78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 79 | ;; 80 | esac 81 | } 82 | 83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | fi 89 | rm -f "$RESOURCES_TO_COPY" 90 | 91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 92 | then 93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 95 | while read line; do 96 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then 97 | XCASSET_FILES+=("$line") 98 | fi 99 | done <<<"$OTHER_XCASSETS" 100 | 101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 102 | fi 103 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double Pods_GithubPilotTestsVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char Pods_GithubPilotTestsVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/Nocilla" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Nocilla/Nocilla.framework/Headers" 7 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Nocilla" 8 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 9 | PODS_BUILD_DIR = $BUILD_DIR 10 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_GithubPilotTests { 2 | umbrella header "Pods-GithubPilotTests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-GithubPilotTests/Pods-GithubPilotTests.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/Nocilla" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Nocilla/Nocilla.framework/Headers" 7 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Nocilla" 8 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 9 | PODS_BUILD_DIR = $BUILD_DIR 10 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GithubPilot - Github API V3 Swifty Wrapper 2 | [![Build Status](https://travis-ci.org/jindulys/GithubPilot.svg)](https://travis-ci.org/jindulys/GithubPilot) 3 | 4 | This is a Swift Github API Wrapper, it could make your life a little easier if you want to make an App with Github's wonderful data. 5 | 6 | # Installation 7 | 8 | ## CocoaPods 9 | 10 | Add a `Podfile` to your project, then edit it by adding: 11 | 12 | use_frameworks! 13 | pod 'GithubPilot', '~>1.0.3' 14 | 15 | then, run the following command: 16 | 17 | $ pod install 18 | 19 | From now on you should use `{Project}.xcworkspace` to open your project 20 | 21 | # Before You start 22 | 23 | ## Setup Your developer applications 24 | 25 | Go to your Github homepage, tap your avatar -> Setting, on your left choose **Applications** -> **Developer applications**, then you should tap **register a new OAuth application** on your top right side. 26 | 27 | Remember you should use a custom **Authorization callback URL**, which will be used later, eg. FunnyGithubTest://random 28 | After registration, you could get your **Client ID** and **Client Secret**. 29 | 30 | ## Setup Your Project 31 | 32 | To allow your user to be re-directed back to your app after OAuth dance, you'll need to associate a custom URL scheme with your app. 33 | 34 | Open your Xcode then open **Info.plist** of your project. copy and paste following code to your Info.plist source code. 35 | 36 | CFBundleURLTypes 37 | 38 | 39 | CFBundleURLSchemes 40 | 41 | your.custom.scheme(eg. FunnyGithubTest) 42 | 43 | 44 | 45 | 46 | # Usage 47 | 48 | ## Authentication 49 | First, add `import GithubPilot` at the top of your **AppDelegate**. You could then add `application(_: didFinishLaunchingWithOptions:)` with following to authenticate your client. You also should take care of `scope` parameter that your client will use, refer to [Github Scope](https://developer.github.com/v3/oauth/#scopes) 50 | 51 | Github.setupClientID("YourClientID", clientSecret: "YourClientSecret", scope: ["user", "repo"], redirectURI: "YourCustomCallBackURL") 52 | Github.authenticate() 53 | 54 | Second, add following code to your **AppDelegate** to get Github _**access token**_ 55 | 56 | func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool 57 | { 58 | Github.requestAccessToken(url) 59 | return true 60 | } 61 | 62 | ## Used in Code 63 | 64 | ### Users 65 | 66 | #### Get the authenticated user 67 | 68 | if let client = Github.authorizedClient { 69 | client.users.getAuthenticatedUser().response({ user, requestError in 70 | if let me = user { 71 | print(me.description) 72 | } else { 73 | print(requestError?.description) 74 | } 75 | }) 76 | } 77 | #### Get a user with username 78 | 79 | if let client = Github.authorizedClient { 80 | client.users.getUser(username: "onevcat").response({ (githubUser, error) -> Void in 81 | if let user = githubUser { 82 | print(user.description) 83 | } else { 84 | print(error?.description) 85 | } 86 | }) 87 | } 88 | 89 | #### Get a page of users from `since` id 90 | 91 | if let client = Github.authorizedClient { 92 | client.users.getAllUsers("1209").response({ (httpResponse, users, requestError) -> Void in 93 | if let response = httpResponse { 94 | // next `since` id 95 | print("Since :\(response)") 96 | } 97 | if let result = users { 98 | for user in result { 99 | print(user.description) 100 | } 101 | } else { 102 | print(requestError?.description) 103 | } 104 | }) 105 | } 106 | 107 | ### Repositories 108 | 109 | #### Get repositories of authenticated user 110 | 111 | if let client = Github.authorizedClient { 112 | client.repos.getAuthenticatedUserRepos().response({ (result, error) -> Void in 113 | if let repos = result { 114 | print(repos.count) 115 | for i in repos { 116 | print(i.name) 117 | print(i.stargazersCount) 118 | } 119 | } 120 | if let requestError = error { 121 | print(requestError.description) 122 | } 123 | }) 124 | } 125 | 126 | #### Get a repo by repo name and repo owner name 127 | 128 | if let client = Github.authorizedClient { 129 | client.repos.getRepo("Yep", owner: "CatchChat").response({ (result, error) -> Void in 130 | if let repo = result { 131 | print(repo.name) 132 | } 133 | if let requestError = error { 134 | print(requestError.description) 135 | } 136 | }) 137 | } 138 | 139 | #### Get repos belong to a user 140 | 141 | if let client = Github.authorizedClient { 142 | client.repos.getRepoFrom(owner: "onevcat").response({ (nextPage, result, error) -> Void in 143 | if let page = nextPage { 144 | print("Next Page is \(page)") 145 | } 146 | if let repos = result { 147 | print(repos.count) 148 | for r in repos { 149 | print(r.name) 150 | print(r.stargazersCount) 151 | } 152 | } 153 | if let requestError = error { 154 | print(requestError.description) 155 | } 156 | }) 157 | } 158 | 159 | ### Events 160 | 161 | #### Get received events for a user 162 | 163 | if let client = Github.authorizedClient { 164 | client.events.getReceivedEventsForUser("someUser", page: "1").response({ (nextpage, results, error) -> Void in 165 | if let events = results { 166 | // New events 167 | } 168 | }) 169 | } 170 | 171 | # Example 172 | 173 | You could refer to one of my project [GitPocket](https://github.com/jindulys/GitPocket) as an example. 174 | 175 | # Credits 176 | 177 | [SwiftyDropbox](https://github.com/dropbox/SwiftyDropbox) 178 | 179 | # Future Work 180 | 181 | There all tons of other API I haven't implementated, like **Search**. I will continuously make this repo better. Welcome to pull request and open issues. 182 | -------------------------------------------------------------------------------- /Sources/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // GitPocket 4 | // 5 | // Created by yansong li on 2016-02-19. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * Constants used by GithubPilot SDK. 13 | */ 14 | public struct Constants { 15 | /** 16 | * Notification Key 17 | */ 18 | public struct NotificationKey { 19 | public static let GithubAccessTokenRequestSuccess = "GithubAccessTokenRequestSuccess" 20 | public static let GithubAccessTokenRequestFailure = "GithubAccessTokenRequestFailure" 21 | } 22 | 23 | public struct AccessToken { 24 | public static let GithubAccessTokenStorageKey = "GithubAccessTokenStorageKey" 25 | } 26 | 27 | /** 28 | ErrorInfo for GithubPilot 29 | 30 | - RequestOverTime: as the name is 31 | - InvalidInput: this means your input value is invalid, e.g empty inputs. 32 | - InvalidOperation: this means the order of your function call is incorrect, check log info. 33 | */ 34 | public enum ErrorInfo: String { 35 | case RequestOverTime = "GithubPilot Request Over Time" 36 | case InvalidInput = "GithubPilot Invalid Input" 37 | case InvalidOperation = "GithubPilot Invalid Call Order" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Github.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Github.swift 3 | // GitPocket 4 | // 5 | // Created by yansong li on 2016-02-19. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Convenience Class 12 | open class Github { 13 | /// authorizedClient used for the whole app to interact with Github API. 14 | open static var authorizedClient: GithubClient? 15 | /// authenticatedUser is the user who authenticates this client. 16 | open static var authenticatedUser: GithubUser? 17 | 18 | /** 19 | This method should be called at the very first to setup related Object. 20 | 21 | - parameter clientID: your clientID registered at Github Developer Page. 22 | - parameter clientSecret: your clientSecret registered at Github Developer Page. 23 | - parameter scope: scopes you want your client to have. 24 | - parameter redirectURI: unique URL that your client could deal with. 25 | */ 26 | open static func setupClientID(_ clientID: String, clientSecret: String, scope:[String], redirectURI: String) { 27 | if GithubAuthManager.sharedAuthManager != nil { 28 | print(Constants.ErrorInfo.InvalidOperation.rawValue + "Only call `Github.setupClientID` once") 29 | } 30 | GithubAuthManager.sharedAuthManager = GithubAuthManager(clientID: clientID, clientSecret: clientSecret, scope: scope, redirectURI: redirectURI) 31 | 32 | // Call `sharedManager` once, to create this singleton. 33 | _ = GithubManager.sharedManager 34 | } 35 | 36 | /** 37 | Authenticate this client, should be called after `setupClientID(_, clientSecret:,scope:,redirectURI:)` 38 | */ 39 | open static func authenticate() { 40 | if GithubAuthManager.sharedAuthManager == nil { 41 | print(Constants.ErrorInfo.InvalidOperation.rawValue + "Call `Github.setupClientID` before this method") 42 | } 43 | GithubAuthManager.sharedAuthManager.authenticate() 44 | } 45 | 46 | /** 47 | Request AccessToken. 48 | 49 | - parameter url: url returned by Authentication Server, this usually should be called from `application(_, openURL:,sourceApplication:,annotation)` 50 | */ 51 | open static func requestAccessToken(_ url: URL) { 52 | if GithubAuthManager.sharedAuthManager == nil { 53 | print(Constants.ErrorInfo.InvalidOperation.rawValue + "Call `Github.setupClientID` before this method") 54 | } 55 | 56 | GithubAuthManager.sharedAuthManager.requestAccessToken(url) 57 | } 58 | 59 | /** 60 | Unlink this app. 61 | */ 62 | open static func unlink() { 63 | if GithubAuthManager.sharedAuthManager == nil { 64 | print(Constants.ErrorInfo.InvalidOperation.rawValue + "Call `Github.setupClientID` before this method") 65 | } 66 | 67 | if Github.authorizedClient == nil { 68 | return 69 | } 70 | 71 | GithubAuthManager.sharedAuthManager.clearStoredAccessToken() 72 | Github.authorizedClient = nil 73 | } 74 | } 75 | 76 | /// Object used for monitor Notification 77 | class GithubManager: NSObject { 78 | static let sharedManager = GithubManager() 79 | 80 | fileprivate override init() { 81 | super.init() 82 | NotificationCenter.default.addObserver(self, selector:#selector(GithubManager.receivedGithubAccessToken), name: NSNotification.Name(rawValue: Constants.NotificationKey.GithubAccessTokenRequestSuccess), object: nil) 83 | NotificationCenter.default.addObserver(self, selector: #selector(GithubManager.receivedGithubAccessTokenFailure), name: NSNotification.Name(rawValue: Constants.NotificationKey.GithubAccessTokenRequestFailure), object: nil) 84 | } 85 | 86 | /** 87 | Get called when get AccessToken. 88 | */ 89 | func receivedGithubAccessToken() { 90 | if GithubAuthManager.sharedAuthManager == nil { 91 | print(Constants.ErrorInfo.InvalidOperation.rawValue + "Call `Github.setupClientID` before this method") 92 | } 93 | 94 | if Github.authorizedClient != nil { 95 | print(Constants.ErrorInfo.InvalidOperation.rawValue + "Client has already been authorized") 96 | } 97 | 98 | if let accessToken = GithubAuthManager.sharedAuthManager.accessToken { 99 | Github.authorizedClient = GithubClient(accessToken: accessToken) 100 | Github.authorizedClient?.users.getAuthenticatedUser().response({ (result, error) -> Void in 101 | if let user = result { 102 | Github.authenticatedUser = user 103 | } 104 | // TODO: what if we could not get authenticated user, does this matter a lot? 105 | }) 106 | } 107 | } 108 | 109 | /** 110 | Getting AccessToken Failed. 111 | */ 112 | func receivedGithubAccessTokenFailure() { 113 | // TODO: save the error to display to the user 114 | print("Failed to get access token") 115 | } 116 | 117 | deinit { 118 | NotificationCenter.default.removeObserver(self) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/GithubClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GithubClient.swift 3 | // GitPocket 4 | // 5 | // Created by yansong li on 2016-02-19. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | /// Class used for all kinds of request, it manages all the routes. 13 | open class GithubClient: GithubNetWorkClient { 14 | let accessToken: String 15 | 16 | open var users: UsersRoutes! 17 | open var repos: ReposRoutes! 18 | open var events: EventsRoutes! 19 | open var stars: StarsRoutes! 20 | open var searchRepo: GithubSearchRepoRoutes! 21 | 22 | /** 23 | Add additionalHeaders if you want. 24 | 25 | - parameter needoauth: need add accessToken to header. 26 | 27 | - returns: modified header. 28 | */ 29 | open override func additionalHeaders(_ needoauth: Bool) -> [String : String] { 30 | var headers: [String: String] = [:] 31 | if needoauth { 32 | headers["Authorization"] = "token \(accessToken)" 33 | } 34 | return headers 35 | } 36 | 37 | /** 38 | Convenience Initializer 39 | 40 | - parameter accessToken: take an access token to initialize 41 | 42 | - returns: a client that takes charge of all kinds of API Request. 43 | */ 44 | public convenience init(accessToken: String) { 45 | let configuration = URLSessionConfiguration.default 46 | configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders 47 | let manager = Alamofire.SessionManager(configuration: configuration) 48 | manager.startRequestsImmediately = false 49 | self.init(accessToken: accessToken, 50 | manager: manager, 51 | baseHosts: ["api": "https://api.github.com"]) 52 | } 53 | 54 | init(accessToken: String, manager: Alamofire.SessionManager, baseHosts: [String: String]) { 55 | self.accessToken = accessToken 56 | super.init(manager: manager, baseHosts: baseHosts) 57 | self.users = UsersRoutes(client: self) 58 | self.repos = ReposRoutes(client: self) 59 | self.events = EventsRoutes(client: self) 60 | self.stars = StarsRoutes(client: self) 61 | self.searchRepo = GithubSearchRepoRoutes(client: self) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Helpers/Character+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCharacter+Extensions.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-02-21. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Extension: Character 13 | */ 14 | public extension Character { 15 | /** 16 | Convert a Character to unicodeScalar value 17 | e.g turn 'a' to 97 18 | */ 19 | func unicodeScalarCodePoint() -> Int { 20 | let characterString = String(self) 21 | let scalars = characterString.unicodeScalars 22 | 23 | return Int(scalars[scalars.startIndex].value) 24 | } 25 | 26 | /** 27 | Convert a Character to unicodeScalar value based on `0` 28 | e.g turn '0' to 0 29 | */ 30 | func zeroCharacterBasedunicodeScalarCodePoint() -> Int { 31 | return self.unicodeScalarCodePoint() - 48 32 | } 33 | } -------------------------------------------------------------------------------- /Sources/Helpers/Dictionray+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionray+Extensions.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-03-24. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * Protocol for generate query string 13 | */ 14 | public protocol QueryStringGenerator { 15 | /** 16 | protocol func that use any source to generate a query string, this is an abstraction 17 | 18 | - parameter source: source to generate a query string with. 19 | */ 20 | func generateQueryStringWithSource(_ source: Any) -> String 21 | } 22 | 23 | // MARK: - GithubPilot Dictionary Extension 24 | public extension Dictionary { 25 | 26 | /** 27 | Generate a query string from `self` with generator 28 | 29 | - parameter generator: generator that could use dictionary to generate a query String 30 | */ 31 | public func queryStringWithGenerator(_ generator: QueryStringGenerator) -> String { 32 | return generator.generateQueryStringWithSource(self) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Helpers/Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-03-25. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Special URL query escape for `GithubSearch`, this one will not escape `+`, otherwise search could not wokr. 13 | 14 | - parameter string: The string to be percent-escaped. 15 | 16 | - returns: The percent-escaped string. 17 | */ 18 | func githubSearchURLQueryEscape(_ string: String) -> String { 19 | let generalDelimitersToEncode = ":#[]@" 20 | 21 | let subDelimitersToEncode = "!$&'()*,;=" 22 | 23 | let allowedCharacterSet = (CharacterSet.urlQueryAllowed as NSCharacterSet).mutableCopy() as! NSMutableCharacterSet 24 | allowedCharacterSet.removeCharacters(in: generalDelimitersToEncode + subDelimitersToEncode) 25 | 26 | var escaped = "" 27 | if #available(iOS 10.0, OSX 10.12, *) { 28 | escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet as CharacterSet) ?? string 29 | } else { 30 | let batchSize = 50 31 | var index = string.startIndex 32 | 33 | while index != string.endIndex { 34 | let startIndex = index 35 | var endIndex: String.Index 36 | if let batchedEndIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) { 37 | endIndex = batchedEndIndex 38 | } else { 39 | endIndex = string.endIndex 40 | } 41 | let range = (startIndex ..< endIndex) 42 | let substring = string.substring(with: range) 43 | escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet as CharacterSet) ?? substring 44 | index = endIndex 45 | } 46 | } 47 | return escaped 48 | } 49 | 50 | /** 51 | Creates percent-escaped, URL encoded query string components from the given key-value pair. 52 | */ 53 | func githubSearchQueryComponents(_ key: String, _ value: String) -> [(String, String)] { 54 | var components: [(String, String)] = [] 55 | 56 | components.append((githubSearchURLQueryEscape(key), githubSearchURLQueryEscape(value))) 57 | 58 | return components 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Models/GithubEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GithubEvent.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-02-21. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// GithubEventType https://developer.github.com/v3/activity/events/types/ 12 | public enum EventType: String { 13 | case CreateEvent = "CreateEvent" 14 | case CommitCommentEvent = "CommitCommmentEvent" 15 | case DeleteEvent = "DeleteEvent" 16 | case DeploymentEvent = "DeploymentEvent" 17 | case DeploymentStatusEvent = "DeploymentStatusEvent" 18 | case DownloadEvent = "DownloadEvent" 19 | case FollowEvent = "FollowEvent" 20 | case ForkEvent = "ForkEvent" 21 | case ForkApplyEvent = "ForkApplyEvent" 22 | case GistEvent = "GistEvent" 23 | case GollumEvent = "GollumEvent" 24 | case IssueCommentEvent = "IssueCommentEvent" 25 | case IssuesEvent = "IssuesEvent" 26 | case MemberEvent = "MemberEvent" 27 | case MembershipEvent = "MembershipEvent" 28 | case PageBuildEvent = "PageBuildEvent" 29 | case PublicEvent = "PublicEvent" 30 | case PullRequestEvent = "PullRequestEvent" 31 | case PullRequestReviewCommentEvent = "PullRequestReviewCommentEvent" 32 | case PushEvent = "PushEvent" 33 | case ReleaseEvent = "ReleaseEvent" 34 | case RepositoryEvent = "RepositoryEvent" 35 | case StatusEvent = "StatusEvent" 36 | case TeamAddEvent = "TeamAddEvent" 37 | case WatchEvent = "WatchEvent" 38 | 39 | init?(event:String) { 40 | switch event { 41 | case "CreateEvent": 42 | self = .CreateEvent 43 | case "CommitCommentEvent": 44 | self = .CommitCommentEvent 45 | case "DeleteEvent": 46 | self = .DeleteEvent 47 | case "DeploymentEvent": 48 | self = .DeploymentEvent 49 | case "DeploymentStatusEvent": 50 | self = .DeploymentStatusEvent 51 | case "DownloadEvent": 52 | self = .DownloadEvent 53 | case "FollowEvent": 54 | self = .FollowEvent 55 | case "ForkEvent": 56 | self = .ForkEvent 57 | case "ForkApplyEvent": 58 | self = .ForkApplyEvent 59 | case "GistEvent": 60 | self = .GistEvent 61 | case "GollumEvent": 62 | self = .GollumEvent 63 | case "IssueCommentEvent": 64 | self = .IssueCommentEvent 65 | case "IssuesEvent": 66 | self = .IssuesEvent 67 | case "MemberEvent": 68 | self = .MemberEvent 69 | case "MembershipEvent": 70 | self = .MembershipEvent 71 | case "PageBuildEvent": 72 | self = .PageBuildEvent 73 | case "PublicEvent": 74 | self = .PublicEvent 75 | case "PullRequestEvent": 76 | self = .PullRequestEvent 77 | case "PullRequestReviewCommentEvent": 78 | self = .PullRequestReviewCommentEvent 79 | case "PushEvent": 80 | self = .PushEvent 81 | case "ReleaseEvent": 82 | self = .ReleaseEvent 83 | case "RepositoryEvent": 84 | self = .RepositoryEvent 85 | case "StatusEvent": 86 | self = .StatusEvent 87 | case "TeamAddEvent": 88 | self = .TeamAddEvent 89 | case "WatchEvent": 90 | self = .WatchEvent 91 | default: 92 | print("There has \(event)") 93 | return nil 94 | } 95 | } 96 | } 97 | 98 | /// GithubEvent represents a Github Event 99 | open class GithubEvent { 100 | open let id: String 101 | open let type: EventType 102 | open let repo: GithubRepo? 103 | open let actor: GithubUser? 104 | open let createdAt: String 105 | 106 | init(id: String, type: EventType, repo: GithubRepo? = nil, actor: GithubUser? = nil, createdAt: String) { 107 | self.id = id 108 | self.type = type 109 | self.repo = repo 110 | self.actor = actor 111 | self.createdAt = createdAt 112 | } 113 | } 114 | 115 | /// EventSerializer GithubEvent <---> JSON 116 | open class EventSerializer: JSONSerializer { 117 | let userSerializer: GithubUserSerializer 118 | let repoSerializer: RepoSerializer 119 | 120 | public init() { 121 | self.userSerializer = GithubUserSerializer() 122 | self.repoSerializer = RepoSerializer() 123 | } 124 | 125 | /** 126 | GithubEvent -> JSON 127 | */ 128 | open func serialize(_ value: GithubEvent) -> JSON { 129 | let retVal = [ 130 | "id": Serialization._StringSerializer.serialize(value.id), 131 | "type": Serialization._StringSerializer.serialize(value.type.rawValue), 132 | "repo": NullableSerializer(self.repoSerializer).serialize(value.repo), 133 | "actor": NullableSerializer(self.userSerializer).serialize(value.actor), 134 | "created_at": Serialization._StringSerializer.serialize(value.createdAt) 135 | ] 136 | return .dictionary(retVal) 137 | } 138 | 139 | /** 140 | JSON -> GithubEvent 141 | */ 142 | open func deserialize(_ json: JSON) -> GithubEvent { 143 | switch json { 144 | case .dictionary(let dict): 145 | let id = Serialization._StringSerializer.deserialize(dict["id"] ?? .null) 146 | let type = EventType(event: Serialization._StringSerializer.deserialize(dict["type"] ?? .null))! 147 | let repo = NullableSerializer(self.repoSerializer).deserialize(dict["repo"] ?? .null) 148 | let actor = NullableSerializer(self.userSerializer).deserialize(dict["actor"] ?? .null) 149 | let createdAt = Serialization._StringSerializer.deserialize(dict["created_at"] ?? .null) 150 | return GithubEvent(id: id, type: type, repo: repo, actor: actor, createdAt: createdAt) 151 | default: 152 | fatalError("Github Event JSON Type Error") 153 | } 154 | } 155 | } 156 | 157 | /// Event Array Serializer, which deal with array. 158 | open class EventArraySerializer: JSONSerializer { 159 | let eventSerializer: EventSerializer 160 | init() { 161 | self.eventSerializer = EventSerializer() 162 | } 163 | 164 | /** 165 | [GithubEvent] -> JSON 166 | */ 167 | open func serialize(_ value: [GithubEvent]) -> JSON { 168 | let users = value.map { self.eventSerializer.serialize($0) } 169 | return .array(users) 170 | } 171 | 172 | /** 173 | JSON -> [GithubEvent] 174 | */ 175 | open func deserialize(_ json: JSON) -> [GithubEvent] { 176 | switch json { 177 | case .array(let users): 178 | return users.map { self.eventSerializer.deserialize($0) } 179 | default: 180 | fatalError("JSON Type should be array") 181 | } 182 | } 183 | } 184 | 185 | -------------------------------------------------------------------------------- /Sources/OAuth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuth.swift 3 | // GitPocket 4 | // 5 | // Created by yansong li on 2016-02-19. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | /** 13 | GithubAuthResult Enum 14 | 15 | - Success: Success 16 | - Error: Error 17 | */ 18 | public enum GithubAuthResult { 19 | case success(String) 20 | case error(String) 21 | } 22 | 23 | /// GithubAuthManager 24 | open class GithubAuthManager { 25 | let clientID: String 26 | let clientSecret: String 27 | let redirectURI: String 28 | let oAuthRouter: GithubAuthenticationRoutes 29 | let oAuthClient: GithubNetWorkClient 30 | let scope: [String] 31 | 32 | var code: String? 33 | var accessToken: String? 34 | var oAuthResult: GithubAuthResult? 35 | 36 | open static var sharedAuthManager: GithubAuthManager! 37 | 38 | init(clientID: String, clientSecret: String, scope:[String], redirectURI: String) { 39 | self.clientID = clientID 40 | self.clientSecret = clientSecret 41 | self.redirectURI = redirectURI 42 | self.scope = scope 43 | let configuration = URLSessionConfiguration.default 44 | configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders 45 | let manager = Alamofire.SessionManager(configuration:configuration) 46 | manager.startRequestsImmediately = false 47 | self.oAuthClient = GithubNetWorkClient(manager: manager, 48 | baseHosts: [ 49 | "login": "https://github.com", 50 | "api": "https://api.github.com"]) 51 | self.oAuthRouter = GithubAuthenticationRoutes(client: self.oAuthClient) 52 | } 53 | 54 | /** 55 | Authenticate client with Github authrization server. This is the first step of 56 | Github OAuth procedure. 57 | */ 58 | open func authenticate() { 59 | if let accessToken = DefaultStorage.get(key: Constants.AccessToken.GithubAccessTokenStorageKey) as? String { 60 | self.accessToken = accessToken 61 | self.oAuthResult = .success(accessToken) 62 | NotificationCenter.default.post(name: Notification.Name(rawValue: Constants.NotificationKey.GithubAccessTokenRequestSuccess), object: nil) 63 | } else { 64 | self.oAuthRouter.requestAuthentication(self.scope, clientID: self.clientID, redirectURI: self.redirectURI) 65 | } 66 | } 67 | 68 | /** 69 | Request AccessToken 70 | 71 | - parameter url: url with `code` value, got from Authentication Step. 72 | */ 73 | open func requestAccessToken(_ url: URL) { 74 | guard let code = url.query?.components(separatedBy: "code=").last else { return } 75 | self.oAuthRouter.requestAccessToken(self.clientID, clientSecret: self.clientSecret, code: code) { (tokenString, requestError) -> Void in 76 | if let error = requestError { 77 | self.oAuthResult = .error(error.description) 78 | NotificationCenter.default.post(name: Notification.Name(rawValue: Constants.NotificationKey.GithubAccessTokenRequestFailure), object: nil) 79 | return 80 | } 81 | if let result = tokenString { 82 | let components = result.components(separatedBy: "&") 83 | componentLoop: for component in components { 84 | let items = component.components(separatedBy: "=") 85 | var isToken = false 86 | itemLoop: for item in items { 87 | if isToken { 88 | self.accessToken = item 89 | self.oAuthResult = .success(item) 90 | // Clear storaged access token 91 | DefaultStorage.clear(key: Constants.AccessToken.GithubAccessTokenStorageKey) 92 | // Save access token 93 | DefaultStorage.save(item as AnyObject, 94 | withKey: Constants.AccessToken.GithubAccessTokenStorageKey) 95 | break componentLoop 96 | } 97 | 98 | if item == "access_token" { 99 | isToken = true 100 | } 101 | } 102 | } 103 | NotificationCenter.default.post(name: Notification.Name(rawValue: Constants.NotificationKey.GithubAccessTokenRequestSuccess), object: nil) 104 | } 105 | } 106 | } 107 | 108 | /** 109 | Clear AccessToken. 110 | */ 111 | open func clearStoredAccessToken() { 112 | DefaultStorage.clear(key: Constants.AccessToken.GithubAccessTokenStorageKey) 113 | } 114 | } 115 | 116 | 117 | /** 118 | * Protocol for PersistentStorage 119 | */ 120 | protocol PersistentStorage { 121 | associatedtype valueType 122 | associatedtype keyType 123 | 124 | static func save(_ info: valueType, withKey key: keyType) -> Void 125 | static func get(key: keyType) -> AnyObject? 126 | static func clear(key: keyType) -> Void 127 | } 128 | 129 | /// DefaultStorage use NSDefault to save information 130 | class DefaultStorage: PersistentStorage { 131 | /** 132 | Save info with key to UserDefaults 133 | 134 | - parameter info: info to be saved. 135 | - parameter key: key. 136 | */ 137 | class func save(_ info: AnyObject, withKey key: String) -> Void { 138 | let defaults = UserDefaults.standard 139 | defaults.set(info, forKey: key) 140 | } 141 | 142 | /** 143 | Get info for a key from UserDefaults 144 | 145 | - parameter key: key 146 | 147 | - returns: related value or nil. 148 | */ 149 | class func get(key: String) -> AnyObject? { 150 | let defaults = UserDefaults.standard 151 | if let value = defaults.object(forKey: key) { 152 | return value as AnyObject? 153 | } 154 | return nil 155 | } 156 | 157 | /** 158 | Remove one key from UserDefaults 159 | 160 | - parameter key: key to be cleaned. 161 | */ 162 | class func clear(key: String) { 163 | let defaults = UserDefaults.standard 164 | defaults.removeObject(forKey: key) 165 | } 166 | } 167 | 168 | -------------------------------------------------------------------------------- /Sources/Routes/AuthenticationRoutes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthenticationRoutes.swift 3 | // GitPocket 4 | // 5 | // Created by yansong li on 2016-02-18. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | /// A Simple Class Represent one Authentication Information 13 | struct GithubAuthentication { 14 | let id: Int32 15 | let token: String 16 | let hashedToken: String 17 | } 18 | 19 | /// Serializer used for Authentication 20 | class GithubAuthenticationSerializer: JSONSerializer { 21 | init() { } 22 | 23 | func serialize(_ value: GithubAuthentication) -> JSON { 24 | var retVal:[String: JSON] = [:] 25 | retVal["id"] = Serialization._Int32Serializer.serialize(value.id) 26 | retVal["token"] = Serialization._StringSerializer.serialize(value.token) 27 | retVal["hashed_token"] = Serialization._StringSerializer.serialize(value.hashedToken) 28 | return .dictionary(retVal) 29 | } 30 | 31 | func deserialize(_ json: JSON) -> GithubAuthentication { 32 | switch json { 33 | case .dictionary(let dict): 34 | let id = Serialization._Int32Serializer.deserialize(dict["id"] ?? .null) 35 | let token = Serialization._StringSerializer.deserialize(dict["token"] ?? .null) 36 | let hashedToken = Serialization._StringSerializer.deserialize(dict["hashed_token"] ?? .null) 37 | return GithubAuthentication(id: id, token: token, hashedToken: hashedToken) 38 | default: 39 | fatalError("Wrong Type") 40 | } 41 | } 42 | } 43 | 44 | enum AuthorizationError: CustomStringConvertible { 45 | case invalidParameter 46 | case httpError(String) 47 | case unknownResponse(String) 48 | 49 | var description: String { 50 | switch self { 51 | case .invalidParameter: 52 | return "Invalid Parameter please check your parameter format" 53 | case .httpError(let error): 54 | return "HTTP Error :\(error)" 55 | case .unknownResponse(let error): 56 | return "Unknown response :\(error)" 57 | } 58 | } 59 | } 60 | 61 | /// Router used for Authentication purpose. 62 | 63 | /// This Router is different from other router since the response for this one is not a JSON Format, although we could change this behavior by set HTTP Header fields. 64 | class GithubAuthenticationRoutes { 65 | unowned let client: GithubNetWorkClient 66 | init(client: GithubNetWorkClient) { 67 | self.client = client 68 | } 69 | 70 | /** 71 | Request Authentication 72 | 73 | - parameter scopes: scopes used by your client app 74 | - parameter clientID: clientID 75 | - parameter redirectURI: redirectURI should be a unique scheme that your application could deal with. 76 | */ 77 | func requestAuthentication(_ scopes:[String], clientID: String, redirectURI: String) { 78 | guard let login = self.client.baseHosts["login"] else { return } 79 | let path = "/login/oauth/authorize" 80 | // TODO: optimize params generate process, use a cooler way. 81 | let urlString = "\(login)\(path)?client_id=\(clientID)&redirect_uri=\(redirectURI)&scope=\(scopes.joined(separator: ","))" 82 | if let url = URL(string: urlString) { 83 | UIApplication.shared.openURL(url) 84 | } else { 85 | fatalError("Client should have login URL") 86 | } 87 | } 88 | 89 | // TODO: deal with error, might be create error ENUM for authentication 90 | /** 91 | Request to access Token 92 | 93 | - parameter clientID: ClientID, required 94 | - parameter clientSecret: ClientSecret, required 95 | - parameter code: the code you get from authentication 96 | - parameter complitionHandler: complitionHandler will return a string contains access_token, or an Error 97 | */ 98 | func requestAccessToken(_ clientID: String, 99 | clientSecret: String, 100 | code: String, 101 | complitionHandler: @escaping (String?, AuthorizationError?) ->Void) { 102 | let url = "\(self.client.baseHosts["login"]!)/login/oauth/access_token" 103 | let accessTokenRequest = 104 | "client_id=\(clientID)&client_secret=\(clientSecret)&code=\(code)" 105 | guard let postData = accessTokenRequest.data(using: String.Encoding.ascii, allowLossyConversion: true) else { 106 | return complitionHandler(nil,.invalidParameter) 107 | } 108 | 109 | Alamofire.request(url, method: .post, 110 | parameters: ["":""], 111 | encoding: DataPostEncoding(data: postData), 112 | headers: nil).response { response in 113 | let d = response.data! 114 | if let error = response.error, let response = response.response { 115 | complitionHandler(nil, .httpError("Request Error, code: \(response.statusCode) description:\(error.localizedDescription)")) 116 | } else { 117 | if let tokenResponse = NSString(data:d, encoding: String.Encoding.ascii.rawValue) as? String { 118 | complitionHandler(tokenResponse, nil) 119 | } else { 120 | complitionHandler(nil,.unknownResponse("could not decode response data")) 121 | } 122 | } 123 | } 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/Routes/EventsRoutes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventsRoutes.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-02-21. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// Routes responsible for Events related request. 12 | open class EventsRoutes { 13 | open unowned let client: GithubNetWorkClient 14 | 15 | init(client: GithubNetWorkClient) { 16 | self.client = client 17 | } 18 | 19 | /** 20 | Events that a user has received 21 | 22 | - parameter name: user 23 | - parameter page: when user has a lot of repos, pagination will be applied. 24 | 25 | - returns: an RpcRequest, whose response result contains `[GithubEvent]`, if pagination is applicable, response result contains `nextpage`. 26 | */ 27 | open func getReceivedEventsForUser(_ name: String, page: String = "1") -> RpcCustomResponseRequest { 28 | if name.characters.count == 0 { 29 | print(Constants.ErrorInfo.InvalidInput.rawValue) 30 | } 31 | return RpcCustomResponseRequest(client: self.client, 32 | host: "api", 33 | route: "/users/\(name)/received_events", 34 | method: .get, 35 | params: ["page":page], 36 | postParams: nil, 37 | postData: nil, 38 | customResponseHandler:GPHttpResponseHandler.PageHandler, 39 | responseSerializer: EventArraySerializer(), 40 | errorSerializer: StringSerializer()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Routes/ReposRoutes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReposRoutes.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-02-21. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | open class ReposRoutes { 12 | open unowned let client: GithubNetWorkClient 13 | init(client: GithubNetWorkClient) { 14 | self.client = client 15 | } 16 | 17 | /** 18 | Get current authenticated user's repo. 19 | 20 | - returns: an RpcRequest, whose response result contains `[GithubRepo]`. 21 | */ 22 | open func getAuthenticatedUserRepos() -> RpcRequest { 23 | return RpcRequest(client: self.client, 24 | host: "api", 25 | route: "/user/repos", 26 | method: .get, 27 | responseSerializer: RepoArraySerializer(), 28 | errorSerializer: StringSerializer()) 29 | } 30 | 31 | /** 32 | Get a specific repo with repo name and repo owner name 33 | 34 | - parameter name: this repo's name 35 | - parameter owner: repo's owner name 36 | 37 | - returns: an RpcRequest, whose response result contain `GithubRepo`. 38 | */ 39 | open func getRepo(_ name: String, owner: String) -> RpcRequest { 40 | if name.characters.count == 0 || owner.characters.count == 0 { 41 | print(Constants.ErrorInfo.InvalidInput.rawValue) 42 | } 43 | 44 | return RpcRequest(client: self.client, 45 | host: "api", 46 | route: "/repos/\(owner)/\(name)", 47 | method: .get, 48 | responseSerializer: RepoSerializer(), 49 | errorSerializer: StringSerializer()) 50 | } 51 | 52 | /** 53 | List public repositories for the specified user. 54 | 55 | - parameter owner: user name 56 | - parameter page: when user has a lot of repos, pagination will be applied. 57 | 58 | - returns: an RpcRequest, whose response result contains `[GithubRepo]`, if pagination is applicable, response result contains `nextpage`. 59 | 60 | - note: Note that page numbering is 1-based and that omitting the ?page parameter will return the first page. 61 | */ 62 | open func getRepoFrom(owner: String, page: String = "1") -> RpcCustomResponseRequest { 63 | if owner.characters.count == 0 { 64 | print(Constants.ErrorInfo.InvalidInput.rawValue) 65 | } 66 | 67 | return RpcCustomResponseRequest(client: self.client, 68 | host: "api", 69 | route: "/users/\(owner)/repos", 70 | method: .get, 71 | params: ["page":page], 72 | postParams: nil, 73 | postData: nil, 74 | customResponseHandler:GPHttpResponseHandler.PageHandler, 75 | responseSerializer: RepoArraySerializer(), 76 | errorSerializer: StringSerializer()) 77 | } 78 | 79 | /** 80 | Use API URL to get the full information of Repo 81 | 82 | - parameter url: api URL 83 | 84 | - returns: Request Object you could get the full information from its successful serializer. 85 | */ 86 | open func getAPIRepo(url: String) -> DirectAPIRequest { 87 | if url.characters.count == 0 { 88 | print(Constants.ErrorInfo.InvalidInput.rawValue) 89 | } 90 | 91 | return DirectAPIRequest(client: self.client, 92 | apiURL: url, 93 | method: .get, 94 | responseSerializer: RepoSerializer(), 95 | errorSerializer: StringSerializer()) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Routes/SearchRoutes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchRoutes.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-03-23. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | 13 | /** 14 | Enumeration for Github Search Query Parameters 15 | */ 16 | public enum GitHubSearchCondition: String { 17 | // Comm Search Condition 18 | case Within = "in" 19 | case Size = "size" 20 | case Fork = "fork" 21 | case User = "user" 22 | case Repo = "repo" 23 | case Language = "language" 24 | 25 | // Sepecific for Search Repo 26 | case Forks = "forks" 27 | case Created = "created" 28 | case Pushed = "pushed" 29 | case Stars = "stars" 30 | 31 | // Sepecific for Search Code 32 | case Extension = "extension" 33 | case FileName = "filename" 34 | case Path = "path" 35 | 36 | // Sepecific for Search Users 37 | case `Type` = "type" 38 | case Repos = "repos" 39 | case Location = "location" 40 | case Followers = "followers" 41 | } 42 | 43 | /** 44 | * Github Search Specific Query Generator 45 | */ 46 | struct GithubSearchQueryGenerator: QueryStringGenerator { 47 | 48 | /** 49 | GithubSearch Query Generator 50 | 51 | - parameter source: dictionary with `GithubSearchCondition: String` 52 | 53 | - returns: format like `language:Swift+repo:leetcode` 54 | */ 55 | func generateQueryStringWithSource(_ source: Any) -> String { 56 | var retVal = "" 57 | 58 | if let conditionDic = source as? [GitHubSearchCondition: String] { 59 | var queryPair: [String] = [] 60 | for (condition, value) in conditionDic { 61 | let pair = condition.rawValue + ":" + value 62 | queryPair.append(pair) 63 | } 64 | 65 | retVal = queryPair.joined(separator: "+") 66 | } 67 | 68 | return retVal 69 | } 70 | } 71 | 72 | internal struct URLQueryEncoding: ParameterEncoding { 73 | func query(_ parameters: [String: String]) -> String { 74 | var components: [(String, String)] = [] 75 | 76 | for key in parameters.keys.sorted(by: <) { 77 | let value = parameters[key]! 78 | components += githubSearchQueryComponents(key, value) 79 | } 80 | 81 | return (components.map { "\($0)=\($1)" } as [String]).joined(separator: "&") 82 | } 83 | 84 | func encode(_ urlRequest: URLRequestConvertible, 85 | with parameters: Parameters?) throws -> URLRequest { 86 | guard var urlRequest = urlRequest.urlRequest else { 87 | throw GithubRequestError.InvalidRequest 88 | } 89 | if let URLComponents = NSURLComponents(url: urlRequest.url!, resolvingAgainstBaseURL: false), 90 | let validParams = parameters as? [String: String] { 91 | let percentEncodedQuery = (URLComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(validParams) 92 | URLComponents.percentEncodedQuery = percentEncodedQuery 93 | urlRequest.url = URLComponents.url 94 | } 95 | return urlRequest 96 | } 97 | } 98 | 99 | /// Github Search Repo Routes 100 | open class GithubSearchRepoRoutes { 101 | 102 | /** 103 | The sort field. One of stars, forks, or updated. Default: results are sorted by best match. 104 | */ 105 | public enum SearchRepoSort: String { 106 | case Stars = "stars" 107 | case Forks = "forks" 108 | case Updated = "updated" 109 | } 110 | 111 | /** 112 | The sort order if sort parameter is provided. One of asc or desc. Default: desc 113 | */ 114 | public enum SearchRepoOrder: String { 115 | case Asc = "asc" 116 | case Desc = "desc" 117 | } 118 | 119 | open unowned let client: GithubNetWorkClient 120 | init(client: GithubNetWorkClient) { 121 | self.client = client 122 | } 123 | 124 | /** 125 | Search Repo for a topic 126 | 127 | - parameter topic: topic to search 128 | - parameter sort: sort type 129 | - parameter order: order type 130 | - parameter conditionDict: additional search condition [GitHubSearchCondition: String] 131 | - parameter page: page info 132 | 133 | - returns: rpc request 134 | */ 135 | open func searchRepoForTopic(_ topic: String, 136 | sort: SearchRepoSort = .Updated, 137 | order: SearchRepoOrder = .Desc, 138 | conditionDict: [GitHubSearchCondition: String]? = nil, 139 | page: String = "1") -> RpcCustomResponseRequest { 140 | if topic.characters.count == 0 { 141 | print(Constants.ErrorInfo.InvalidInput.rawValue) 142 | } 143 | var topicQuery = topic 144 | if let conditions = conditionDict { 145 | topicQuery += "+" + conditions.queryStringWithGenerator(GithubSearchQueryGenerator()) 146 | } 147 | 148 | 149 | return RpcCustomResponseRequest(client: self.client, 150 | host: "api", 151 | route: "/search/repositories", 152 | method: .get, 153 | params: ["q":topicQuery, "sort": sort.rawValue, "order": order.rawValue, "page":page], 154 | postParams: nil, 155 | postData: nil, 156 | encoding: URLQueryEncoding(), 157 | customResponseHandler: GPHttpResponseHandler.PageHandler, 158 | responseSerializer: SearchResultSerializer(), 159 | errorSerializer: StringSerializer()) 160 | } 161 | } 162 | 163 | /// RepoArraySerializer 164 | open class SearchResultSerializer: JSONSerializer { 165 | let reposSerializer: RepoArraySerializer 166 | init() { 167 | self.reposSerializer = RepoArraySerializer() 168 | } 169 | 170 | /** 171 | descriptions 172 | */ 173 | open func serialize(_ value: [GithubRepo]) -> JSON { 174 | return .null 175 | } 176 | 177 | /** 178 | JSON -> [GithubRepo] 179 | */ 180 | open func deserialize(_ json: JSON) -> [GithubRepo] { 181 | switch json { 182 | case .dictionary(let infoDict): 183 | var retVal: [GithubRepo] = [] 184 | if let items = infoDict["items"] { 185 | retVal = self.reposSerializer.deserialize(items) 186 | } 187 | 188 | return retVal 189 | default: 190 | fatalError("JSON Type should be array") 191 | } 192 | } 193 | } 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /Sources/Routes/StarsRoutes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StarsRoutes.swift 3 | // Pods 4 | // 5 | // Created by yansong li on 2016-02-22. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// Routes for Stars related request. 12 | open class StarsRoutes { 13 | open unowned let client: GithubNetWorkClient 14 | // This queue is used for some long time task to stay around, espicially pagination operation. 15 | let longTimeWaitQueue: DispatchQueue 16 | 17 | init(client: GithubNetWorkClient) { 18 | self.client = client 19 | self.longTimeWaitQueue = DispatchQueue(label: "com.githubpilot.stargazersRoutes.waitingQueue", attributes: []) 20 | } 21 | 22 | /** 23 | Users that stars a repo belongs to a user. 24 | 25 | - parameter repo: repo name 26 | - parameter name: owner 27 | - parameter page: when user has a lot of repos, pagination will be applied. 28 | 29 | - returns: an RpcRequest, whose response result contains `[GithubUser]`, if pagination is applicable, response result contains `nextpage`. 30 | */ 31 | open func getStargazersFor(repo: String, 32 | owner: String, 33 | page: String = "1", 34 | defaultResponseQueue: DispatchQueue? = nil) -> RpcCustomResponseRequest { 35 | if repo.characters.count == 0 || owner.characters.count == 0 { 36 | print("Repo name and Owner name must not be empty") 37 | } 38 | return RpcCustomResponseRequest(client: self.client, 39 | host: "api", 40 | route: "/repos/\(owner)/\(repo)/stargazers", 41 | method: .get, 42 | params: ["page":page], 43 | postParams: nil, 44 | postData: nil, 45 | customResponseHandler: GPHttpResponseHandler.PageHandler, 46 | defaultResponseQueue: defaultResponseQueue, 47 | responseSerializer: UserArraySerializer(), 48 | errorSerializer: StringSerializer()) 49 | } 50 | 51 | /** 52 | Get all the stargazers belong to a owner's repo. 53 | 54 | - note: This request is time consuming if this repo is a quite popular one. but it will run on a private serial queue and will not block main queue. 55 | 56 | - parameter repo: repo's name. 57 | - parameter owner: owner's name. 58 | - parameter complitionHandler: callback that call on main thread. 59 | */ 60 | fileprivate func getAllStargazersOldFor(repo: String, 61 | owner: String, 62 | complitionHandler:@escaping ([GithubUser]?, String?)-> Void) { 63 | self.longTimeWaitQueue.async { () -> Void in 64 | let privateQueue = DispatchQueue(label: "com.githubpilot.stargazersRoutes.responseQueue", attributes: []) 65 | var retVal: [GithubUser] = [] 66 | var retError: String? 67 | let semaphore = DispatchSemaphore(value: 0) 68 | var recursiveStargazers: (String, String, String, DispatchQueue?) -> Void = {_, _, _, _ in } 69 | recursiveStargazers = { 70 | repo, owner, page, queue in 71 | self.getStargazersFor(repo: repo, 72 | owner: owner, 73 | page: page, 74 | defaultResponseQueue: queue).response { 75 | (nextPage, result, error) -> Void in 76 | if let error = error { 77 | retError = error.description 78 | semaphore.signal() 79 | } 80 | 81 | if let users = result { 82 | retVal.append(contentsOf: users) 83 | } 84 | 85 | if let vpage = nextPage { 86 | if vpage == "1" { 87 | semaphore.signal() 88 | } else { 89 | recursiveStargazers(repo, owner, vpage, queue) 90 | } 91 | } 92 | } 93 | } 94 | 95 | recursiveStargazers(repo, owner, "1", privateQueue) 96 | let timeoutTime = DispatchTime.now() + Double(Int64(100 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC) 97 | if semaphore.wait(timeout: timeoutTime) == .timedOut { 98 | retError = Constants.ErrorInfo.RequestOverTime.rawValue 99 | } 100 | DispatchQueue.main.async(execute: { () -> Void in 101 | complitionHandler(retVal, retError) 102 | }) 103 | } 104 | } 105 | 106 | /** 107 | Get all the stargazers belong to a owner's repo. 108 | 109 | - note: This request is time consuming if this repo is a quite popular one. but it will run on a private serial queue and will not block main queue. 110 | 111 | - parameter repo: repo's name. 112 | - parameter owner: owner's name. 113 | - parameter complitionHandler: callback that call on main thread. 114 | */ 115 | open func getAllStargazersFor(repo: String, 116 | owner: String, 117 | complitionHandler: @escaping ([GithubUser]?, String?) -> Void) { 118 | var recursiveStargazers: (String, String, String) -> Void = {_, _, _ in } 119 | var retVal: [GithubUser] = [] 120 | recursiveStargazers = { 121 | repo, owner, page in 122 | self.getStargazersFor(repo: repo, owner: owner, page: page).response { 123 | (nextPage, result, error) -> Void in 124 | guard let users = result, let vpage = nextPage else { 125 | complitionHandler(nil, error?.description ?? "Error,Could not finish this request") 126 | return 127 | } 128 | 129 | retVal.append(contentsOf: users) 130 | if vpage == "1" { 131 | complitionHandler(retVal, nil) 132 | } else { 133 | recursiveStargazers(repo, owner, vpage) 134 | } 135 | } 136 | } 137 | recursiveStargazers(repo, owner, "1") 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Sources/Routes/UsersRoutes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserRoutes.swift 3 | // GitPocket 4 | // 5 | // Created by yansong li on 2016-02-17. 6 | // Copyright © 2016 yansong li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Routes for User related Request. 12 | open class UsersRoutes { 13 | open unowned let client: GithubNetWorkClient 14 | init(client: GithubNetWorkClient) { 15 | self.client = client 16 | } 17 | 18 | /** 19 | Get a single user. 20 | 21 | - parameter username: a Github user's username. 22 | 23 | - returns: an RpcRequest, whose response result is `GithubUser`. 24 | */ 25 | open func getUser(username: String) -> RpcRequest { 26 | return RpcRequest(client: self.client, 27 | host: "api", 28 | route: "/users/\(username)", 29 | method: .get, 30 | responseSerializer: GithubUserSerializer(), 31 | errorSerializer: StringSerializer()) 32 | } 33 | 34 | /** 35 | Get followers for user. 36 | 37 | - parameter user: a Github user's username. 38 | 39 | - parameter page: a specific page to query. 40 | 41 | - returns: an RpcRequest, where contains an array of followers. 42 | */ 43 | open func getFollowersFor(user: String, page: String = "1") -> RpcCustomResponseRequest { 44 | return RpcCustomResponseRequest(client: self.client, 45 | host: "api", 46 | route: "/users/\(user)/followers", 47 | method: .get, 48 | params: ["page" : page], 49 | postParams: nil, 50 | postData: nil, 51 | customResponseHandler: GPHttpResponseHandler.PageHandler, 52 | responseSerializer: UserArraySerializer(), 53 | errorSerializer: StringSerializer()) 54 | 55 | } 56 | 57 | /** 58 | Get following for user. 59 | 60 | - parameter user: a Github user's username. 61 | 62 | - parameter page: a specific page to query. 63 | 64 | - returns: an RpcRequest, where contains an array of followers. 65 | */ 66 | open func getFollowingFor(user: String, page: String = "1") -> RpcCustomResponseRequest { 67 | return RpcCustomResponseRequest(client: self.client, 68 | host: "api", 69 | route: "/users/\(user)/following", 70 | method: .get, 71 | params: ["page" : page], 72 | postParams: nil, 73 | postData: nil, 74 | customResponseHandler: GPHttpResponseHandler.PageHandler, 75 | responseSerializer: UserArraySerializer(), 76 | errorSerializer: StringSerializer()) 77 | } 78 | 79 | /** 80 | Get current authenticated user. 81 | 82 | - returns: an RpcRequest, whose response result is `GithubUser`. 83 | */ 84 | open func getAuthenticatedUser() -> RpcRequest { 85 | return RpcRequest(client: self.client, 86 | host: "api", 87 | route: "/user", 88 | method: .get, 89 | responseSerializer: GithubUserSerializer(), 90 | errorSerializer: StringSerializer()) 91 | } 92 | 93 | /** 94 | Get all Users, this is a pagination request, you could get `since` which represent next page of users' start ID from `.response` complitionHandler's first paramete 95 | 96 | - parameter since: The integer ID of the last User that you've seen, you could get this from `.response` complitionHandler's first parameter 97 | 98 | - returns: an RpcCustomResponseRequest 99 | */ 100 | open func getAllUsers(_ since: String) -> RpcCustomResponseRequest { 101 | if since.characters.count == 0 { 102 | print(Constants.ErrorInfo.InvalidInput.rawValue) 103 | } 104 | let params = ["since": since] 105 | return RpcCustomResponseRequest(client: self.client, 106 | host: "api", 107 | route: "/users", 108 | method: .get, 109 | params: params, 110 | postParams: nil, 111 | postData: nil, 112 | customResponseHandler: GPHttpResponseHandler.SinceHandler, 113 | responseSerializer: UserArraySerializer(), 114 | errorSerializer: StringSerializer()) 115 | } 116 | 117 | /** 118 | Get a user's full information through API url. 119 | 120 | - parameter url: user's api url, e.g `https://api.github.com/users/octocat`. 121 | 122 | - returns: a DirectAPIRequest, which you could use to get user's info through `response` method. 123 | */ 124 | open func getAPIUser(url: String) -> DirectAPIRequest { 125 | if url.characters.count == 0 { 126 | print("GithubPilotError Invalid input") 127 | } 128 | return DirectAPIRequest(client: self.client, 129 | apiURL: url, 130 | method: .get, 131 | responseSerializer: GithubUserSerializer(), 132 | errorSerializer: StringSerializer()) 133 | } 134 | 135 | /** 136 | Get a list of users' full information 137 | 138 | - parameter userAPIURLs: a list of url contains userAPIURL, which could be used to fetch for the full information 139 | - parameter complitionHandler: callback when all users get fetched, contains full information of users. 140 | */ 141 | open func getFullUsers(_ userAPIURLs: [String], complitionHandler:@escaping ([GithubUser]?)->Void) { 142 | let fetchUserGroup = DispatchGroup() 143 | var results: [GithubUser] = [] 144 | for url in userAPIURLs { 145 | if url.characters.count > 0 { 146 | // Enter group 147 | fetchUserGroup.enter() 148 | getAPIUser(url: url).response({ (result, error) -> Void in 149 | if let fetchError = error { 150 | print("Meeet an error \(fetchError)") 151 | fetchUserGroup.leave() 152 | } 153 | 154 | if let user = result { 155 | results.append(user) 156 | fetchUserGroup.leave() 157 | } 158 | }) 159 | } 160 | } 161 | 162 | fetchUserGroup.notify(queue: DispatchQueue.main) { () -> Void in 163 | complitionHandler(results) 164 | } 165 | } 166 | } 167 | --------------------------------------------------------------------------------