├── .gitignore
├── Sources
└── SPM-Acknowledgments
│ ├── Localizable.strings
│ ├── ParsePackages.swift
│ ├── AcknowledgmentsTextViewController.swift
│ └── AcknowledgmentsTableViewController.swift
├── Tests
├── LinuxMain.swift
└── SPM-AcknowledgmentsTests
│ ├── XCTestManifests.swift
│ ├── ParsingTests.swift
│ └── Package.resolved
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ ├── SPM-AcknowledgmentsTests.xcscheme
│ └── SPM-Acknowledgments.xcscheme
├── Package.swift
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/Sources/SPM-Acknowledgments/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timroesner/SPM-Acknowledgments/HEAD/Sources/SPM-Acknowledgments/Localizable.strings
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import SPM_AcknowledgmentsTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += SPM_AcknowledgmentsTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/SPM-AcknowledgmentsTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(ParsingTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Tests/SPM-AcknowledgmentsTests/ParsingTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SPM_Acknowledgments
3 |
4 | final class ParsingTests: XCTestCase {
5 | func testParsing() {
6 | // Cannot be tested until resources support is added
7 | }
8 |
9 | static var allTests = [
10 | ("testParsing", testParsing),
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SPM-Acknowledgments",
8 | platforms: [.iOS("11.0")],
9 | products: [
10 | .library(
11 | name: "SPM-Acknowledgments",
12 | targets: ["SPM-Acknowledgments"]),
13 | ],
14 | targets: [
15 | .target(
16 | name: "SPM-Acknowledgments",
17 | dependencies: []),
18 | .testTarget(
19 | name: "SPM-AcknowledgmentsTests",
20 | dependencies: ["SPM-Acknowledgments"]),
21 | ],
22 | swiftLanguageVersions: [.v4_2]
23 | )
24 |
--------------------------------------------------------------------------------
/Tests/SPM-AcknowledgmentsTests/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CVCalendar",
6 | "repositoryURL": "https://github.com/timroesner/CVCalendar",
7 | },
8 | {
9 | "package": "Disk",
10 | "repositoryURL": "https://github.com/saoudrizwan/Disk",
11 | },
12 | {
13 | "package": "Hero",
14 | "repositoryURL": "https://github.com/HeroTransitions/Hero",
15 | },
16 | {
17 | "package": "SkyFloatingLabelTextField",
18 | "repositoryURL": "https://github.com/Skyscanner/SkyFloatingLabelTextField",
19 | },
20 | {
21 | "package": "SPM-Acknowledgments",
22 | "repositoryURL": "https://github.com/timroesner/SPM-Acknowledgments",
23 | },
24 | ]
25 | },
26 | "version": 1
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Tim Roesner
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sources/SPM-Acknowledgments/ParsePackages.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsePackages.swift
3 | // FlightTrack
4 | //
5 | // Created by Tim Roesner on 2/19/20.
6 | // Copyright © 2020 Tim Roesner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct Package: Codable {
12 | let name: String
13 | let licenseURL: URL
14 | var license: String = ""
15 |
16 | private enum CodingKeys: String, CodingKey{
17 | case name = "package"
18 | case licenseURL = "repositoryURL"
19 | }
20 |
21 | init(from decoder: Decoder) throws {
22 | let values = try decoder.container(keyedBy: CodingKeys.self)
23 | name = try values.decode(String.self, forKey: .name)
24 | let baseURL = try values.decode(URL.self, forKey: .licenseURL)
25 | licenseURL = baseURL.appendingPathComponent("/raw/master/LICENSE")
26 | }
27 | }
28 |
29 | internal class ParsePackages {
30 | private struct Object: Codable {
31 | var object: Pins
32 | }
33 |
34 | private struct Pins: Codable {
35 | var pins: [Package]
36 | }
37 |
38 | func parsePackages() -> [Package] {
39 | guard let packagesPath = Bundle.main.path(forResource: "Package", ofType: "resolved"),
40 | let data = try? Data(contentsOf: URL(fileURLWithPath: packagesPath)) ,
41 | let json = try? JSONDecoder().decode(Object.self, from: data) else { return [] }
42 | return json.object.pins.filter({ $0.name != "SPM-Acknowledgments" && !$0.licenseURL.absoluteString.contains(".git/") })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📦 SPM-Acknowledgments
2 |
3 | A Swift Package Manager utility to create an Acknowledgments list of all packages used, by parsing the Package.resolved file and displaying all your SPM packages in a Table View with their linked licenses.
4 |
5 | ## Installation
6 | In Xcode 11 select File > Swift Packages > Add Package Dependency. From there you can add the package through the GitHub repo URL: https://github.com/timroesner/SPM-Acknowledgments
7 |
8 | ## Setup
9 | For the parsing to properly work, you need to add the Package.resolved file to your project. The easiest way to keep this file up to date is by adding the following as a Build Phase:
10 | `cp ${PROJECT_FILE_PATH}/project.xcworkspace/xcshareddata/swiftpm/Package.resolved ${PROJECT_DIR}`.
11 | 
12 |
13 | Once you build your project at least once you should now have a `Package.resolved` file in your project directory. Drag this file into your Xcode project and import it. Important, do not copy this file, but simply create a reference to it, as it will be automatically updated with each build. This is mainly so changes to your dependencies are automatically reflected, without you having to do any updates to the `Package.resovled` file.
14 | 
15 |
16 | ## Usage
17 | ```swift
18 | import SPM_Acknowledgments
19 |
20 | let acknowledgmentList = AcknowledgmentsTableViewController(style: .grouped)
21 | navigationController?.pushViewController(acknowledgmentList, animated: true)
22 | ```
23 |
24 | ## Screenshots
25 | 
26 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SPM-AcknowledgmentsTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Sources/SPM-Acknowledgments/AcknowledgmentsTextViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AcknowledgmentsTextViewController.swift
3 | // FlightTrack
4 | //
5 | // Created by Tim Roesner on 2/20/20.
6 | // Copyright © 2020 Tim Roesner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal class AcknowledgmentsTextViewController: UIViewController {
12 | private let package: Package
13 | private let loadingSpinner = UIActivityIndicatorView()
14 | private let textView: UITextView = {
15 | let textView = UITextView()
16 | textView.dataDetectorTypes = .link
17 | textView.isEditable = false
18 | textView.font = UIFont.preferredFont(forTextStyle: .body)
19 | textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
20 | return textView
21 | }()
22 |
23 | init(package: Package) {
24 | self.package = package
25 | super.init(nibName: nil, bundle: nil)
26 | title = package.name
27 | if #available(iOS 13.0, *) {
28 | view.backgroundColor = .systemBackground
29 | } else {
30 | view.backgroundColor = .white
31 | }
32 | }
33 |
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 |
38 | textView.translatesAutoresizingMaskIntoConstraints = false
39 | view.addSubview(textView)
40 |
41 | loadingSpinner.translatesAutoresizingMaskIntoConstraints = false
42 | view.addSubview(loadingSpinner)
43 |
44 | let safeArea = view.safeAreaLayoutGuide
45 | NSLayoutConstraint.activate([
46 | textView.topAnchor.constraint(equalTo: safeArea.topAnchor),
47 | textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
48 | safeArea.trailingAnchor.constraint(equalTo: textView.trailingAnchor),
49 | safeArea.bottomAnchor.constraint(equalTo: textView.bottomAnchor),
50 |
51 | loadingSpinner.centerYAnchor.constraint(equalTo: safeArea.centerYAnchor),
52 | loadingSpinner.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor)
53 | ])
54 |
55 | loadingSpinner.startAnimating()
56 | }
57 |
58 | override func viewDidAppear(_ animated: Bool) {
59 | let task = URLSession.shared.downloadTask(with: package.licenseURL) { [weak self] localURL, urlResponse, error in
60 | guard let self = self, let localURL = localURL else { return }
61 | DispatchQueue.main.async {
62 | self.textView.text = try? String(contentsOf: localURL)
63 | self.loadingSpinner.stopAnimating()
64 | }
65 | }
66 | task.resume()
67 | }
68 |
69 | @available(*, unavailable)
70 | required init?(coder: NSCoder) {
71 | fatalError("init(coder:) has not been implemented")
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Sources/SPM-Acknowledgments/AcknowledgmentsTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AcknowledgmentsTableViewController.swift
3 | // FlightTrack
4 | //
5 | // Created by Tim Roesner on 2/20/20.
6 | // Copyright © 2020 Tim Roesner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// The TableViewController that displays all SPM packages in a list. Tapping on the name of a package will
12 | /// present the license, which is being downloaded from GitHub.
13 | ///
14 | /// - Note: Should be presented within a `UINavigationController` to display titles in a Navigation Bar.
15 | final public class AcknowledgmentsTableViewController: UITableViewController {
16 | private var packages = [Package]()
17 | private let cellIdentifier = "cell"
18 |
19 | override public func viewDidLoad() {
20 | super.viewDidLoad()
21 | packages = ParsePackages().parsePackages()
22 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
23 | footerView.frame.size = footerView.systemLayoutSizeFitting(CGSize(width: view.frame.width, height: .greatestFiniteMagnitude),
24 | withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)
25 | tableView.tableFooterView = footerView
26 | }
27 |
28 | override public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
29 | return packages.count
30 | }
31 |
32 | override public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
33 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
34 | cell.textLabel?.text = packages[indexPath.row].name
35 | cell.accessoryType = .disclosureIndicator
36 | return cell
37 | }
38 |
39 | override public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
40 | tableView.deselectRow(at: indexPath, animated: true)
41 | let textViewController = AcknowledgmentsTextViewController(package: packages[indexPath.row])
42 | if let navController = navigationController {
43 | navController.pushViewController(textViewController, animated: true)
44 | } else {
45 | present(textViewController, animated: true, completion: nil)
46 | }
47 | }
48 |
49 | private let footerView: UITextView = {
50 | let textView = UITextView()
51 | textView.dataDetectorTypes = .link
52 | textView.isEditable = false
53 | textView.backgroundColor = .clear
54 |
55 | let secondaryLabel: UIColor
56 | if #available(iOS 13.0, *) {
57 | secondaryLabel = .secondaryLabel
58 | } else {
59 | secondaryLabel = .lightGray
60 | }
61 |
62 | let attributedString = NSMutableAttributedString(string: "SPM Acknowledgments")
63 | attributedString.addAttribute(.link, value: "https://github.com/timroesner/SPM-Acknowledgments", range: NSRange(location: 0, length: attributedString.string.count))
64 | attributedString.insert(NSAttributedString(string: "\(NSLocalizedString("Generated with", comment: "Footer text giving credit to this package.")) "), at: 0)
65 |
66 | let centerAlign = NSMutableParagraphStyle()
67 | centerAlign.alignment = .center
68 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.preferredFont(forTextStyle: .footnote), .foregroundColor: secondaryLabel, .paragraphStyle: centerAlign]
69 | attributedString.addAttributes(attributes, range: NSRange(location: 0, length: attributedString.string.count))
70 |
71 | textView.attributedText = attributedString
72 | return textView
73 | }()
74 | }
75 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SPM-Acknowledgments.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------