diff --git a/Assets/acknowledgements.csv b/Assets/acknowledgements.csv new file mode 100644 index 00000000..e690c084 --- /dev/null +++ b/Assets/acknowledgements.csv @@ -0,0 +1,6 @@ +Component,License,Origin,Copyright +SwiftDate,MIT,https://github.com/malcommac/SwiftDate,"Copyright (c) 2018 Daniele Margutti" +leveldb,BSD-3-Clause,https://github.com/firebase/leveldb,"Copyright (c) 2011 The LevelDB Authors. All rights reserved." +swift-composable-architecture,MIT,https://github.com/pointfreeco/swift-composable-architecture,"Copyright (c) 2020 Point-Free, Inc." +GoogleUtilities,Apache-2.0,https://github.com/google/GoogleUtilities,"Copyright (c) 2017 Landon J. Fuller " +MSAL,MIT,https://github.com/AzureAD/microsoft-authentication-library-for-objc,"Copyright (c) Microsoft Corporation" diff --git a/README.md b/README.md index b2810469..6b3c7a76 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,12 @@ You can see options by `license-plist --help`. - If this path is specified, a markdown acknowledgements file will be generated. - [Example is here](https://github.com/mono0926/LicensePlist/blob/master/Assets/acknowledgements.md) +#### `--csv-path` + +- Default: None. +- If this path is specified, a csv acknowledgements file will be generated. + - [Example is here](https://github.com/mono0926/LicensePlist/blob/master/Assets/acknowledgements.csv) + #### `--license-file-names` - Default: `LICENSE, LICENSE.*`. diff --git a/Sources/LicensePlist/main.swift b/Sources/LicensePlist/main.swift index cae00348..d0449e71 100644 --- a/Sources/LicensePlist/main.swift +++ b/Sources/LicensePlist/main.swift @@ -64,6 +64,9 @@ struct LicensePlist: ParsableCommand { @Option(name: .long, completion: .file()) var markdownPath: String? + @Option(name: .long, completion: .file()) + var csvPath: String? + @Option(name: .long, parsing: .upToNextOption, completion: .empty) var licenseFileNames = [String]() @@ -100,14 +103,7 @@ struct LicensePlist: ParsableCommand { func run() throws { Logger.configure(logLevel: logLevel, colorCommandLineFlag: color) - var config = loadConfig(configPath: URL(fileURLWithPath: configPath)) - config.force = force ?? config.options.force ?? false - config.addVersionNumbers = addVersionNumbers ?? config.options.addVersionNumbers ?? false - config.sandboxMode = sandboxMode ?? config.options.sandboxMode ?? false - config.suppressOpeningDirectory = (suppressOpeningDirectory ?? config.options.suppressOpeningDirectory ?? false) || config.sandboxMode - config.singlePage = singlePage ?? config.options.singlePage ?? false - config.failIfMissingLicense = failIfMissingLicense ?? config.options.failIfMissingLicense ?? false - config.addSources = addSources ?? config.options.addSources ?? false + let config = getConfig() let cartfilePath = cartfilePath.asPathURL(other: config.options.cartfilePath, default: Consts.cartfileName) let mintfilePath = mintfilePath.asPathURL(other: config.options.mintfilePath, default: Consts.mintfileName) let podsPath = podsPath.asPathURL(other: config.options.podsPath, default: Consts.podsDirectoryName) @@ -121,6 +117,7 @@ struct LicensePlist: ParsableCommand { let prefix = prefix ?? config.options.prefix ?? Consts.prefix let htmlPath = htmlPath.asPathURL(other: config.options.htmlPath) let markdownPath = markdownPath.asPathURL(other: config.options.markdownPath) + let csvPath = csvPath.asPathURL(other: config.options.csvPath) let configLicenseFileNames = config.options.licenseFileNames ?? Consts.licenseFileNames let licenseFileNames = licenseFileNames.isEmpty ? configLicenseFileNames : licenseFileNames let options = Options(outputPath: outputPath, @@ -135,11 +132,25 @@ struct LicensePlist: ParsableCommand { gitHubToken: githubToken, htmlPath: htmlPath, markdownPath: markdownPath, + csvPath: csvPath, licenseFileNames: licenseFileNames, config: config) let tool = LicensePlistCore.LicensePlist() tool.process(options: options) } + + /// Provided cli config options. Defaults to Yaml config file. + private func getConfig() -> Config { + var config = loadConfig(configPath: URL(fileURLWithPath: configPath)) + config.force = force ?? config.options.force ?? false + config.addVersionNumbers = addVersionNumbers ?? config.options.addVersionNumbers ?? false + config.sandboxMode = sandboxMode ?? config.options.sandboxMode ?? false + config.suppressOpeningDirectory = (suppressOpeningDirectory ?? config.options.suppressOpeningDirectory ?? false) || config.sandboxMode + config.singlePage = singlePage ?? config.options.singlePage ?? false + config.failIfMissingLicense = failIfMissingLicense ?? config.options.failIfMissingLicense ?? false + config.addSources = addSources ?? config.options.addSources ?? false + return config + } } LicensePlist.main() diff --git a/Sources/LicensePlistCore/Entity/GeneralOptions.swift b/Sources/LicensePlistCore/Entity/GeneralOptions.swift index 0b3aba0c..fa0bea8e 100644 --- a/Sources/LicensePlistCore/Entity/GeneralOptions.swift +++ b/Sources/LicensePlistCore/Entity/GeneralOptions.swift @@ -15,6 +15,7 @@ public struct GeneralOptions { public let gitHubToken: String? public let htmlPath: URL? public let markdownPath: URL? + public let csvPath: URL? public let licenseFileNames: [String]? public let force: Bool? public let addVersionNumbers: Bool? @@ -36,6 +37,7 @@ public struct GeneralOptions { gitHubToken: nil, htmlPath: nil, markdownPath: nil, + csvPath: nil, licenseFileNames: nil, force: nil, addVersionNumbers: nil, @@ -57,6 +59,7 @@ public struct GeneralOptions { gitHubToken: String?, htmlPath: URL?, markdownPath: URL?, + csvPath: URL?, licenseFileNames: [String]?, force: Bool?, addVersionNumbers: Bool?, @@ -77,6 +80,7 @@ public struct GeneralOptions { self.gitHubToken = gitHubToken self.htmlPath = htmlPath self.markdownPath = markdownPath + self.csvPath = csvPath self.licenseFileNames = licenseFileNames self.force = force self.addVersionNumbers = addVersionNumbers @@ -102,6 +106,7 @@ extension GeneralOptions { lhs.gitHubToken == rhs.gitHubToken && lhs.htmlPath == rhs.htmlPath && lhs.markdownPath == rhs.markdownPath && + lhs.csvPath == rhs.csvPath && lhs.licenseFileNames == rhs.licenseFileNames && lhs.force == rhs.force && lhs.addVersionNumbers == rhs.addVersionNumbers && @@ -128,6 +133,7 @@ extension GeneralOptions { gitHubToken: raw["gitHubToken"]?.string, htmlPath: raw["htmlPath"]?.string.asPathURL(in: configBasePath), markdownPath: raw["markdownPath"]?.string.asPathURL(in: configBasePath), + csvPath: raw["csvPath"]?.string.asPathURL(in: configBasePath), licenseFileNames: raw["licenseFileNames"]?.sequence?.compactMap { $0.string }, force: raw["force"]?.bool, addVersionNumbers: raw["addVersionNumbers"]?.bool, diff --git a/Sources/LicensePlistCore/Entity/LicenseCSVHolder.swift b/Sources/LicensePlistCore/Entity/LicenseCSVHolder.swift new file mode 100644 index 00000000..ea0c9244 --- /dev/null +++ b/Sources/LicensePlistCore/Entity/LicenseCSVHolder.swift @@ -0,0 +1,64 @@ +import Foundation +import LoggerAPI + +struct LicenseCSVHolder { + let csv: String + static func load(licenses: [LicenseInfo], options: Options) -> LicenseCSVHolder { + var csv = [ + "Component", + "License", + options.config.addSources ? "Origin" : nil, + "Copyright" + ] + .compactMap { $0 } + .joined(separator: .delemiter) + .newLine + + for license in licenses { + let component = license.name(withVersion: options.config.addVersionNumbers) + let licenseType = license.licenseType.rawValue + let copyright = license.copyright.quoted + if options.config.addSources, let source = license.source { + csv += [ + component, + licenseType, + source, + copyright + ].joined(separator: .delemiter) + } else { + csv += [ + component, + licenseType, + copyright + ].joined(separator: .delemiter) + } + csv += .newLine + } + return LicenseCSVHolder(csv: csv) + } + + func write(to csvPath: URL) { + do { + try csv.data(using: .utf8)!.write(to: csvPath) + } catch { + Log.error("Failed to write to (csvPath: \(csvPath)).\nerror: \(error)") + } + } +} + +extension LicenseInfo { + fileprivate var copyright: String { + let copyrightRange = body.range( + of: #"Copyright \(c\) .*"#, + options: .regularExpression + ) + return copyrightRange.map { String(body[$0]) } ?? "" + } +} + +extension String { + fileprivate static let newLine = "\n" + fileprivate static let delemiter = "," + fileprivate var quoted: String { + "\"" + self + "\"" + } +} diff --git a/Sources/LicensePlistCore/Entity/Options.swift b/Sources/LicensePlistCore/Entity/Options.swift index 649c19d6..8c859e5b 100644 --- a/Sources/LicensePlistCore/Entity/Options.swift +++ b/Sources/LicensePlistCore/Entity/Options.swift @@ -13,6 +13,7 @@ public struct Options { public let gitHubToken: String? public let htmlPath: URL? public let markdownPath: URL? + public let csvPath: URL? public let licenseFileNames: [String] public let config: Config @@ -28,6 +29,7 @@ public struct Options { gitHubToken: nil, htmlPath: nil, markdownPath: nil, + csvPath: nil, licenseFileNames: [], config: Config.empty) @@ -43,6 +45,7 @@ public struct Options { gitHubToken: String?, htmlPath: URL?, markdownPath: URL?, + csvPath: URL?, licenseFileNames: [String], config: Config) { self.outputPath = outputPath @@ -57,6 +60,7 @@ public struct Options { self.gitHubToken = gitHubToken self.htmlPath = htmlPath self.markdownPath = markdownPath + self.csvPath = csvPath self.licenseFileNames = licenseFileNames self.config = config } diff --git a/Sources/LicensePlistCore/Entity/PlistInfo.swift b/Sources/LicensePlistCore/Entity/PlistInfo.swift index 7b5db6dc..c92ba845 100644 --- a/Sources/LicensePlistCore/Entity/PlistInfo.swift +++ b/Sources/LicensePlistCore/Entity/PlistInfo.swift @@ -129,6 +129,11 @@ struct PlistInfo { markdownHolder.write(to: markdownPath) } + if let csvPath = options.csvPath { + let csvHolder = LicenseCSVHolder.load(licenses: licenses, options: options) + csvHolder.write(to: csvPath) + } + if let htmlPath = options.htmlPath { let htmlHolder = LicenseHTMLHolder.load(licenses: licenses, options: options) htmlHolder.write(to: htmlPath) diff --git a/Tests/LicensePlistTests/Entity/ConfigTests.swift b/Tests/LicensePlistTests/Entity/ConfigTests.swift index 6a11b96d..14caa0b7 100644 --- a/Tests/LicensePlistTests/Entity/ConfigTests.swift +++ b/Tests/LicensePlistTests/Entity/ConfigTests.swift @@ -36,6 +36,7 @@ class ConfigTests: XCTestCase { gitHubToken: "YOUR_GITHUB_TOKEN", htmlPath: URL(fileURLWithPath: "acknowledgements.html", relativeTo: configBasePath), markdownPath: URL(fileURLWithPath: "acknowledgements.md", relativeTo: configBasePath), + csvPath: nil, licenseFileNames: ["LICENSE", "LICENSE.*"], force: false, addVersionNumbers: false, diff --git a/Tests/LicensePlistTests/Entity/LicensePlistHolderTests.swift b/Tests/LicensePlistTests/Entity/LicensePlistHolderTests.swift index 699065bb..db7e96f9 100644 --- a/Tests/LicensePlistTests/Entity/LicensePlistHolderTests.swift +++ b/Tests/LicensePlistTests/Entity/LicensePlistHolderTests.swift @@ -121,6 +121,7 @@ extension Options { gitHubToken: nil, htmlPath: nil, markdownPath: nil, + csvPath: nil, licenseFileNames: [], config: config ) diff --git a/Tests/LicensePlistTests/Entity/PlistInfoTests.swift b/Tests/LicensePlistTests/Entity/PlistInfoTests.swift index 5ce06dd8..b542f8a6 100644 --- a/Tests/LicensePlistTests/Entity/PlistInfoTests.swift +++ b/Tests/LicensePlistTests/Entity/PlistInfoTests.swift @@ -21,6 +21,7 @@ class PlistInfoTests: XCTestCase { gitHubToken: nil, htmlPath: nil, markdownPath: nil, + csvPath: nil, licenseFileNames: [], config: Config(githubs: [GitHub(name: "facebook-ios-sdk", nameSpecified: nil, diff --git a/Tests/LicensePlistTests/Entity/PlistInfoWithSourcePackagesTests.swift b/Tests/LicensePlistTests/Entity/PlistInfoWithSourcePackagesTests.swift index 5482a33a..cd74af87 100644 --- a/Tests/LicensePlistTests/Entity/PlistInfoWithSourcePackagesTests.swift +++ b/Tests/LicensePlistTests/Entity/PlistInfoWithSourcePackagesTests.swift @@ -68,6 +68,7 @@ final class PlistInfoWithSourcePackagesTests: XCTestCase { gitHubToken: nil, htmlPath: nil, markdownPath: nil, + csvPath: nil, licenseFileNames: licenseFileNames, config: .empty) }