Skip to content

Diff starter #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 74 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
8ec4f82
Adds some initial structure for diffing two pieces of RenderJSON
ethan-kusters May 19, 2022
914a844
RenderNode, Optional, ResolvedTopicReference, RenderMetadata, Module,…
May 24, 2022
588f9d5
Added diffing for the hierarchy
May 24, 2022
b91421c
Improve performance of static hosting transformation
ethan-kusters May 22, 2022
ed1244a
Remove unnecessary disk access when visiting images
ethan-kusters May 22, 2022
99776d6
Specify `isDirectory` parameter when creating/modifying URLs
ethan-kusters May 23, 2022
3e496c7
Don't write RenderJSON atomically
ethan-kusters May 22, 2022
5a68281
Navigator index generation performance improvements
ethan-kusters May 22, 2022
d746462
Update swift-markdown from caafc56d to 97df6e2
ethan-kusters May 21, 2022
3433b8c
Allow writing doc links to subsections using special characters (#262)
d-ronnqvist May 25, 2022
6140cac
Switched to diffing arrays instead of BidirectionalCollections
May 25, 2022
3007c79
TopicSections and SeeAlsoSections can be diffed
May 25, 2022
b118908
Finished diffing for RenderMetadata
May 25, 2022
1049414
Added comments, finished diffing RenderTutorialsHierarchy, tried to g…
May 26, 2022
f23c513
Add the ability to add arbitrary attributes to a NavigatorIndex
daniel-grumberg May 11, 2022
ffb520a
Add Diffable functionality to RenderReferences
May 27, 2022
962f3c1
Made RenderReferences work with Diffable protocol
May 28, 2022
a89eea3
Cleaned up path and difference types for switching over to CodingKeys
May 28, 2022
f5c2409
Added ability to diff RenderSections
May 31, 2022
0e18f92
Debugs issue where two RenderReferences were incorrectly considered d…
May 31, 2022
fb6e9cc
Cleans up comments and corrects path names
May 31, 2022
049540a
Show user-friendly diagnostic for Info.plist decode error
Kyle-Ye May 29, 2022
41f116f
Update build script helper to use python3 (#273)
ethan-kusters Jun 2, 2022
85ff312
Encodes differences between the kind property and the abstract in Ren…
Jun 2, 2022
ecbe39f
Removes unnecessary VersionDifferences struct
Jun 2, 2022
878dca8
Moved diffing methods for SemanticVersions, RenderMetadata, and Resol…
Jun 3, 2022
ff7f8ac
Adds diffing for seeAlsoSections and topicSections
Jun 4, 2022
a99c27f
Progress on diffing dictionaries, stuck on inablility to dynamically …
Jun 7, 2022
685fe40
Adds ability to diff arrays of RenderSections
Jun 7, 2022
86411e6
Change VersionPatch to comply with new RenderNode JSON spec
Jun 7, 2022
eeec50b
Merge pull request #3 from mayaepps/encoding-diffs
mayaepps Jun 7, 2022
7ef549a
Adds a command-line argument to give docc a previous archive path to …
Jun 8, 2022
aeffa70
Uses DocumentationArchiveOption to validate the DocC Archive passed o…
Jun 8, 2022
6804ce0
Pulls out validation logic for DocCArchiveOption and reuses that logi…
Jun 8, 2022
43d87b4
Adds checking path is a directory to validateDocCArchive, and removes…
Jun 8, 2022
4dc663e
Copies over the old archive into the new archive so old assets and fi…
Jun 9, 2022
6af3deb
Don’t filter topic group items from unavailable languages (#283)
ethan-kusters Jun 9, 2022
393bbc6
Refactor render node output consumer test code
franklinsch May 9, 2022
b3c12e9
Don't emit variants for article-only catalogs
franklinsch May 9, 2022
0d6be32
Fix `ResolvedTopicReference` memory leak when not converting (#287)
ethan-kusters Jun 10, 2022
e0ae261
Fixes 'file already exists' error by only copying the data directory …
Jun 10, 2022
a4eb7bc
Fixes tests due to changes in ArchiveVersion to fit spec, addition of…
Jun 10, 2022
54518cb
Adds a print statement indicating whether a previous DocC Archive has…
Jun 10, 2022
c821da0
Adds some initial structure for diffing two pieces of RenderJSON
ethan-kusters May 19, 2022
3d0efcb
RenderNode, Optional, ResolvedTopicReference, RenderMetadata, Module,…
May 24, 2022
b7baff9
Added diffing for the hierarchy
May 24, 2022
c667f90
Switched to diffing arrays instead of BidirectionalCollections
May 25, 2022
ff1b242
TopicSections and SeeAlsoSections can be diffed
May 25, 2022
fd6ad83
Finished diffing for RenderMetadata
May 25, 2022
1017ccb
Added comments, finished diffing RenderTutorialsHierarchy, tried to g…
May 26, 2022
0b1e55e
Add Diffable functionality to RenderReferences
May 27, 2022
1321235
Made RenderReferences work with Diffable protocol
May 28, 2022
837216a
Cleaned up path and difference types for switching over to CodingKeys
May 28, 2022
5d61f96
Added ability to diff RenderSections
May 31, 2022
660d061
Debugs issue where two RenderReferences were incorrectly considered d…
May 31, 2022
1d46102
Cleans up comments and corrects path names
May 31, 2022
9cd3de6
Encodes differences between the kind property and the abstract in Ren…
Jun 2, 2022
9a3b154
Removes unnecessary VersionDifferences struct
Jun 2, 2022
e36c18c
Moved diffing methods for SemanticVersions, RenderMetadata, and Resol…
Jun 3, 2022
8227b0a
Adds diffing for seeAlsoSections and topicSections
Jun 4, 2022
971a12a
Progress on diffing dictionaries, stuck on inablility to dynamically …
Jun 7, 2022
c8484ba
Adds ability to diff arrays of RenderSections
Jun 7, 2022
88fdc93
Change VersionPatch to comply with new RenderNode JSON spec
Jun 7, 2022
6d5963b
Adds a command-line argument to give docc a previous archive path to …
Jun 8, 2022
192bc9a
Uses DocumentationArchiveOption to validate the DocC Archive passed o…
Jun 8, 2022
5282c8c
Pulls out validation logic for DocCArchiveOption and reuses that logi…
Jun 8, 2022
e28883e
Adds checking path is a directory to validateDocCArchive, and removes…
Jun 8, 2022
da2304c
Copies over the old archive into the new archive so old assets and fi…
Jun 9, 2022
71f9730
Fixes 'file already exists' error by only copying the data directory …
Jun 10, 2022
339c25c
Fixes tests due to changes in ArchiveVersion to fit spec, addition of…
Jun 10, 2022
fef98f5
Adds a print statement indicating whether a previous DocC Archive has…
Jun 10, 2022
292ca0c
Adds decoding of the version display name and identifier in the previ…
Jun 11, 2022
f4e4e68
Merge branch 'diff-starter' of https://github.com/mayaepps/swift-docc…
Jun 11, 2022
c12167d
Adds a version to the RenderMetadata, diffs against the previous Rend…
Jun 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"repositoryURL": "https://github.com/apple/swift-markdown.git",
"state": {
"branch": "main",
"revision": "caafc56d3794a08c2203fe417b3aff81e2ab2fc1",
"revision": "97df6e2812adcf8698204ca5f0756563ef36e5c1",
"version": null
}
},
Expand Down
2 changes: 2 additions & 0 deletions Sources/DocCDocumentation/DocCDocumentation.docc/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDBundleVersionDisplayName</key>
<string>0.1.0</string>
<key>CDDefaultModuleKind</key>
<string>Tool</string>
</dict>
Expand Down
38 changes: 38 additions & 0 deletions Sources/SwiftDocC/Converter/RenderNode+Coding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,31 @@ extension CodingUserInfoKey {
static let variantOverrides = CodingUserInfoKey(rawValue: "variantOverrides")!

static let baseEncodingPath = CodingUserInfoKey(rawValue: "baseEncodingPath")!

/// A user info key that encapsulates version patches.
///
/// This key is used by encoders to accumulate the ``VersionPatch`` between this RenderNode and the previousNode.
static let versionPatch = CodingUserInfoKey(rawValue: "versionPatch")!

/// A user key that encapsulates the previous RenderNode that will be compared to this one to find differences and create patches.
static let previousNode = CodingUserInfoKey(rawValue: "previousNode")!
}

extension Encoder {
/// The variant overrides accumulated as part of the encoding process.
var userInfoVariantOverrides: VariantOverrides? {
userInfo[.variantOverrides] as? VariantOverrides
}

/// The version patch accumulated as part of the encoding process.
var userInfoVersionPatch: VersionPatch? {
userInfo[.versionPatch] as? VersionPatch
}

/// The previous RenderNode to be used in the diffing process.
var userInfoPreviousNode: RenderNode? {
userInfo[.previousNode] as? RenderNode
}

/// The base path to use when creating dynamic JSON pointers
/// with this encoder.
Expand Down Expand Up @@ -65,6 +83,26 @@ extension JSONEncoder {
}
}

/// The version patch accumulated as part of the encoding process.
public var userInfoVersionPatch: VersionPatch? {
get {
userInfo[.versionPatch] as? VersionPatch
}
set {
userInfo[.versionPatch] = newValue
}
}

/// The previous RenderNode to be used in the diffing process.
public var userInfoPreviousNode: RenderNode? {
get {
userInfo[.previousNode] as? RenderNode
}
set {
userInfo[.previousNode] = newValue
}
}

/// The base path to use when creating dynamic JSON pointers
/// with this encoder.
var baseJSONPatchPath: [String]? {
Expand Down
16 changes: 13 additions & 3 deletions Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class FileSystemRenderNodeProvider: RenderNodeProvider {
}

extension RenderNode {
private static let typesThatShouldNotUseNavigatorTitle: Set<NavigatorIndex.PageType> = [
.framework, .class, .structure, .enumeration, .protocol, .typeAlias, .associatedType
]

/// Returns a navigator title preferring the fragments inside the metadata, if applicable.
func navigatorTitle() -> String? {
Expand All @@ -83,7 +86,9 @@ extension RenderNode {
// FIXME: Use `metadata.navigatorTitle` for all Swift symbols (github.com/apple/swift-docc/issues/176).
if identifier.sourceLanguage == .swift || (metadata.navigatorTitle ?? []).isEmpty {
let pageType = navigatorPageType()
guard ![.framework, .class, .structure, .enumeration, .protocol, .typeAlias, .associatedType].contains(pageType) else { return metadata.title }
guard !Self.typesThatShouldNotUseNavigatorTitle.contains(pageType) else {
return metadata.title
}
fragments = metadata.fragments
} else {
fragments = metadata.navigatorTitle
Expand All @@ -96,8 +101,13 @@ extension RenderNode {
public func navigatorPageType() -> NavigatorIndex.PageType {

// This is a workaround to support plist keys.
if metadata.roleHeading?.lowercased() == "property list key" { return .propertyListKey }
else if metadata.roleHeading?.lowercased() == "property list key reference" { return .propertyListKeyReference }
if let roleHeading = metadata.roleHeading?.lowercased() {
if roleHeading == "property list key" {
return .propertyListKey
} else if roleHeading == "property list key reference" {
return .propertyListKeyReference
}
}

switch self.kind {
case .article:
Expand Down
180 changes: 125 additions & 55 deletions Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,48 +136,134 @@ public class NavigatorIndex {
}()

/**
Initialize an `NavigatorIndex` from a given path.
Initializes a `NavigatorIndex` from a given path on disk.

- Parameter url: The URL pointing to the path from which the index should be read.
- Parameter bundleIdentifier: The name of the bundle the index is referring to.
- Parameter readNavigatorTree: Indicates if the init needs to read the navigator tree from the disk, if false, then `readNavigatorTree` needs to be called later. Default: `true`.
- Parameter presentationIdentifier: Indicates if the index has an indentifier useful for presentation contexts.
Most uses should be made using just the url parameter:
```swift
let indexFilePath = URL(string: "file://path/to/index/on/disk")
let index = NavigatorIndex.readNavigatorIndex(url: indexFilePath)
```

- Parameters:
- url: The URL pointing to the path from which the index should be read.
- bundleIdentifier: The name of the bundle the index is referring to.
- readNavigatorTree: Indicates if the init the navigator tree should be read from the disk now or later, if false, then `readNavigatorTree` needs to be called later. Default: `true`.
- presentationIdentifier: Indicates if the index has an identifier useful for presentation contexts.
- onNodeRead: An action to perform after reading a node. This allows clients to perform arbitrary actions on the node while it is being read from disk. This is useful for clients wanting to attach data to ``NavigatorTree/Node/attributes``.

- Throws: A `NavigatorIndex.Error` describing the nature of the problem.

- Note: The index powered by LMDB opens in `readOnly` mode to avoid performing a filesystem lock which fails without writing permissions. As this initializer opens a built index, write permission is not expected.
*/
public init(url: URL, bundleIdentifier: String? = nil, readNavigatorTree: Bool = true, presentationIdentifier: String? = nil) throws {
self.url = url

public static func readNavigatorIndex(
url: URL,
bundleIdentifier: String? = nil,
readNavigatorTree: Bool = true,
presentationIdentifier: String? = nil,
onNodeRead: ((NavigatorTree.Node) -> Void)? = nil
) throws -> NavigatorIndex {
// To avoid performing a filesystem lock which might fail without write permission, we pass `.readOnly` and `.noLock` to open the index.
let environment = try LMDB.Environment(path: url.path, flags: [.readOnly, .noLock], maxDBs: 4, mapSize: 100 * 1024 * 1024) // mapSize = 100MB
self.environment = environment
self.database = try environment.openDatabase(named: "index", flags: [])
self.availability = try environment.openDatabase(named: "availability", flags: [])
let database = try environment.openDatabase(named: "index", flags: [])
let availability = try environment.openDatabase(named: "availability", flags: [])

let information = try environment.openDatabase(named: "information", flags: [])
self.information = information

let data = try Data(contentsOf: url.appendingPathComponent("availability.index"))
let data = try Data(contentsOf: url.appendingPathComponent("availability.index", isDirectory: false))
let plistDecoder = PropertyListDecoder()
let availabilityIndex = try plistDecoder.decode(AvailabilityIndex.self, from: data)
self.availabilityIndex = availabilityIndex
let bundleIdentifier = bundleIdentifier ?? information.get(type: String.self, forKey: NavigatorIndex.bundleKey) ?? NavigatorIndex.UnknownBundleIdentifier

guard bundleIdentifier != NavigatorIndex.UnknownBundleIdentifier else {
throw Error.missingBundleIndentifier
}

self.presentationIdentifier = presentationIdentifier
self.bundleIdentifier = bundleIdentifier ?? information.get(type: String.self, forKey: NavigatorIndex.bundleKey) ?? NavigatorIndex.UnknownBundleIdentifier
// Use `.fnv1` by default if no path hasher is set for compatibility reasons.
self.pathHasher = PathHasher(rawValue: information.get(type: String.self, forKey: NavigatorIndex.pathHasherKey) ?? "") ?? .fnv1
let pathHasher = PathHasher(rawValue: information.get(type: String.self, forKey: NavigatorIndex.pathHasherKey) ?? "") ?? .fnv1

let navigatorTree: NavigatorTree
if readNavigatorTree {
self.navigatorTree = try NavigatorTree.read(from: url.appendingPathComponent("navigator.index"), bundleIdentifier: self.bundleIdentifier, interfaceLanguages: availabilityIndex.interfaceLanguages, presentationIdentifier: presentationIdentifier)
navigatorTree = try NavigatorTree.read(
from: url.appendingPathComponent("navigator.index", isDirectory: false),
bundleIdentifier: bundleIdentifier,
interfaceLanguages: availabilityIndex.interfaceLanguages,
presentationIdentifier: presentationIdentifier,
onNodeRead: onNodeRead)
} else {
self.navigatorTree = NavigatorTree()
navigatorTree = NavigatorTree()
}

guard self.bundleIdentifier != NavigatorIndex.UnknownBundleIdentifier else {
throw Error.missingBundleIndentifier
}
return NavigatorIndex(
url: url,
presentationIdentifier: presentationIdentifier,
bundleIdentifier: bundleIdentifier,
environment: environment,
database: database,
availability: availability,
information: information,
availabilityIndex: availabilityIndex,
pathHasher: pathHasher,
navigatorTree: navigatorTree
)
}

fileprivate init(
url: URL,
presentationIdentifier: String?,
bundleIdentifier: String,
environment: LMDB.Environment,
database: LMDB.Database,
availability: LMDB.Database,
information: LMDB.Database,
availabilityIndex: AvailabilityIndex,
pathHasher: PathHasher,
navigatorTree: NavigatorTree
) {
self.url = url
self.presentationIdentifier = presentationIdentifier
self.bundleIdentifier = bundleIdentifier
self.environment = environment
self.database = database
self.availability = availability
self.information = information
self.availabilityIndex = availabilityIndex
self.pathHasher = pathHasher
self.navigatorTree = navigatorTree
}
/**
Initialize an `NavigatorIndex` from a given path.

- Parameters:
- url: The URL pointing to the path from which the index should be read.
- bundleIdentifier: The name of the bundle the index is referring to.
- readNavigatorTree: Indicates if the init needs to read the navigator tree from the disk, if false, then `readNavigatorTree` needs to be called later. Default: `true`.
- presentationIdentifier: Indicates if the index has an indentifier useful for presentation contexts.

- Throws: A `NavigatorIndex.Error` describing the nature of the problem.

- Note: The index powered by LMDB opens in `readOnly` mode to avoid performing a filesystem lock which fails without writing permissions. As this initializer opens a built index, write permission is not expected.
*/
@available(*, deprecated, message: "Use NavigatorIndex.readNavigatorIndex instead")
public convenience init(url: URL, bundleIdentifier: String? = nil, readNavigatorTree: Bool = true, presentationIdentifier: String? = nil) throws {
let navigator = try NavigatorIndex.readNavigatorIndex(
url: url,
bundleIdentifier: bundleIdentifier,
readNavigatorTree: readNavigatorTree,
presentationIdentifier: presentationIdentifier
)

self.init(
url: navigator.url,
presentationIdentifier: navigator.presentationIdentifier,
bundleIdentifier: navigator.bundleIdentifier,
environment: navigator.environment!,
database: navigator.database!,
availability: navigator.availability!,
information: navigator.information!,
availabilityIndex: navigator.availabilityIndex,
pathHasher: navigator.pathHasher,
navigatorTree: navigator.navigatorTree
)
}

/**
Expand Down Expand Up @@ -409,12 +495,14 @@ public class NavigatorIndex {
}

extension ResolvedTopicReference {
func navigatorIndexIdentifier(
func normalizedNavigatorIndexIdentifier(
forLanguage languageIdentifier: InterfaceLanguage.ID
) -> NavigatorIndex.Identifier {
let normalizedPath = NodeURLGenerator.fileSafeReferencePath(self, lowercased: true)

return NavigatorIndex.Identifier(
bundleIdentifier: bundleIdentifier,
path: path,
bundleIdentifier: bundleIdentifier.lowercased(),
path: "/" + normalizedPath,
fragment: fragment,
languageIdentifier: languageIdentifier
)
Expand Down Expand Up @@ -586,10 +674,6 @@ extension NavigatorIndex {
throw Error.navigatorIndexIsNil
}

guard let title = (usePageTitle) ? renderNode.metadata.title : renderNode.navigatorTitle() else {
throw Error.missingTitle(description: "\(renderNode.identifier.absoluteString.singleQuoted) has an empty title and so can't have a usable entry in the index.")
}

// Process the language
let interfaceLanguage = renderNode.identifier.sourceLanguage
let interfaceLanguageID = interfaceLanguage.id.lowercased()
Expand All @@ -605,13 +689,20 @@ extension NavigatorIndex {
idToLanguage[interfaceLanguageID] = language
}

let identifier = renderNode.identifier.navigatorIndexIdentifier(forLanguage: language.mask)
guard identifierToNode[identifier] == nil else {
let normalizedIdentifier = renderNode
.identifier
.normalizedNavigatorIndexIdentifier(forLanguage: language.mask)

guard identifierToNode[normalizedIdentifier] == nil else {
return // skip as item exists already.
}

guard let title = (usePageTitle) ? renderNode.metadata.title : renderNode.navigatorTitle() else {
throw Error.missingTitle(description: "\(renderNode.identifier.absoluteString.singleQuoted) has an empty title and so can't have a usable entry in the index.")
}

// Get the identifier path
let identifierPath = NodeURLGenerator().urlForReference(renderNode.identifier, lowercased: true).path
let identifierPath = normalizedIdentifier.path

// Store the language inside the availability index.
navigatorIndex.availabilityIndex.add(language: language)
Expand Down Expand Up @@ -688,7 +779,7 @@ extension NavigatorIndex {
let fragment = "\(title)#\(index)".addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!

let groupIdentifier = Identifier(
bundleIdentifier: identifier.bundleIdentifier,
bundleIdentifier: normalizedIdentifier.bundleIdentifier,
path: identifierPath,
fragment: fragment,
languageIdentifier: language.mask
Expand Down Expand Up @@ -737,20 +828,14 @@ extension NavigatorIndex {
}
}

let normalizedIdentifier = renderNode
.identifier
.normalizedForNavigation
.navigatorIndexIdentifier(forLanguage: language.mask)

// Keep track of the node
identifierToNode[normalizedIdentifier] = navigatorNode
identifierToChildren[normalizedIdentifier] = children
pendingUncuratedReferences.insert(normalizedIdentifier)

// Track a multiple curated node
if multiCuratedUnvisited.contains(normalizedIdentifier) {
if multiCuratedUnvisited.remove(normalizedIdentifier) != nil {
multiCurated[normalizedIdentifier] = navigatorNode
multiCuratedUnvisited.remove(normalizedIdentifier)
}

// Bump the nodes counter.
Expand Down Expand Up @@ -1206,21 +1291,6 @@ fileprivate extension Error {
}


extension ResolvedTopicReference {

/// Returns a normalized instance useful to build a navigator index.
/// - Note: This logic relies on what `PresentationURLGenerator.presentationURLForReference(_: _:)` does in the last line of code.
/// Changing the logic of this normalization method without fixing the other logic would generate a mismatch and break the navigator index process.
var normalizedForNavigation: ResolvedTopicReference {
let normalizedPath = NodeURLGenerator().urlForReference(self).path
return ResolvedTopicReference(bundleIdentifier: bundleIdentifier.lowercased(),
path: normalizedPath.lowercased(),
fragment: fragment,
sourceLanguages: sourceLanguages)
}

}

extension LMDB.Database {
enum NodeError: Error {
/// A database error that includes the path of a specific node and the original database error.
Expand Down
Loading