Skip to content

Commit

Permalink
Get license from GitHub API
Browse files Browse the repository at this point in the history
  • Loading branch information
vtourraine committed Dec 7, 2023
1 parent e1e4e0a commit de7a971
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 10 deletions.
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.
public 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 de7a971

Please sign in to comment.