From c4d3935f80019b748b8b5e01bfe9fde5dfb232f5 Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Mon, 15 Apr 2024 16:54:11 +0100 Subject: [PATCH 01/14] Set up diff command line inputs. --- .../ArgumentParsing/Subcommands/Diff.swift | 56 +++++++++++++++++++ .../Subcommands/ProcessArchive.swift | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Diff.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Diff.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Diff.swift new file mode 100644 index 0000000000..7e745d6701 --- /dev/null +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Diff.swift @@ -0,0 +1,56 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import ArgumentParser +import Foundation +import SwiftDocC + +extension Docc.ProcessArchive { + struct Diff: ParsableCommand { + + // MARK: - Configuration + + static var configuration = CommandConfiguration( + commandName: "diff", + abstract: "Produce the symbol diff between two Render JSON files.", + shouldDisplay: true) + + // MARK: - Command Line Options & Arguments + + @Argument( + help: ArgumentHelp( + "The path to a Render JSON file to be compared.", + valueName: "renderJSON1"), + transform: URL.init(fileURLWithPath:)) + var firstRenderJSON: URL + + @Argument( + help: ArgumentHelp( + "The path to a second Render JSON file to be compared.", + valueName: "renderJSON2"), + transform: URL.init(fileURLWithPath:)) + var secondRenderJSON: URL + + // MARK: - Execution + + public mutating func run() throws { + let firstRenderJSONData = try Data(contentsOf: firstRenderJSON) + let secondRenderJSONData = try Data(contentsOf: secondRenderJSON) + + let decoder = RenderJSONDecoder.makeDecoder() + let firstRenderNode = try decoder.decode(RenderNode.self, from: firstRenderJSONData) + let secondRenderNode = try decoder.decode(RenderNode.self, from: secondRenderJSONData) + + let difference = firstRenderNode._difference(from: secondRenderNode) + print(difference) + } + + } +} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift index 59da03fff5..c996026bf1 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift @@ -18,7 +18,7 @@ extension Docc { static var configuration = CommandConfiguration( commandName: "process-archive", abstract: "Perform operations on documentation archives ('.doccarchive' directories).", - subcommands: [TransformForStaticHosting.self, Index.self]) + subcommands: [TransformForStaticHosting.self, Index.self, Diff.self]) } } From 7ac8ec7ab61abd89ba97985acf9080662c68b2bb Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Thu, 18 Apr 2024 13:38:04 +0100 Subject: [PATCH 02/14] Diff two DocC Archives to produce three lists: (1) The existing symbols within the first DocC Archive, (2) The existing symbols within the second DocC Archive, and (3) The new symbols added to the second DocC Archive that did not exist in the first DocC Archive --- .../{Diff.swift => DiffRenderJSON.swift} | 4 +- .../ArgumentParsing/Subcommands/File.swift | 81 +++++++++++++++++++ .../Subcommands/ProcessArchive.swift | 2 +- 3 files changed, 84 insertions(+), 3 deletions(-) rename Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/{Diff.swift => DiffRenderJSON.swift} (95%) create mode 100644 Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Diff.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffRenderJSON.swift similarity index 95% rename from Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Diff.swift rename to Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffRenderJSON.swift index 7e745d6701..e671bfe1e8 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Diff.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffRenderJSON.swift @@ -13,12 +13,12 @@ import Foundation import SwiftDocC extension Docc.ProcessArchive { - struct Diff: ParsableCommand { + struct DiffRenderJSON: ParsableCommand { // MARK: - Configuration static var configuration = CommandConfiguration( - commandName: "diff", + commandName: "diff-render-json", abstract: "Produce the symbol diff between two Render JSON files.", shouldDisplay: true) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift new file mode 100644 index 0000000000..00b9b47af1 --- /dev/null +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift @@ -0,0 +1,81 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import ArgumentParser +import Foundation +import SwiftDocC + +extension Docc.ProcessArchive { + struct DiffDocCArchive: ParsableCommand { + + // MARK: - Configuration + + static var configuration = CommandConfiguration( + commandName: "diff-docc-archive", + abstract: "Produce a list of symbols added in the newer DocC Archive that did not exist in the initial DocC Archive.", + shouldDisplay: true) + + // MARK: - Command Line Options & Arguments + + @Argument( + help: ArgumentHelp( + "The path to the initial DocC Archive to be compared.", + valueName: "initialDocCArchive"), + transform: URL.init(fileURLWithPath:)) + var initialDocCArchivePath: URL + + @Argument( + help: ArgumentHelp( + "The path to the newer DocC Archive to be compared.", + valueName: "newerDocCArchive"), + transform: URL.init(fileURLWithPath:)) + var newerDocCArchivePath: URL + + // MARK: - Execution + + public mutating func run() throws { + + let initialDocCArchiveAPIs: [String] = try findAllSymbols(initialPath: initialDocCArchivePath) + let newDocCArchiveAPIs: [String] = try findAllSymbols(initialPath: newerDocCArchivePath) + + print("\ninitialDocCArchiveAPIs: ") + print(initialDocCArchiveAPIs) + + print("\nnewDocCArchiveAPIs: ") + print(newDocCArchiveAPIs) + + let initialSet = Set(initialDocCArchiveAPIs.map { $0.plainText }) + let newSet = Set(newDocCArchiveAPIs.map { $0.plainText }) + let difference = newSet.subtracting(initialSet) + print("\nDifference:\n\(difference)") + } + + func findAllSymbols(initialPath: URL) throws -> [String] { + guard let enumerator = FileManager.default.enumerator( + at: initialPath, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles, + errorHandler: nil + ) else { + return [] + } + + var returnSymbols: [String] = [] + for case let filePath as URL in enumerator { + if filePath.lastPathComponent.hasSuffix(".json") { + returnSymbols.append(filePath.lastPathComponent) + } + } + + return returnSymbols + } + + } +} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift index c996026bf1..31ccf2bc39 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift @@ -18,7 +18,7 @@ extension Docc { static var configuration = CommandConfiguration( commandName: "process-archive", abstract: "Perform operations on documentation archives ('.doccarchive' directories).", - subcommands: [TransformForStaticHosting.self, Index.self, Diff.self]) + subcommands: [TransformForStaticHosting.self, Index.self, DiffRenderJSON.self, DiffDocCArchive.self]) } } From 098dc89c8a19ebcacbee55595d3a8ab280f4faac Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Thu, 18 Apr 2024 15:05:17 +0100 Subject: [PATCH 03/14] Diff now returns all symbols as links. --- .../ArgumentParsing/Subcommands/File.swift | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift index 00b9b47af1..a50687ed46 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift @@ -13,6 +13,7 @@ import Foundation import SwiftDocC extension Docc.ProcessArchive { + struct DiffDocCArchive: ParsableCommand { // MARK: - Configuration @@ -42,8 +43,13 @@ extension Docc.ProcessArchive { public mutating func run() throws { - let initialDocCArchiveAPIs: [String] = try findAllSymbols(initialPath: initialDocCArchivePath) - let newDocCArchiveAPIs: [String] = try findAllSymbols(initialPath: newerDocCArchivePath) + // Process arguments to start from the /data/ subdirectory. + // This is where all the relevant renderJSON are contained. + let initialProcessedArchivePath = initialDocCArchivePath.appendingPathComponent("data") + let newerProcessedArchivePath = newerDocCArchivePath.appendingPathComponent("data") + + let initialDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: initialProcessedArchivePath) + let newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerProcessedArchivePath) print("\ninitialDocCArchiveAPIs: ") print(initialDocCArchiveAPIs) @@ -51,12 +57,34 @@ extension Docc.ProcessArchive { print("\nnewDocCArchiveAPIs: ") print(newDocCArchiveAPIs) - let initialSet = Set(initialDocCArchiveAPIs.map { $0.plainText }) - let newSet = Set(newDocCArchiveAPIs.map { $0.plainText }) + let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) + let newSet = Set(newDocCArchiveAPIs.map { $0 }) let difference = newSet.subtracting(initialSet) print("\nDifference:\n\(difference)") } + // Given a URL, return each of the symbols by their unique identifying links + func findAllSymbolLinks(initialPath: URL) throws -> [URL] { + guard let enumerator = FileManager.default.enumerator( + at: initialPath, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles, + errorHandler: nil + ) else { + return [] + } + + var returnSymbolLinks: [URL] = [] + for case let filePath as URL in enumerator { + if filePath.lastPathComponent.hasSuffix(".json") { + let symbolLink = try findSymbolLink(symbolPath: filePath) + returnSymbolLinks.append(symbolLink) + } + } + + return returnSymbolLinks + } + func findAllSymbols(initialPath: URL) throws -> [String] { guard let enumerator = FileManager.default.enumerator( at: initialPath, @@ -76,6 +104,15 @@ extension Docc.ProcessArchive { return returnSymbols } + + // Given a file path to a renderJSON, return that symbol's url from its identifier. + func findSymbolLink(symbolPath: URL) throws -> URL { + let renderJSONData = try Data(contentsOf: symbolPath) + let decoder = RenderJSONDecoder.makeDecoder() + let renderNode = try decoder.decode(RenderNode.self, from: renderJSONData) + + return renderNode.identifier.url + } } } From 39bcd8a8016ab067ccdd31ba89566342b178a36c Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Mon, 29 Apr 2024 12:33:38 +0100 Subject: [PATCH 04/14] Added support for exports and for docc archives, and return both additions and removals. --- .../Actions/Init/CatalogTemplateKind.swift | 21 ++ .../Subcommands/DiffDocCArchive.swift | 246 ++++++++++++++++++ .../ArgumentParsing/Subcommands/File.swift | 118 --------- 3 files changed, 267 insertions(+), 118 deletions(-) create mode 100644 Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift delete mode 100644 Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift index 0cbf321890..4fbaf80d49 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift @@ -87,4 +87,25 @@ extension CatalogTemplateKind { """ ] } + + /// Content of the 'changeLog' template + static func changeLogTemplateFiles(_ frameworkName: String) -> [String: String] { + [ + "\(frameworkName)_ChangeLog.md": """ + # \(frameworkName) + + + + @Metadata { + @TechnologyRoot + } + + Add a single sentence or sentence fragment, which DocC uses as the page’s abstract or summary. + + ## Overview + + Add one or more paragraphs that introduce your content overview. + """ + ] + } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift new file mode 100644 index 0000000000..52c9b400a3 --- /dev/null +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift @@ -0,0 +1,246 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import ArgumentParser +import Foundation +import SwiftDocC + +extension Docc.ProcessArchive { + + struct DiffDocCArchive: ParsableCommand { + + // MARK: - Content and Configuration + + /// Command line configuration. + static var configuration = CommandConfiguration( + commandName: "diff-docc-archive", + abstract: "Produce a list of symbols added in the newer DocC Archive that did not exist in the initial DocC Archive.", + shouldDisplay: true) + + /// Content of the 'changeLog' template. + static func changeLogTemplateFileContent( + frameworkName: String, + initialDocCArchiveName: String, + newerDocCArchiveName: String, + additionLinks: String, + removalLinks: String + ) -> [String : String] { + [ + "\(frameworkName.localizedCapitalized)_ChangeLog.md": """ + # \(frameworkName.localizedCapitalized) Updates + + @Metadata { @PageColor(yellow) } + + Learn about important changes to \(frameworkName.localizedCapitalized). + + ## Overview + + Browse notable changes in \(frameworkName.localizedCapitalized). + + ## Version: Diff between \(initialDocCArchiveName) and \(newerDocCArchiveName) + + + ### Change Log + + #### Additions + _New symbols added in \(newerDocCArchiveName) that did not previously exist in \(initialDocCArchiveName)._ + + \(additionLinks) + + + #### Removals + _Old symbols that existed in \(initialDocCArchiveName) that no longer exist in \(newerDocCArchiveName)._ + + \(removalLinks) + + """ + ] + } + + // MARK: - Command Line Options & Arguments + + @Argument( + help: ArgumentHelp( + "The name of the initial DocC Archive to be compared.", + valueName: "initialDocCArchiveName")) + var initialDocCArchiveName: String + + @Argument( + help: ArgumentHelp( + "The path to the initial DocC Archive to be compared.", + valueName: "initialDocCArchive"), + transform: URL.init(fileURLWithPath:)) + var initialDocCArchivePath: URL + + @Argument( + help: ArgumentHelp( + "The name of the newer DocC Archive to be compared.", + valueName: "newerDocCArchiveName")) + var newerDocCArchiveName: String + + @Argument( + help: ArgumentHelp( + "The path to the newer DocC Archive to be compared.", + valueName: "newerDocCArchive"), + transform: URL.init(fileURLWithPath:)) + var newerDocCArchivePath: URL + + // MARK: - Execution + + public mutating func run() throws { + let initialDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: initialDocCArchivePath) + let newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerDocCArchivePath) + + print("\nInitial DocC Archive: ") + printAllSymbols(symbols: initialDocCArchiveAPIs) + + print("\nNew DocC Archive: ") + printAllSymbols(symbols: newDocCArchiveAPIs) + + let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) + let newSet = Set(newDocCArchiveAPIs.map { $0 }) + + // Compute additions and removals to both sets + let additionsToNewSet = newSet.subtracting(initialSet) + let removedFromOldSet = initialSet.subtracting(newSet) + + print("\nAdditions to New DocC Archive:") + printAllSymbols(symbols: Array(additionsToNewSet)) + print("\nRemovals from Initial DocC Archive:") + printAllSymbols(symbols: Array(removedFromOldSet)) + + // Map identifier urls in differences to external urls + let additionsExternalURLs = Set(additionsToNewSet.map { findExternalLink(identifierURL: $0) }) + let removalsExternalURLs = Set(removedFromOldSet.map { findExternalLink(identifierURL: $0) }) + + // The framework name is the path component after "/documentation/". + var frameworkName: String = "No_Framework_Name" + var potentialFrameworkName = try findFrameworkName(initialPath: initialDocCArchivePath) + if potentialFrameworkName == nil { + potentialFrameworkName = try findFrameworkName(initialPath: newerDocCArchivePath) + } + + if potentialFrameworkName != nil { + frameworkName = potentialFrameworkName ?? "No_Framework_Name" + } + + var additionLinks: String = "" + for addition in additionsExternalURLs { + additionLinks.append("\n- <\(addition)>") + } + + var removalLinks: String = "" + for removal in removalsExternalURLs { + removalLinks.append("\n- <\(removal)>") + } + + // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. + for fileNameAndContent in Docc.ProcessArchive.DiffDocCArchive.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveName: initialDocCArchiveName, newerDocCArchiveName: newerDocCArchiveName, additionLinks: additionLinks, removalLinks: removalLinks) { + let fileName = fileNameAndContent.key + let content = fileNameAndContent.value + try FileManager.default.createFile(at: initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName), contents: Data(content.utf8)) + } + } + + /// Pretty print all symbols' url identifiers into a pretty format, with a new line between each symbol. + func printAllSymbols(symbols: [URL]) { + for symbol in symbols { + print(symbol) + } + } + + /// The framework name is the path component after "/documentation/". + func findFrameworkName(initialPath: URL) throws -> String? { + guard let enumerator = FileManager.default.enumerator( + at: initialPath, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles, + errorHandler: nil + ) else { + return nil + } + + var frameworkName: String? + for case let filePath as URL in enumerator { + let pathComponents = filePath.pathComponents + var isFrameworkName = false + for pathComponent in pathComponents { + if isFrameworkName { + frameworkName = pathComponent + return frameworkName + } + + if pathComponent == "documentation" { + isFrameworkName = true + } + } + } + + return frameworkName + } + + /// Given the identifier url, cut off everything preceding /documentation/ and append this resulting string to doc: + func findExternalLink(identifierURL: URL) -> String { + var resultantURL = identifierURL.absoluteString + var shouldAppend = false + for pathComponent in identifierURL.pathComponents { + if pathComponent == "documentation" { + resultantURL = "doc:" + shouldAppend = true + } + if shouldAppend { + resultantURL.append(pathComponent + "/") + } + } + return resultantURL + } + + /// Given a URL, return each of the symbols by their unique identifying links + func findAllSymbolLinks(initialPath: URL) throws -> [URL] { + guard let enumerator = FileManager.default.enumerator( + at: initialPath, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles, + errorHandler: nil + ) else { + return [] + } + + var returnSymbolLinks: [URL] = [] + for case let filePath as URL in enumerator { + if filePath.lastPathComponent.hasSuffix(".json") { + let symbolLink = try findSymbolLink(symbolPath: filePath) + if symbolLink != nil { + returnSymbolLinks.append(symbolLink!) + } + } + } + + return returnSymbolLinks + } + + func findSymbolLink(symbolPath: URL) throws -> URL? { + struct ContainerWithTopicReferenceIdentifier: Codable { + var identifier: ResolvedTopicReference + } + + let renderJSONData = try Data(contentsOf: symbolPath) + let decoder = RenderJSONDecoder.makeDecoder() + + do { + let identifier = try decoder.decode(ContainerWithTopicReferenceIdentifier.self, from: renderJSONData).identifier + return identifier.url + } catch { + return nil + } + } + + } +} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift deleted file mode 100644 index a50687ed46..0000000000 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/File.swift +++ /dev/null @@ -1,118 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import ArgumentParser -import Foundation -import SwiftDocC - -extension Docc.ProcessArchive { - - struct DiffDocCArchive: ParsableCommand { - - // MARK: - Configuration - - static var configuration = CommandConfiguration( - commandName: "diff-docc-archive", - abstract: "Produce a list of symbols added in the newer DocC Archive that did not exist in the initial DocC Archive.", - shouldDisplay: true) - - // MARK: - Command Line Options & Arguments - - @Argument( - help: ArgumentHelp( - "The path to the initial DocC Archive to be compared.", - valueName: "initialDocCArchive"), - transform: URL.init(fileURLWithPath:)) - var initialDocCArchivePath: URL - - @Argument( - help: ArgumentHelp( - "The path to the newer DocC Archive to be compared.", - valueName: "newerDocCArchive"), - transform: URL.init(fileURLWithPath:)) - var newerDocCArchivePath: URL - - // MARK: - Execution - - public mutating func run() throws { - - // Process arguments to start from the /data/ subdirectory. - // This is where all the relevant renderJSON are contained. - let initialProcessedArchivePath = initialDocCArchivePath.appendingPathComponent("data") - let newerProcessedArchivePath = newerDocCArchivePath.appendingPathComponent("data") - - let initialDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: initialProcessedArchivePath) - let newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerProcessedArchivePath) - - print("\ninitialDocCArchiveAPIs: ") - print(initialDocCArchiveAPIs) - - print("\nnewDocCArchiveAPIs: ") - print(newDocCArchiveAPIs) - - let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) - let newSet = Set(newDocCArchiveAPIs.map { $0 }) - let difference = newSet.subtracting(initialSet) - print("\nDifference:\n\(difference)") - } - - // Given a URL, return each of the symbols by their unique identifying links - func findAllSymbolLinks(initialPath: URL) throws -> [URL] { - guard let enumerator = FileManager.default.enumerator( - at: initialPath, - includingPropertiesForKeys: [], - options: .skipsHiddenFiles, - errorHandler: nil - ) else { - return [] - } - - var returnSymbolLinks: [URL] = [] - for case let filePath as URL in enumerator { - if filePath.lastPathComponent.hasSuffix(".json") { - let symbolLink = try findSymbolLink(symbolPath: filePath) - returnSymbolLinks.append(symbolLink) - } - } - - return returnSymbolLinks - } - - func findAllSymbols(initialPath: URL) throws -> [String] { - guard let enumerator = FileManager.default.enumerator( - at: initialPath, - includingPropertiesForKeys: [], - options: .skipsHiddenFiles, - errorHandler: nil - ) else { - return [] - } - - var returnSymbols: [String] = [] - for case let filePath as URL in enumerator { - if filePath.lastPathComponent.hasSuffix(".json") { - returnSymbols.append(filePath.lastPathComponent) - } - } - - return returnSymbols - } - - // Given a file path to a renderJSON, return that symbol's url from its identifier. - func findSymbolLink(symbolPath: URL) throws -> URL { - let renderJSONData = try Data(contentsOf: symbolPath) - let decoder = RenderJSONDecoder.makeDecoder() - let renderNode = try decoder.decode(RenderNode.self, from: renderJSONData) - - return renderNode.identifier.url - } - - } -} From b226fd99fbbcc5061171654f2497b477d5ece813 Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 30 Apr 2024 11:19:56 +0100 Subject: [PATCH 05/14] Remove unused ChangeLogTemplate from CatalogTemplateKind. --- .../Actions/Init/CatalogTemplateKind.swift | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift index 4fbaf80d49..0cbf321890 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift @@ -87,25 +87,4 @@ extension CatalogTemplateKind { """ ] } - - /// Content of the 'changeLog' template - static func changeLogTemplateFiles(_ frameworkName: String) -> [String: String] { - [ - "\(frameworkName)_ChangeLog.md": """ - # \(frameworkName) - - - - @Metadata { - @TechnologyRoot - } - - Add a single sentence or sentence fragment, which DocC uses as the page’s abstract or summary. - - ## Overview - - Add one or more paragraphs that introduce your content overview. - """ - ] - } } From 670068a8cc464c7905768571f3247a264e33b5df Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 30 Apr 2024 11:22:36 +0100 Subject: [PATCH 06/14] Remove unused DiffRenderJSON.swift file. --- .../Subcommands/DiffRenderJSON.swift | 56 ------------------- .../Subcommands/ProcessArchive.swift | 2 +- 2 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffRenderJSON.swift diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffRenderJSON.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffRenderJSON.swift deleted file mode 100644 index e671bfe1e8..0000000000 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffRenderJSON.swift +++ /dev/null @@ -1,56 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import ArgumentParser -import Foundation -import SwiftDocC - -extension Docc.ProcessArchive { - struct DiffRenderJSON: ParsableCommand { - - // MARK: - Configuration - - static var configuration = CommandConfiguration( - commandName: "diff-render-json", - abstract: "Produce the symbol diff between two Render JSON files.", - shouldDisplay: true) - - // MARK: - Command Line Options & Arguments - - @Argument( - help: ArgumentHelp( - "The path to a Render JSON file to be compared.", - valueName: "renderJSON1"), - transform: URL.init(fileURLWithPath:)) - var firstRenderJSON: URL - - @Argument( - help: ArgumentHelp( - "The path to a second Render JSON file to be compared.", - valueName: "renderJSON2"), - transform: URL.init(fileURLWithPath:)) - var secondRenderJSON: URL - - // MARK: - Execution - - public mutating func run() throws { - let firstRenderJSONData = try Data(contentsOf: firstRenderJSON) - let secondRenderJSONData = try Data(contentsOf: secondRenderJSON) - - let decoder = RenderJSONDecoder.makeDecoder() - let firstRenderNode = try decoder.decode(RenderNode.self, from: firstRenderJSONData) - let secondRenderNode = try decoder.decode(RenderNode.self, from: secondRenderJSONData) - - let difference = firstRenderNode._difference(from: secondRenderNode) - print(difference) - } - - } -} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift index 31ccf2bc39..279c8b39f2 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift @@ -18,7 +18,7 @@ extension Docc { static var configuration = CommandConfiguration( commandName: "process-archive", abstract: "Perform operations on documentation archives ('.doccarchive' directories).", - subcommands: [TransformForStaticHosting.self, Index.self, DiffRenderJSON.self, DiffDocCArchive.self]) + subcommands: [TransformForStaticHosting.self, Index.self, DiffDocCArchive.self]) } } From 8a798bf12ddd33ebea017fb2068a5d3a94e48fca Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 30 Apr 2024 13:58:35 +0100 Subject: [PATCH 07/14] Update help menu description and parameter naming. --- .../Subcommands/DiffDocCArchive.swift | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift index 52c9b400a3..d0d7f504b1 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift @@ -21,14 +21,14 @@ extension Docc.ProcessArchive { /// Command line configuration. static var configuration = CommandConfiguration( commandName: "diff-docc-archive", - abstract: "Produce a list of symbols added in the newer DocC Archive that did not exist in the initial DocC Archive.", + abstract: "Produce a markdown file saved as {FrameworkName}_ChangeLog.md containing the diff of added/removed symbols between the two provided DocC archives.", shouldDisplay: true) /// Content of the 'changeLog' template. static func changeLogTemplateFileContent( frameworkName: String, - initialDocCArchiveName: String, - newerDocCArchiveName: String, + initialDocCArchiveVersion: String, + newerDocCArchiveVersion: String, additionLinks: String, removalLinks: String ) -> [String : String] { @@ -44,19 +44,19 @@ extension Docc.ProcessArchive { Browse notable changes in \(frameworkName.localizedCapitalized). - ## Version: Diff between \(initialDocCArchiveName) and \(newerDocCArchiveName) + ## Version: Diff between \(initialDocCArchiveVersion) and \(newerDocCArchiveVersion) ### Change Log #### Additions - _New symbols added in \(newerDocCArchiveName) that did not previously exist in \(initialDocCArchiveName)._ + _New symbols added in \(newerDocCArchiveVersion) that did not previously exist in \(initialDocCArchiveVersion)._ \(additionLinks) #### Removals - _Old symbols that existed in \(initialDocCArchiveName) that no longer exist in \(newerDocCArchiveName)._ + _Old symbols that existed in \(initialDocCArchiveVersion) that no longer exist in \(newerDocCArchiveVersion)._ \(removalLinks) @@ -68,9 +68,9 @@ extension Docc.ProcessArchive { @Argument( help: ArgumentHelp( - "The name of the initial DocC Archive to be compared.", - valueName: "initialDocCArchiveName")) - var initialDocCArchiveName: String + "The version of the initial DocC Archive to be compared.", + valueName: "initialDocCArchiveVersion")) + var initialDocCArchiveVersion: String @Argument( help: ArgumentHelp( @@ -81,9 +81,9 @@ extension Docc.ProcessArchive { @Argument( help: ArgumentHelp( - "The name of the newer DocC Archive to be compared.", - valueName: "newerDocCArchiveName")) - var newerDocCArchiveName: String + "The version of the newer DocC Archive to be compared.", + valueName: "newerDocCArchiveVersion")) + var newerDocCArchiveVersion: String @Argument( help: ArgumentHelp( @@ -142,7 +142,7 @@ extension Docc.ProcessArchive { } // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. - for fileNameAndContent in Docc.ProcessArchive.DiffDocCArchive.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveName: initialDocCArchiveName, newerDocCArchiveName: newerDocCArchiveName, additionLinks: additionLinks, removalLinks: removalLinks) { + for fileNameAndContent in Docc.ProcessArchive.DiffDocCArchive.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialDocCArchiveVersion, newerDocCArchiveVersion: newerDocCArchiveVersion, additionLinks: additionLinks, removalLinks: removalLinks) { let fileName = fileNameAndContent.key let content = fileNameAndContent.value try FileManager.default.createFile(at: initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName), contents: Data(content.utf8)) From 278b3c274e9893167438371829809eaa1aa0a0ee Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 30 Apr 2024 16:29:16 +0100 Subject: [PATCH 08/14] Remove extra logging. --- .../Subcommands/DiffDocCArchive.swift | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift index d0d7f504b1..91226bb995 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift @@ -98,12 +98,6 @@ extension Docc.ProcessArchive { let initialDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: initialDocCArchivePath) let newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerDocCArchivePath) - print("\nInitial DocC Archive: ") - printAllSymbols(symbols: initialDocCArchiveAPIs) - - print("\nNew DocC Archive: ") - printAllSymbols(symbols: newDocCArchiveAPIs) - let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) let newSet = Set(newDocCArchiveAPIs.map { $0 }) @@ -111,11 +105,6 @@ extension Docc.ProcessArchive { let additionsToNewSet = newSet.subtracting(initialSet) let removedFromOldSet = initialSet.subtracting(newSet) - print("\nAdditions to New DocC Archive:") - printAllSymbols(symbols: Array(additionsToNewSet)) - print("\nRemovals from Initial DocC Archive:") - printAllSymbols(symbols: Array(removedFromOldSet)) - // Map identifier urls in differences to external urls let additionsExternalURLs = Set(additionsToNewSet.map { findExternalLink(identifierURL: $0) }) let removalsExternalURLs = Set(removedFromOldSet.map { findExternalLink(identifierURL: $0) }) @@ -145,7 +134,9 @@ extension Docc.ProcessArchive { for fileNameAndContent in Docc.ProcessArchive.DiffDocCArchive.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialDocCArchiveVersion, newerDocCArchiveVersion: newerDocCArchiveVersion, additionLinks: additionLinks, removalLinks: removalLinks) { let fileName = fileNameAndContent.key let content = fileNameAndContent.value - try FileManager.default.createFile(at: initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName), contents: Data(content.utf8)) + let filePath = initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName) + try FileManager.default.createFile(at: filePath, contents: Data(content.utf8)) + print("\nOutput file path: \(filePath)") } } From 50346a816c253458707aa2ef4e7f82088fc42d1b Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Mon, 6 May 2024 15:55:36 +0100 Subject: [PATCH 09/14] Add logic to return added/removed symbols in an alphabetical list. --- .../Subcommands/DiffDocCArchive.swift | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift index 91226bb995..f1a62c1322 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift @@ -120,15 +120,24 @@ extension Docc.ProcessArchive { frameworkName = potentialFrameworkName ?? "No_Framework_Name" } - var additionLinks: String = "" - for addition in additionsExternalURLs { - additionLinks.append("\n- <\(addition)>") - } - var removalLinks: String = "" - for removal in removalsExternalURLs { - removalLinks.append("\n- <\(removal)>") - } + let additionLinks = groupSeparateSymbols(symbolLinks: additionsExternalURLs) + let removalLinks = groupSeparateSymbols(symbolLinks: removalsExternalURLs) + + + +// let sortedAdditionSymbols = groupSeparateSymbols(symbolLinks: additionsExternalURLs) +// let sortedRemovalSymbols = groupSeparateSymbols(symbolLinks: removalsExternalURLs) +// +// var additionLinks: String = "" +// for addition in sortedAdditionSymbols { +// additionLinks.append("\n- <\(addition)>") +// } +// +// var removalLinks: String = "" +// for removal in sortedRemovalSymbols { +// removalLinks.append("\n- <\(removal)>") +// } // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. for fileNameAndContent in Docc.ProcessArchive.DiffDocCArchive.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialDocCArchiveVersion, newerDocCArchiveVersion: newerDocCArchiveVersion, additionLinks: additionLinks, removalLinks: removalLinks) { @@ -232,6 +241,33 @@ extension Docc.ProcessArchive { return nil } } + + /// Process lists of symbols to group them according to the highest level path component. + /// + /// If a class didn't exist in the old version but now exists in the new version: + /// - print that a new class was added, + /// - display the number of symbols added within that class beside it. + /// + /// Otherwise, group symbols by their highest path component below a header, and then print a nested list. + func groupSeparateSymbols(symbolLinks: Set) -> String { + + // Sort list alphabetically + let sortedSymbols: [String] = symbolLinks.sorted { $0.localizedCompare($1) == .orderedAscending } + + // Check matching path components + // for each path component after the initial path component.... +// for symbol in sortedSymbols { +// // example path components: ["/", "documentation", "accelerate", "vdsp", "vector-scalar_real_arithmetic_functions"] +// print(symbol.pathComponents) +// } + + var links: String = "" + for symbol in sortedSymbols { + links.append("\n- <\(symbol)>") + } + + return links // TODO: STUB + } } } From 60f2f5ba37e278aa5bd03b50704f7b689da83c2b Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 7 May 2024 14:57:35 +0100 Subject: [PATCH 10/14] Group symbols by highest level path component, and separate groups by spaces. --- .../Subcommands/DiffDocCArchive.swift | 64 +++++++------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift index f1a62c1322..a396a8e8dc 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift @@ -105,10 +105,6 @@ extension Docc.ProcessArchive { let additionsToNewSet = newSet.subtracting(initialSet) let removedFromOldSet = initialSet.subtracting(newSet) - // Map identifier urls in differences to external urls - let additionsExternalURLs = Set(additionsToNewSet.map { findExternalLink(identifierURL: $0) }) - let removalsExternalURLs = Set(removedFromOldSet.map { findExternalLink(identifierURL: $0) }) - // The framework name is the path component after "/documentation/". var frameworkName: String = "No_Framework_Name" var potentialFrameworkName = try findFrameworkName(initialPath: initialDocCArchivePath) @@ -120,24 +116,8 @@ extension Docc.ProcessArchive { frameworkName = potentialFrameworkName ?? "No_Framework_Name" } - - let additionLinks = groupSeparateSymbols(symbolLinks: additionsExternalURLs) - let removalLinks = groupSeparateSymbols(symbolLinks: removalsExternalURLs) - - - -// let sortedAdditionSymbols = groupSeparateSymbols(symbolLinks: additionsExternalURLs) -// let sortedRemovalSymbols = groupSeparateSymbols(symbolLinks: removalsExternalURLs) -// -// var additionLinks: String = "" -// for addition in sortedAdditionSymbols { -// additionLinks.append("\n- <\(addition)>") -// } -// -// var removalLinks: String = "" -// for removal in sortedRemovalSymbols { -// removalLinks.append("\n- <\(removal)>") -// } + let additionLinks = groupSymbols(symbolLinks: additionsToNewSet, frameworkName: frameworkName) + let removalLinks = groupSymbols(symbolLinks: removedFromOldSet, frameworkName: frameworkName) // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. for fileNameAndContent in Docc.ProcessArchive.DiffDocCArchive.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialDocCArchiveVersion, newerDocCArchiveVersion: newerDocCArchiveVersion, additionLinks: additionLinks, removalLinks: removalLinks) { @@ -242,31 +222,31 @@ extension Docc.ProcessArchive { } } - /// Process lists of symbols to group them according to the highest level path component. - /// - /// If a class didn't exist in the old version but now exists in the new version: - /// - print that a new class was added, - /// - display the number of symbols added within that class beside it. - /// - /// Otherwise, group symbols by their highest path component below a header, and then print a nested list. - func groupSeparateSymbols(symbolLinks: Set) -> String { - + /// Process lists of symbols to group them according to the highest level path component, split by spaces. + func groupSymbols(symbolLinks: Set, frameworkName: String) -> String { // Sort list alphabetically - let sortedSymbols: [String] = symbolLinks.sorted { $0.localizedCompare($1) == .orderedAscending } - - // Check matching path components - // for each path component after the initial path component.... -// for symbol in sortedSymbols { -// // example path components: ["/", "documentation", "accelerate", "vdsp", "vector-scalar_real_arithmetic_functions"] -// print(symbol.pathComponents) -// } + let sortedSymbols: [URL] = symbolLinks.sorted { $0.absoluteString.localizedCompare($1.absoluteString) == .orderedAscending } var links: String = "" - for symbol in sortedSymbols { - links.append("\n- <\(symbol)>") + + // find most similar path up until framework name by iterating over path components one at a time + guard var first = sortedSymbols.first else { + return links + } + + for symbol in sortedSymbols.dropFirst() { + let parent: String = first.absoluteString.commonPrefix(with: symbol.absoluteString) + + // If there are no common path components, add a space. Then reset the first to find the next parent. + if parent.localizedLowercase.hasSuffix(frameworkName + "/") { + links.append("\n\n") + first = symbol + } + + links.append("\n- <\(findExternalLink(identifierURL: symbol))>") } - return links // TODO: STUB + return links } } From 36ae1a7fa88af424a279407078341fca1796645a Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Mon, 20 May 2024 11:29:11 +0100 Subject: [PATCH 11/14] Add changes for demo; default diff is only modules, classes, protocols, and structs; and include --show-all flag for more granular symbol diff (i.e. all symbols that have changed). --- .../Model/Rendering/RenderNode.swift | 21 ++ .../Subcommands/DiffDocCArchive.swift | 81 +++- .../Subcommands/GenerateChangeLog.swift | 355 ++++++++++++++++++ Sources/SwiftDocCUtilities/Docc.swift | 1 + 4 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNode.swift b/Sources/SwiftDocC/Model/Rendering/RenderNode.swift index c3ef2d5515..3e496adb3d 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNode.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNode.swift @@ -275,5 +275,26 @@ public struct RenderNode: VariantContainer { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unknown RenderNode.Kind: '\(unknown)'.") } } + +// // Return the string representing this kind. +// // If the kind is a symbol, return the symbol kind. +// public func kindString() -> String { +// var kind = "" +// +// switch(self) { +// case .article: +// kind = "article" +// case .tutorial: +// kind = "tutorial" +// case .section: +// kind = "section" +// case .overview: +// kind = "overview" +// case .symbol: +// kind = "symbol" +// } +// +// return kind +// } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift index a396a8e8dc..7cf7a615a7 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift @@ -1,5 +1,5 @@ /* - This source file is part of the Swift.org open source project + This source file is part doccof the Swift.org open source project Copyright (c) 2024 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception @@ -36,7 +36,9 @@ extension Docc.ProcessArchive { "\(frameworkName.localizedCapitalized)_ChangeLog.md": """ # \(frameworkName.localizedCapitalized) Updates - @Metadata { @PageColor(yellow) } + @Metadata { + @PageColor(yellow) + } Learn about important changes to \(frameworkName.localizedCapitalized). @@ -222,6 +224,48 @@ extension Docc.ProcessArchive { } } + // TODO: CONTINUE +// func findPageType(symbolPath: URL) throws -> URL? { +// struct ContainerWithPageType: Codable { +// var pageType: NavigatorIndex.PageType +// } +// +// let renderJSONData = try Data(contentsOf: symbolPath) +// let decoder = RenderJSONDecoder.makeDecoder() +// +// do { +// let identifier = try decoder.decode(NavigatorIndex.self, from: renderJSONData). +// return identifier.url +// } catch { +// return nil +// } +// } + + func findClassName(symbolPath: URL) -> String { + return symbolPath.lastPathComponent + } + +// func findClassName(symbolPath: URL) throws -> [[String]]? { +// struct ContainerWithRenderHierarchy: Codable { +// var hierarchy: RenderHierarchy +// } +// +// let renderJSONData = try Data(contentsOf: symbolPath) +// let decoder = RenderJSONDecoder.makeDecoder() +// +// do { +// let hierarchy = try decoder.decode(ContainerWithRenderHierarchy.self, from: renderJSONData).hierarchy +// +// if hierarchy != nil { +// return hierarchy.paths +// } +// +// return identifier.url +// } catch { +// return nil +// } +// } + /// Process lists of symbols to group them according to the highest level path component, split by spaces. func groupSymbols(symbolLinks: Set, frameworkName: String) -> String { // Sort list alphabetically @@ -239,7 +283,7 @@ extension Docc.ProcessArchive { // If there are no common path components, add a space. Then reset the first to find the next parent. if parent.localizedLowercase.hasSuffix(frameworkName + "/") { - links.append("\n\n") + links.append("\n \n") first = symbol } @@ -248,6 +292,37 @@ extension Docc.ProcessArchive { return links } + + func addClassNames(allSymbolsString: String) -> String { +// let processedString = "" + + // Split string into string array on a double newline + return longestCommonPrefix(of: allSymbolsString) + } + + func longestCommonPrefix(of string: String) -> String { + + let words = string.split(separator: " ") +// let words = string.split(separator: "\n\n") + guard let first = words.first else { + return "" + } + + var (minWord, maxWord) = (first, first) + for word in words.dropFirst() { + if word < minWord { + print(word) + print(maxWord) + minWord = word + } else if word > maxWord { + print(word) + print(maxWord) + maxWord = word + } + } + + return minWord.commonPrefix(with: maxWord) + } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift new file mode 100644 index 0000000000..781fffaa6f --- /dev/null +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift @@ -0,0 +1,355 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import ArgumentParser +import Foundation +import SwiftDocC + +extension Docc { + + struct GenerateChangelog: ParsableCommand { + + // MARK: - Content and Configuration + + /// Command line configuration. + static var configuration = CommandConfiguration( + commandName: "generate-changelog", + abstract: "Generate a changelog with symbol diffs between documentation archives ('.doccarchive' directories).", + shouldDisplay: true) + + /// Content of the 'changeLog' template. + static func changeLogTemplateFileContent( + frameworkName: String, + initialDocCArchiveVersion: String, + newerDocCArchiveVersion: String, + additionLinks: String, + removalLinks: String + ) -> [String : String] { + [ + "\(frameworkName.localizedCapitalized)_Changelog.md": """ + # \(frameworkName.localizedCapitalized) Updates + + @Metadata { + @PageColor(yellow) + } + + Learn about important changes to \(frameworkName.localizedCapitalized). + + ## Overview + + Browse notable changes in \(frameworkName.localizedCapitalized). + + ## Diff between \(initialDocCArchiveVersion) and \(newerDocCArchiveVersion) + + + ### Change Log + + #### Additions + _New symbols added in \(newerDocCArchiveVersion) that did not previously exist in \(initialDocCArchiveVersion)._ + + \(additionLinks) + + + #### Removals + _Old symbols that existed in \(initialDocCArchiveVersion) that no longer exist in \(newerDocCArchiveVersion)._ + + \(removalLinks) + + """ + ] + } + + + // MARK: - Command Line Options & Arguments + + @Argument( + help: ArgumentHelp( + "The path to the initial DocC Archive to be compared.", + valueName: "initialDocCArchive"), + transform: URL.init(fileURLWithPath:)) + var initialDocCArchivePath: URL + + @Argument( + help: ArgumentHelp( + "The path to the newer DocC Archive to be compared.", + valueName: "newerDocCArchive"), + transform: URL.init(fileURLWithPath:)) + var newerDocCArchivePath: URL + + @Option( + name: [.customLong("show-all", withSingleDash: false)], + help: "Produces full symbol diff: including all properties, methods, and overrides" + ) + var showAllSymbols: Bool = false + + // MARK: - Execution + + public mutating func run() throws { + var initialDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: initialDocCArchivePath) + var newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerDocCArchivePath) + + if showAllSymbols { + print("Showing ALL symbols") + initialDocCArchiveAPIs = try findAllSymbolLinks_Full(initialPath: initialDocCArchivePath) + newDocCArchiveAPIs = try findAllSymbolLinks_Full(initialPath: newerDocCArchivePath) + } else { + print("Showing ONLY modules, classes, protocols, and structs.") + } + + let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) + let newSet = Set(newDocCArchiveAPIs.map { $0 }) + + // Compute additions and removals to both sets + let additionsToNewSet = newSet.subtracting(initialSet) + let removedFromOldSet = initialSet.subtracting(newSet) + + // The framework name is the path component after "/documentation/". + var frameworkName: String = "No_Framework_Name" + var potentialFrameworkName = try findFrameworkName(initialPath: initialDocCArchivePath) + if potentialFrameworkName == nil { + potentialFrameworkName = try findFrameworkName(initialPath: newerDocCArchivePath) + } + + if potentialFrameworkName != nil { + frameworkName = potentialFrameworkName ?? "No_Framework_Name" + } + + let additionLinks = groupSymbols(symbolLinks: additionsToNewSet, frameworkName: frameworkName) + let removalLinks = groupSymbols(symbolLinks: removedFromOldSet, frameworkName: frameworkName) + + // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. + for fileNameAndContent in Docc.GenerateChangelog.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: "RainbowF RC", newerDocCArchiveVersion: "Geode Beta 1", additionLinks: additionLinks, removalLinks: removalLinks) { + let fileName = fileNameAndContent.key + let content = fileNameAndContent.value + let filePath = initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName) + try FileManager.default.createFile(at: filePath, contents: Data(content.utf8)) + print("\nOutput file path: \(filePath)") + } + } + + + /// Pretty print all symbols' url identifiers into a pretty format, with a new line between each symbol. + func printAllSymbols(symbols: [URL]) { + for symbol in symbols { + print(symbol) + } + } + + + /// The framework name is the path component after "/documentation/". + func findFrameworkName(initialPath: URL) throws -> String? { + guard let enumerator = FileManager.default.enumerator( + at: initialPath, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles, + errorHandler: nil + ) else { + return nil + } + + var frameworkName: String? + for case let filePath as URL in enumerator { + let pathComponents = filePath.pathComponents + var isFrameworkName = false + for pathComponent in pathComponents { + if isFrameworkName { + frameworkName = pathComponent + return frameworkName + } + + if pathComponent == "documentation" { + isFrameworkName = true + } + } + } + + return frameworkName + } + + /// Given the identifier url, cut off everything preceding /documentation/ and append this resulting string to doc: + func findExternalLink(identifierURL: URL) -> String { + var resultantURL = identifierURL.absoluteString + var shouldAppend = false + for pathComponent in identifierURL.pathComponents { + if pathComponent == "documentation" { + resultantURL = "doc:" + shouldAppend = true + } + if shouldAppend { + resultantURL.append(pathComponent + "/") + } + } + return resultantURL + } + + /// Given a URL, return each of the symbols by their unique identifying links + func findAllSymbolLinks(initialPath: URL) throws -> [URL] { + guard let enumerator = FileManager.default.enumerator( + at: initialPath, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles, + errorHandler: nil + ) else { + return [] + } + + var returnSymbolLinks: [URL] = [] + for case let filePath as URL in enumerator { + if filePath.lastPathComponent.hasSuffix(".json") { + let symbolLink = try findSymbolLink(symbolPath: filePath) + let symbolKind = try findKind(symbolPath: filePath) + + if (symbolLink != nil && symbolKind != nil) { + if let validSymbol = symbolKind?.contains("module") { + if validSymbol == true { + returnSymbolLinks.append(symbolLink!) + } + } + + if let validSymbol = symbolKind?.contains("class") { + if validSymbol == true { + returnSymbolLinks.append(symbolLink!) + } + } + + if let validSymbol = symbolKind?.contains("protocol") { + if validSymbol == true { + returnSymbolLinks.append(symbolLink!) + } + } + + if let validSymbol = symbolKind?.contains("struct") { + if validSymbol == true { + returnSymbolLinks.append(symbolLink!) + } + } + } + } + } + return returnSymbolLinks + } + + /// Given a URL, return each of the symbols by their unique identifying links + func findAllSymbolLinks_Full(initialPath: URL) throws -> [URL] { + guard let enumerator = FileManager.default.enumerator( + at: initialPath, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles, + errorHandler: nil + ) else { + return [] + } + + var returnSymbolLinks: [URL] = [] + for case let filePath as URL in enumerator { + if filePath.lastPathComponent.hasSuffix(".json") { + let symbolLink = try findSymbolLink(symbolPath: filePath) + if symbolLink != nil { + returnSymbolLinks.append(symbolLink!) + } + } + } + + return returnSymbolLinks + } + + func findSymbolLink(symbolPath: URL) throws -> URL? { + struct ContainerWithTopicReferenceIdentifier: Codable { + var identifier: ResolvedTopicReference + } + + let renderJSONData = try Data(contentsOf: symbolPath) + let decoder = RenderJSONDecoder.makeDecoder() + + do { + let identifier = try decoder.decode(ContainerWithTopicReferenceIdentifier.self, from: renderJSONData).identifier + return identifier.url + } catch { + return nil + } + } + + func findKind(symbolPath: URL) throws -> String? { + struct ContainerWithKind: Codable { + var metadata: RenderMetadata + } + + let renderJSONData = try Data(contentsOf: symbolPath) + let decoder = RenderJSONDecoder.makeDecoder() + + do { + let metadata = try decoder.decode(ContainerWithKind.self, from: renderJSONData).metadata + return metadata.symbolKind + } catch { + return nil + } + } + + func findClassName(symbolPath: URL) -> String { + return symbolPath.lastPathComponent + } + + /// Process lists of symbols to group them according to the highest level path component, split by spaces. + func groupSymbols(symbolLinks: Set, frameworkName: String) -> String { + // Sort list alphabetically + let sortedSymbols: [URL] = symbolLinks.sorted { $0.absoluteString.localizedCompare($1.absoluteString) == .orderedAscending } + + var links: String = "" + + // find most similar path up until framework name by iterating over path components one at a time + guard var first = sortedSymbols.first else { + return links + } + + for symbol in sortedSymbols.dropFirst() { + let parent: String = first.absoluteString.commonPrefix(with: symbol.absoluteString) + + // If there are no common path components, add a space. Then reset the first to find the next parent. + if parent.localizedLowercase.hasSuffix(frameworkName + "/") { + links.append("\n") + first = symbol + } + + links.append("\n- <\(findExternalLink(identifierURL: symbol))>") + } + + return links + } + + func addClassNames(allSymbolsString: String) -> String { + // Split string into string array on a double newline + return longestCommonPrefix(of: allSymbolsString) + } + + func longestCommonPrefix(of string: String) -> String { + + let words = string.split(separator: " ") + guard let first = words.first else { + return "" + } + + var (minWord, maxWord) = (first, first) + for word in words.dropFirst() { + if word < minWord { + print(word) + print(maxWord) + minWord = word + } else if word > maxWord { + print(word) + print(maxWord) + maxWord = word + } + } + + return minWord.commonPrefix(with: maxWord) + } + + } +} diff --git a/Sources/SwiftDocCUtilities/Docc.swift b/Sources/SwiftDocCUtilities/Docc.swift index 49ac4bb9cf..d714cf5879 100644 --- a/Sources/SwiftDocCUtilities/Docc.swift +++ b/Sources/SwiftDocCUtilities/Docc.swift @@ -18,6 +18,7 @@ private var subcommands: [ParsableCommand.Type] { Docc._Index.self, Docc.Init.self, Docc.Merge.self, + Docc.GenerateChangelog.self, ] #if canImport(NIOHTTP1) subcommands.insert(Docc.Preview.self, at: 1) From 4a47bad80119199f1503ce117fbbcee0e7e8621b Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Mon, 20 May 2024 11:42:49 +0100 Subject: [PATCH 12/14] Cleanup GenerateChangeLog command, removing unnecessary functions. --- .../Model/Rendering/RenderNode.swift | 21 -- .../Subcommands/DiffDocCArchive.swift | 328 ------------------ .../Subcommands/GenerateChangeLog.swift | 52 +-- .../Subcommands/ProcessArchive.swift | 2 +- 4 files changed, 17 insertions(+), 386 deletions(-) delete mode 100644 Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNode.swift b/Sources/SwiftDocC/Model/Rendering/RenderNode.swift index 3e496adb3d..c3ef2d5515 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNode.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNode.swift @@ -275,26 +275,5 @@ public struct RenderNode: VariantContainer { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unknown RenderNode.Kind: '\(unknown)'.") } } - -// // Return the string representing this kind. -// // If the kind is a symbol, return the symbol kind. -// public func kindString() -> String { -// var kind = "" -// -// switch(self) { -// case .article: -// kind = "article" -// case .tutorial: -// kind = "tutorial" -// case .section: -// kind = "section" -// case .overview: -// kind = "overview" -// case .symbol: -// kind = "symbol" -// } -// -// return kind -// } } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift deleted file mode 100644 index 7cf7a615a7..0000000000 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/DiffDocCArchive.swift +++ /dev/null @@ -1,328 +0,0 @@ -/* - This source file is part doccof the Swift.org open source project - - Copyright (c) 2024 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import ArgumentParser -import Foundation -import SwiftDocC - -extension Docc.ProcessArchive { - - struct DiffDocCArchive: ParsableCommand { - - // MARK: - Content and Configuration - - /// Command line configuration. - static var configuration = CommandConfiguration( - commandName: "diff-docc-archive", - abstract: "Produce a markdown file saved as {FrameworkName}_ChangeLog.md containing the diff of added/removed symbols between the two provided DocC archives.", - shouldDisplay: true) - - /// Content of the 'changeLog' template. - static func changeLogTemplateFileContent( - frameworkName: String, - initialDocCArchiveVersion: String, - newerDocCArchiveVersion: String, - additionLinks: String, - removalLinks: String - ) -> [String : String] { - [ - "\(frameworkName.localizedCapitalized)_ChangeLog.md": """ - # \(frameworkName.localizedCapitalized) Updates - - @Metadata { - @PageColor(yellow) - } - - Learn about important changes to \(frameworkName.localizedCapitalized). - - ## Overview - - Browse notable changes in \(frameworkName.localizedCapitalized). - - ## Version: Diff between \(initialDocCArchiveVersion) and \(newerDocCArchiveVersion) - - - ### Change Log - - #### Additions - _New symbols added in \(newerDocCArchiveVersion) that did not previously exist in \(initialDocCArchiveVersion)._ - - \(additionLinks) - - - #### Removals - _Old symbols that existed in \(initialDocCArchiveVersion) that no longer exist in \(newerDocCArchiveVersion)._ - - \(removalLinks) - - """ - ] - } - - // MARK: - Command Line Options & Arguments - - @Argument( - help: ArgumentHelp( - "The version of the initial DocC Archive to be compared.", - valueName: "initialDocCArchiveVersion")) - var initialDocCArchiveVersion: String - - @Argument( - help: ArgumentHelp( - "The path to the initial DocC Archive to be compared.", - valueName: "initialDocCArchive"), - transform: URL.init(fileURLWithPath:)) - var initialDocCArchivePath: URL - - @Argument( - help: ArgumentHelp( - "The version of the newer DocC Archive to be compared.", - valueName: "newerDocCArchiveVersion")) - var newerDocCArchiveVersion: String - - @Argument( - help: ArgumentHelp( - "The path to the newer DocC Archive to be compared.", - valueName: "newerDocCArchive"), - transform: URL.init(fileURLWithPath:)) - var newerDocCArchivePath: URL - - // MARK: - Execution - - public mutating func run() throws { - let initialDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: initialDocCArchivePath) - let newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerDocCArchivePath) - - let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) - let newSet = Set(newDocCArchiveAPIs.map { $0 }) - - // Compute additions and removals to both sets - let additionsToNewSet = newSet.subtracting(initialSet) - let removedFromOldSet = initialSet.subtracting(newSet) - - // The framework name is the path component after "/documentation/". - var frameworkName: String = "No_Framework_Name" - var potentialFrameworkName = try findFrameworkName(initialPath: initialDocCArchivePath) - if potentialFrameworkName == nil { - potentialFrameworkName = try findFrameworkName(initialPath: newerDocCArchivePath) - } - - if potentialFrameworkName != nil { - frameworkName = potentialFrameworkName ?? "No_Framework_Name" - } - - let additionLinks = groupSymbols(symbolLinks: additionsToNewSet, frameworkName: frameworkName) - let removalLinks = groupSymbols(symbolLinks: removedFromOldSet, frameworkName: frameworkName) - - // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. - for fileNameAndContent in Docc.ProcessArchive.DiffDocCArchive.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialDocCArchiveVersion, newerDocCArchiveVersion: newerDocCArchiveVersion, additionLinks: additionLinks, removalLinks: removalLinks) { - let fileName = fileNameAndContent.key - let content = fileNameAndContent.value - let filePath = initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName) - try FileManager.default.createFile(at: filePath, contents: Data(content.utf8)) - print("\nOutput file path: \(filePath)") - } - } - - /// Pretty print all symbols' url identifiers into a pretty format, with a new line between each symbol. - func printAllSymbols(symbols: [URL]) { - for symbol in symbols { - print(symbol) - } - } - - /// The framework name is the path component after "/documentation/". - func findFrameworkName(initialPath: URL) throws -> String? { - guard let enumerator = FileManager.default.enumerator( - at: initialPath, - includingPropertiesForKeys: [], - options: .skipsHiddenFiles, - errorHandler: nil - ) else { - return nil - } - - var frameworkName: String? - for case let filePath as URL in enumerator { - let pathComponents = filePath.pathComponents - var isFrameworkName = false - for pathComponent in pathComponents { - if isFrameworkName { - frameworkName = pathComponent - return frameworkName - } - - if pathComponent == "documentation" { - isFrameworkName = true - } - } - } - - return frameworkName - } - - /// Given the identifier url, cut off everything preceding /documentation/ and append this resulting string to doc: - func findExternalLink(identifierURL: URL) -> String { - var resultantURL = identifierURL.absoluteString - var shouldAppend = false - for pathComponent in identifierURL.pathComponents { - if pathComponent == "documentation" { - resultantURL = "doc:" - shouldAppend = true - } - if shouldAppend { - resultantURL.append(pathComponent + "/") - } - } - return resultantURL - } - - /// Given a URL, return each of the symbols by their unique identifying links - func findAllSymbolLinks(initialPath: URL) throws -> [URL] { - guard let enumerator = FileManager.default.enumerator( - at: initialPath, - includingPropertiesForKeys: [], - options: .skipsHiddenFiles, - errorHandler: nil - ) else { - return [] - } - - var returnSymbolLinks: [URL] = [] - for case let filePath as URL in enumerator { - if filePath.lastPathComponent.hasSuffix(".json") { - let symbolLink = try findSymbolLink(symbolPath: filePath) - if symbolLink != nil { - returnSymbolLinks.append(symbolLink!) - } - } - } - - return returnSymbolLinks - } - - func findSymbolLink(symbolPath: URL) throws -> URL? { - struct ContainerWithTopicReferenceIdentifier: Codable { - var identifier: ResolvedTopicReference - } - - let renderJSONData = try Data(contentsOf: symbolPath) - let decoder = RenderJSONDecoder.makeDecoder() - - do { - let identifier = try decoder.decode(ContainerWithTopicReferenceIdentifier.self, from: renderJSONData).identifier - return identifier.url - } catch { - return nil - } - } - - // TODO: CONTINUE -// func findPageType(symbolPath: URL) throws -> URL? { -// struct ContainerWithPageType: Codable { -// var pageType: NavigatorIndex.PageType -// } -// -// let renderJSONData = try Data(contentsOf: symbolPath) -// let decoder = RenderJSONDecoder.makeDecoder() -// -// do { -// let identifier = try decoder.decode(NavigatorIndex.self, from: renderJSONData). -// return identifier.url -// } catch { -// return nil -// } -// } - - func findClassName(symbolPath: URL) -> String { - return symbolPath.lastPathComponent - } - -// func findClassName(symbolPath: URL) throws -> [[String]]? { -// struct ContainerWithRenderHierarchy: Codable { -// var hierarchy: RenderHierarchy -// } -// -// let renderJSONData = try Data(contentsOf: symbolPath) -// let decoder = RenderJSONDecoder.makeDecoder() -// -// do { -// let hierarchy = try decoder.decode(ContainerWithRenderHierarchy.self, from: renderJSONData).hierarchy -// -// if hierarchy != nil { -// return hierarchy.paths -// } -// -// return identifier.url -// } catch { -// return nil -// } -// } - - /// Process lists of symbols to group them according to the highest level path component, split by spaces. - func groupSymbols(symbolLinks: Set, frameworkName: String) -> String { - // Sort list alphabetically - let sortedSymbols: [URL] = symbolLinks.sorted { $0.absoluteString.localizedCompare($1.absoluteString) == .orderedAscending } - - var links: String = "" - - // find most similar path up until framework name by iterating over path components one at a time - guard var first = sortedSymbols.first else { - return links - } - - for symbol in sortedSymbols.dropFirst() { - let parent: String = first.absoluteString.commonPrefix(with: symbol.absoluteString) - - // If there are no common path components, add a space. Then reset the first to find the next parent. - if parent.localizedLowercase.hasSuffix(frameworkName + "/") { - links.append("\n \n") - first = symbol - } - - links.append("\n- <\(findExternalLink(identifierURL: symbol))>") - } - - return links - } - - func addClassNames(allSymbolsString: String) -> String { -// let processedString = "" - - // Split string into string array on a double newline - return longestCommonPrefix(of: allSymbolsString) - } - - func longestCommonPrefix(of string: String) -> String { - - let words = string.split(separator: " ") -// let words = string.split(separator: "\n\n") - guard let first = words.first else { - return "" - } - - var (minWord, maxWord) = (first, first) - for word in words.dropFirst() { - if word < minWord { - print(word) - print(maxWord) - minWord = word - } else if word > maxWord { - print(word) - print(maxWord) - maxWord = word - } - } - - return minWord.commonPrefix(with: maxWord) - } - - } -} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift index 781fffaa6f..266da55e3e 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift @@ -83,9 +83,21 @@ extension Docc { transform: URL.init(fileURLWithPath:)) var newerDocCArchivePath: URL + @Option( + name: [.customLong("initial-archive-name", withSingleDash: false)], + help: "The name of the initial DocC Archive version to be compared." + ) + var initialArchiveName: String = "Version 1" + + @Option( + name: [.customLong("newer-archive-name", withSingleDash: false)], + help: "The name of the newer DocC Archive version to be compared." + ) + var newerArchiveName: String = "Version 2" + @Option( name: [.customLong("show-all", withSingleDash: false)], - help: "Produces full symbol diff: including all properties, methods, and overrides" + help: "Boolean value to indicate whether to produce a full symbol diff, including all properties, methods, and overrides" ) var showAllSymbols: Bool = false @@ -96,11 +108,11 @@ extension Docc { var newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerDocCArchivePath) if showAllSymbols { - print("Showing ALL symbols") + print("Showing ALL symbols.") initialDocCArchiveAPIs = try findAllSymbolLinks_Full(initialPath: initialDocCArchivePath) newDocCArchiveAPIs = try findAllSymbolLinks_Full(initialPath: newerDocCArchivePath) } else { - print("Showing ONLY modules, classes, protocols, and structs.") + print("Showing ONLY high-level symbol diffs: modules, classes, protocols, and structs.") } let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) @@ -125,7 +137,7 @@ extension Docc { let removalLinks = groupSymbols(symbolLinks: removedFromOldSet, frameworkName: frameworkName) // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. - for fileNameAndContent in Docc.GenerateChangelog.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: "RainbowF RC", newerDocCArchiveVersion: "Geode Beta 1", additionLinks: additionLinks, removalLinks: removalLinks) { + for fileNameAndContent in Docc.GenerateChangelog.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialArchiveName, newerDocCArchiveVersion: newerArchiveName, additionLinks: additionLinks, removalLinks: removalLinks) { let fileName = fileNameAndContent.key let content = fileNameAndContent.value let filePath = initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName) @@ -292,10 +304,6 @@ extension Docc { } } - func findClassName(symbolPath: URL) -> String { - return symbolPath.lastPathComponent - } - /// Process lists of symbols to group them according to the highest level path component, split by spaces. func groupSymbols(symbolLinks: Set, frameworkName: String) -> String { // Sort list alphabetically @@ -323,33 +331,5 @@ extension Docc { return links } - func addClassNames(allSymbolsString: String) -> String { - // Split string into string array on a double newline - return longestCommonPrefix(of: allSymbolsString) - } - - func longestCommonPrefix(of string: String) -> String { - - let words = string.split(separator: " ") - guard let first = words.first else { - return "" - } - - var (minWord, maxWord) = (first, first) - for word in words.dropFirst() { - if word < minWord { - print(word) - print(maxWord) - minWord = word - } else if word > maxWord { - print(word) - print(maxWord) - maxWord = word - } - } - - return minWord.commonPrefix(with: maxWord) - } - } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift index 279c8b39f2..59da03fff5 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/ProcessArchive.swift @@ -18,7 +18,7 @@ extension Docc { static var configuration = CommandConfiguration( commandName: "process-archive", abstract: "Perform operations on documentation archives ('.doccarchive' directories).", - subcommands: [TransformForStaticHosting.self, Index.self, DiffDocCArchive.self]) + subcommands: [TransformForStaticHosting.self, Index.self]) } } From 05b5e76ce0813880562adeca1d47e6ed87bedf3a Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 21 May 2024 11:28:02 +0100 Subject: [PATCH 13/14] Refactor to resolve some PR comments. --- .../Actions/Init/CatalogTemplateKind.swift | 42 ++++++++++++++ .../Subcommands/GenerateChangeLog.swift | 58 ++----------------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift index 0cbf321890..f356d71be8 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/Init/CatalogTemplateKind.swift @@ -87,4 +87,46 @@ extension CatalogTemplateKind { """ ] } + + /// Content of the 'changeLog' template + static func changeLogTemplateFileContent( + frameworkName: String, + initialDocCArchiveVersion: String, + newerDocCArchiveVersion: String, + additionLinks: String, + removalLinks: String + ) -> [String : String] { + [ + "\(frameworkName.localizedCapitalized)_Changelog.md": """ + # \(frameworkName.localizedCapitalized) Updates + + @Metadata { + @PageColor(yellow) + } + + Learn about important changes to \(frameworkName.localizedCapitalized). + + ## Overview + + Browse notable changes in \(frameworkName.localizedCapitalized). + + ## Diff between \(initialDocCArchiveVersion) and \(newerDocCArchiveVersion) + + + ### Change Log + + #### Additions + _New symbols added in \(newerDocCArchiveVersion) that did not previously exist in \(initialDocCArchiveVersion)._ + + \(additionLinks) + + + #### Removals + _Old symbols that existed in \(initialDocCArchiveVersion) that no longer exist in \(newerDocCArchiveVersion)._ + + \(removalLinks) + + """ + ] + } } diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift index 266da55e3e..5dbe2a129c 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift @@ -16,7 +16,7 @@ extension Docc { struct GenerateChangelog: ParsableCommand { - // MARK: - Content and Configuration + // MARK: - Configuration /// Command line configuration. static var configuration = CommandConfiguration( @@ -24,48 +24,6 @@ extension Docc { abstract: "Generate a changelog with symbol diffs between documentation archives ('.doccarchive' directories).", shouldDisplay: true) - /// Content of the 'changeLog' template. - static func changeLogTemplateFileContent( - frameworkName: String, - initialDocCArchiveVersion: String, - newerDocCArchiveVersion: String, - additionLinks: String, - removalLinks: String - ) -> [String : String] { - [ - "\(frameworkName.localizedCapitalized)_Changelog.md": """ - # \(frameworkName.localizedCapitalized) Updates - - @Metadata { - @PageColor(yellow) - } - - Learn about important changes to \(frameworkName.localizedCapitalized). - - ## Overview - - Browse notable changes in \(frameworkName.localizedCapitalized). - - ## Diff between \(initialDocCArchiveVersion) and \(newerDocCArchiveVersion) - - - ### Change Log - - #### Additions - _New symbols added in \(newerDocCArchiveVersion) that did not previously exist in \(initialDocCArchiveVersion)._ - - \(additionLinks) - - - #### Removals - _Old symbols that existed in \(initialDocCArchiveVersion) that no longer exist in \(newerDocCArchiveVersion)._ - - \(removalLinks) - - """ - ] - } - // MARK: - Command Line Options & Arguments @@ -115,29 +73,25 @@ extension Docc { print("Showing ONLY high-level symbol diffs: modules, classes, protocols, and structs.") } - let initialSet = Set(initialDocCArchiveAPIs.map { $0 }) - let newSet = Set(newDocCArchiveAPIs.map { $0 }) + let initialSet = Set(initialDocCArchiveAPIs) + let newSet = Set(newDocCArchiveAPIs) // Compute additions and removals to both sets let additionsToNewSet = newSet.subtracting(initialSet) let removedFromOldSet = initialSet.subtracting(newSet) // The framework name is the path component after "/documentation/". - var frameworkName: String = "No_Framework_Name" var potentialFrameworkName = try findFrameworkName(initialPath: initialDocCArchivePath) if potentialFrameworkName == nil { potentialFrameworkName = try findFrameworkName(initialPath: newerDocCArchivePath) } - - if potentialFrameworkName != nil { - frameworkName = potentialFrameworkName ?? "No_Framework_Name" - } + let frameworkName: String = potentialFrameworkName ?? "No_Framework_Name" let additionLinks = groupSymbols(symbolLinks: additionsToNewSet, frameworkName: frameworkName) let removalLinks = groupSymbols(symbolLinks: removedFromOldSet, frameworkName: frameworkName) // Create markdown file with changes in the newer DocC Archive that do not exist in the initial DocC Archive. - for fileNameAndContent in Docc.GenerateChangelog.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialArchiveName, newerDocCArchiveVersion: newerArchiveName, additionLinks: additionLinks, removalLinks: removalLinks) { + for fileNameAndContent in CatalogTemplateKind.changeLogTemplateFileContent(frameworkName: frameworkName, initialDocCArchiveVersion: initialArchiveName, newerDocCArchiveVersion: newerArchiveName, additionLinks: additionLinks, removalLinks: removalLinks) { let fileName = fileNameAndContent.key let content = fileNameAndContent.value let filePath = initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName) @@ -145,7 +99,6 @@ extension Docc { print("\nOutput file path: \(filePath)") } } - /// Pretty print all symbols' url identifiers into a pretty format, with a new line between each symbol. func printAllSymbols(symbols: [URL]) { @@ -154,7 +107,6 @@ extension Docc { } } - /// The framework name is the path component after "/documentation/". func findFrameworkName(initialPath: URL) throws -> String? { guard let enumerator = FileManager.default.enumerator( From b9387365503a94731573f8b84f59e98ed5457471 Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 21 May 2024 17:27:26 +0100 Subject: [PATCH 14/14] Use LogHandle instead of just print(). --- .../Subcommands/GenerateChangeLog.swift | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift index 5dbe2a129c..b7962a9db0 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/GenerateChangeLog.swift @@ -17,6 +17,8 @@ extension Docc { struct GenerateChangelog: ParsableCommand { // MARK: - Configuration + + static var logHandle: LogHandle = .standardOutput /// Command line configuration. static var configuration = CommandConfiguration( @@ -62,15 +64,17 @@ extension Docc { // MARK: - Execution public mutating func run() throws { - var initialDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: initialDocCArchivePath) - var newDocCArchiveAPIs: [URL] = try findAllSymbolLinks(initialPath: newerDocCArchivePath) + var initialDocCArchiveAPIs: [URL] = [] + var newDocCArchiveAPIs: [URL] = [] if showAllSymbols { - print("Showing ALL symbols.") + print("Showing ALL symbols.", to: &Docc.GenerateChangelog.logHandle) initialDocCArchiveAPIs = try findAllSymbolLinks_Full(initialPath: initialDocCArchivePath) newDocCArchiveAPIs = try findAllSymbolLinks_Full(initialPath: newerDocCArchivePath) } else { - print("Showing ONLY high-level symbol diffs: modules, classes, protocols, and structs.") + print("Showing ONLY high-level symbol diffs: modules, classes, protocols, and structs.", to: &Docc.GenerateChangelog.logHandle) + initialDocCArchiveAPIs = try findAllSymbolLinks(initialPath: initialDocCArchivePath) + newDocCArchiveAPIs = try findAllSymbolLinks(initialPath: newerDocCArchivePath) } let initialSet = Set(initialDocCArchiveAPIs) @@ -96,14 +100,7 @@ extension Docc { let content = fileNameAndContent.value let filePath = initialDocCArchivePath.deletingLastPathComponent().appendingPathComponent(fileName) try FileManager.default.createFile(at: filePath, contents: Data(content.utf8)) - print("\nOutput file path: \(filePath)") - } - } - - /// Pretty print all symbols' url identifiers into a pretty format, with a new line between each symbol. - func printAllSymbols(symbols: [URL]) { - for symbol in symbols { - print(symbol) + print("\nOutput file path: \(filePath)", to: &Docc.GenerateChangelog.logHandle) } }