Skip to content

Commit

Permalink
Merge pull request #113 from vtourraine/fetch-license
Browse files Browse the repository at this point in the history
Get missing licenses from GitHub API
  • Loading branch information
vtourraine authored Dec 13, 2023
2 parents e1e4e0a + 9eafde2 commit a2df5c3
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 10 deletions.
8 changes: 8 additions & 0 deletions AcknowList.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
32A5DE4625C7DD3D00ED11BB /* AcknowListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A5DE3F25C7DD3D00ED11BB /* AcknowListViewController.swift */; };
32A5DE4825C7DD3D00ED11BB /* AcknowLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A5DE4125C7DD3D00ED11BB /* AcknowLocalization.swift */; };
503E61E9212997CE00322F6C /* Pods-acknowledgements-RegexTesting.plist in Resources */ = {isa = PBXBuildFile; fileRef = 503E61E8212997CD00322F6C /* Pods-acknowledgements-RegexTesting.plist */; };
BB8545732B227D39001BF421 /* GitHubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8545722B227D39001BF421 /* GitHubAPI.swift */; };
BB8545752B227D49001BF421 /* GitHubAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8545742B227D49001BF421 /* GitHubAPITests.swift */; };
D70473D225CC4DB0004F2BEC /* Pods-acknowledgements.plist in Resources */ = {isa = PBXBuildFile; fileRef = D70473D125CC4DB0004F2BEC /* Pods-acknowledgements.plist */; };
D70473E525CC530E004F2BEC /* AcknowListTestsHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70473E425CC530E004F2BEC /* AcknowListTestsHelpers.swift */; };
D705FE2B268A0B0600B501D7 /* AcknowList.docc in Sources */ = {isa = PBXBuildFile; fileRef = D705FE2A268A0B0600B501D7 /* AcknowList.docc */; };
Expand Down Expand Up @@ -60,6 +62,8 @@
32A5DE4125C7DD3D00ED11BB /* AcknowLocalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcknowLocalization.swift; sourceTree = "<group>"; };
32A5DE5225C7E2FB00ED11BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
503E61E8212997CD00322F6C /* Pods-acknowledgements-RegexTesting.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Pods-acknowledgements-RegexTesting.plist"; sourceTree = "<group>"; };
BB8545722B227D39001BF421 /* GitHubAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubAPI.swift; sourceTree = "<group>"; };
BB8545742B227D49001BF421 /* GitHubAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubAPITests.swift; sourceTree = "<group>"; };
D70473D125CC4DB0004F2BEC /* Pods-acknowledgements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Pods-acknowledgements.plist"; sourceTree = "<group>"; };
D70473E425CC530E004F2BEC /* AcknowListTestsHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcknowListTestsHelpers.swift; sourceTree = "<group>"; };
D705FE2A268A0B0600B501D7 /* AcknowList.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = AcknowList.docc; sourceTree = "<group>"; };
Expand Down Expand Up @@ -145,6 +149,7 @@
1420A2E31CAEC92200D98F9C /* AcknowLocalizationTests.swift */,
1A7421731CAC3685007E44FD /* AcknowParserTests.swift */,
1A7421741CAC3685007E44FD /* AcknowViewControllerTests.swift */,
BB8545742B227D49001BF421 /* GitHubAPITests.swift */,
D70B4286212B2EFF007B6A81 /* Resources */,
1AD73B8F1CAC3AEB0084F8CA /* Supporting Files */,
);
Expand Down Expand Up @@ -182,6 +187,7 @@
D7A68B462833C2D30064C2C0 /* AcknowPodDecoder.swift */,
D78335EE2628934800A380A3 /* AcknowSwiftUI.swift */,
32A5DE3E25C7DD3D00ED11BB /* AcknowViewController.swift */,
BB8545722B227D39001BF421 /* GitHubAPI.swift */,
D705FE2A268A0B0600B501D7 /* AcknowList.docc */,
);
name = Sources;
Expand Down Expand Up @@ -349,6 +355,7 @@
32A5DE4625C7DD3D00ED11BB /* AcknowListViewController.swift in Sources */,
D7A68B482833C2D30064C2C0 /* AcknowPodDecoder.swift in Sources */,
D705FE2B268A0B0600B501D7 /* AcknowList.docc in Sources */,
BB8545732B227D39001BF421 /* GitHubAPI.swift in Sources */,
D738F2AE2640342B001BA008 /* AcknowListSwiftUI.swift in Sources */,
D7A68B452833C2C90064C2C0 /* AcknowList.swift in Sources */,
D7A68B492833C2D30064C2C0 /* AcknowPackageDecoder.swift in Sources */,
Expand All @@ -363,6 +370,7 @@
1A7421771CAC3685007E44FD /* AcknowViewControllerTests.swift in Sources */,
1A7421751CAC3685007E44FD /* AcknowListViewControllerTests.swift in Sources */,
D70473E525CC530E004F2BEC /* AcknowListTestsHelpers.swift in Sources */,
BB8545752B227D49001BF421 /* GitHubAPITests.swift in Sources */,
1A7421761CAC3685007E44FD /* AcknowParserTests.swift in Sources */,
1420A2E41CAEC92200D98F9C /* AcknowLocalizationTests.swift in Sources */,
);
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 3.1 (work in progress)

- Add `GitHubAPI` to get licenses from GitHub API
- Update `AcknowListViewController` to get missing licenses from GitHub API, with new `canFetchLicenseFromGitHub` property to disable this behavior


## 3.0.1 (24 November 2022)

- Update `AcknowListSwiftUIView` to fix navigation to repository URL
Expand Down
52 changes: 42 additions & 10 deletions Sources/AcknowList/AcknowListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ open class AcknowListViewController: UITableViewController {
/// The represented array of `Acknow`.
open var acknowledgements: [Acknow] = []

/// Indicates if the view controller should try to fetch missing licenses from the GitHub API.
open var canFetchLicenseFromGitHub = true

/**
Header text to be displayed above the list of the acknowledgements.
It needs to get set before `viewDidLoad` gets called.
Expand Down Expand Up @@ -385,18 +388,30 @@ open class AcknowListViewController: UITableViewController {
- indexPath: An index path locating the new selected row in `tableView`.
*/
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let acknowledgement = acknowledgements[(indexPath as NSIndexPath).row] as Acknow?,
if let acknowledgement = acknowledgements[indexPath.row] as Acknow?,
let navigationController = navigationController {
if acknowledgement.text != nil {
let viewController = AcknowViewController(acknowledgement: acknowledgement)
navigationController.pushViewController(viewController, animated: true)
}
else if canOpenRepository(for: acknowledgement),
let repository = acknowledgement.repository {
#if !os(tvOS)
let safariViewController = SFSafariViewController(url: repository)
present(safariViewController, animated: true)
#endif
else if canFetchLicenseFromGitHub,
let repository = acknowledgement.repository,
GitHubAPI.isGitHubRepository(repository) {
GitHubAPI.getLicense(for: repository) { [weak self] result in
switch result {
case .success(let text):
let updatedAcknowledgement = Acknow(title: acknowledgement.title, text: text, license: acknowledgement.license, repository: acknowledgement.repository)
self?.acknowledgements[indexPath.row] = updatedAcknowledgement
let viewController = AcknowViewController(acknowledgement: updatedAcknowledgement)
navigationController.pushViewController(viewController, animated: true)

case .failure:
self?.openRepository(repository)
}
}
}
else if let repository = acknowledgement.repository {
openRepository(repository)
}
}
}
Expand All @@ -415,16 +430,33 @@ open class AcknowListViewController: UITableViewController {
// MARK: - Navigation

private func canOpen(_ acknowledgement: Acknow) -> Bool {
return acknowledgement.text != nil || canOpenRepository(for: acknowledgement)
if acknowledgement.text != nil {
return true
}
else if let repository = acknowledgement.repository {
return canOpenRepository(repository)
}
else {
return false
}
}

private func canOpenRepository(for acknowledgement: Acknow) -> Bool {
guard let scheme = acknowledgement.repository?.scheme else {
private func canOpenRepository(_ repository: URL) -> Bool {
guard let scheme = repository.scheme else {
return false
}

return scheme == "http" || scheme == "https"
}

private func openRepository(_ repository: URL) {
#if !os(tvOS)
if canOpenRepository(repository) {
let safariViewController = SFSafariViewController(url: repository)
present(safariViewController, animated: true)
}
#endif
}
}

#endif
76 changes: 76 additions & 0 deletions Sources/AcknowList/GitHubAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// GitHubAPI.swift
//
// Copyright (c) 2015-2023 Vincent Tourraine (https://www.vtourraine.net)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

/// An object that interacts with the GitHub API.
open class GitHubAPI {

/**
Gets the repository license.
- Parameters:
- repository: The GitHub URL for the repository. For example: `https://github.com/vtourraine/AcknowList.git`
- completionHandler: The completion handler to call when the load request is complete. This handler is executed on the main queue. It takes a `Result` parameter, with either the body of the license, or an error object that indicates why the request failed.
*/
@discardableResult public static func getLicense(for repository: URL, completionHandler: @escaping (Result<String, Error>) -> Void) -> URLSessionDataTask {
// GitHub API documentation
// https://docs.github.com/en/rest/licenses/licenses#get-the-license-for-a-repository

let request = getLicenseRequest(for: repository)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
if (response as? HTTPURLResponse)?.statusCode == 200,
let data,
let text = String(data: data, encoding: .utf8) {
completionHandler(.success(text))
}
else {
completionHandler(.failure(error ?? URLError(URLError.Code.unknown)))
}
}
}

task.resume()
return task
}

/**
Returns a Boolean value indicating whether a URL is a valid GitHub repository URL.
- Parameter repository: The GitHub URL for the repository. For example: `https://github.com/vtourraine/AcknowList.git`
*/
public static func isGitHubRepository(_ repository: URL) -> Bool {
return repository.absoluteString.hasPrefix("https://github.com/")
}

internal static func getLicenseRequest(for repository: URL) -> URLRequest {
let path = pathWithoutExtension(for: repository)
let url = "https://api.github.com/repos\(path)/license"
var request = URLRequest(url: URL(string: url)!)
request.allHTTPHeaderFields = ["Accept": "application/vnd.github.raw"]
return request
}

internal static func pathWithoutExtension(for repository: URL) -> String {
return repository.path.replacingOccurrences(of: ".git", with: "")
}
}
31 changes: 31 additions & 0 deletions Tests/AcknowListTests/GitHubAPITests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// GitHubAPITests.swift
// AcknowExampleTests
//
// Created by Vincent Tourraine on 07/12/2023.
// Copyright © 2015-2022 Vincent Tourraine. All rights reserved.
//

import XCTest

@testable import AcknowList

class GitHubAPITests: XCTestCase {

func testRecognizeGitHubRepository() {
let repoURL = URL(string: "https://github.com/vtourraine/AcknowList.git")!
XCTAssertTrue(GitHubAPI.isGitHubRepository(repoURL))

let otherURL = URL(string: "https://www.website.com")!
XCTAssertFalse(GitHubAPI.isGitHubRepository(otherURL))
}

func testGetLicenseRequest() {
let repoURL = URL(string: "https://github.com/vtourraine/AcknowList.git")!
let request = GitHubAPI.getLicenseRequest(for: repoURL)

XCTAssertEqual(request.url?.absoluteString, "https://api.github.com/repos/vtourraine/AcknowList/license")
XCTAssertEqual(request.allHTTPHeaderFields, ["Accept": "application/vnd.github.raw"])
XCTAssertEqual(request.httpMethod, "GET")
}
}

0 comments on commit a2df5c3

Please sign in to comment.