From e1b8d78945afd30b020068249582ff4a77f45927 Mon Sep 17 00:00:00 2001 From: Mattt Date: Mon, 23 Mar 2020 04:16:15 -0700 Subject: [PATCH 01/11] Initial implementation of HTML support --- Package.resolved | 46 +- Package.swift | 11 +- .../SwiftDoc/Extensions/Array+Parallel.swift | 16 +- Sources/SwiftDoc/Interface.swift | 58 +- Sources/SwiftDoc/Module.swift | 4 +- Sources/SwiftDoc/SourceFile.swift | 26 +- Sources/SwiftDoc/Symbol.swift | 98 +- .../Extensions/CommonMark+Extensions.swift | 8 + .../Extensions/DCOV+Extensions.swift | 2 +- .../HypertextLiteral+Extensions.swift | 9 + .../Extensions/SwiftDoc+Extensions.swift | 76 ++ Sources/swift-doc/Subcommands/Diagram.swift | 5 +- Sources/swift-doc/Subcommands/Generate.swift | 62 +- .../Supporting Types/Component.swift | 8 +- .../Components/ConformingTypes.swift | 17 +- .../Components/Declaration.swift | 33 + .../Components/Documentation.swift | 104 +- .../Components/Inheritance.swift | 29 +- .../Supporting Types/Components/Members.swift | 90 +- .../Components/NestedTypes.swift | 17 +- .../Components/Requirements.swift | 11 +- .../swift-doc/Supporting Types/Helpers.swift | 92 ++ .../swift-doc/Supporting Types/Layout.swift | 1146 +++++++++++++++++ Sources/swift-doc/Supporting Types/Page.swift | 35 +- .../Supporting Types/Pages/FooterPage.swift | 28 +- .../Supporting Types/Pages/GlobalPage.swift | 32 +- .../Supporting Types/Pages/HomePage.swift | 75 +- .../Supporting Types/Pages/SidebarPage.swift | 35 +- .../Supporting Types/Pages/TypePage.swift | 31 +- .../Pages/TypealiasPage.swift | 22 +- Tests/SwiftDocTests/SourceFileTests.swift | 28 +- 31 files changed, 2035 insertions(+), 219 deletions(-) create mode 100644 Sources/swift-doc/Extensions/CommonMark+Extensions.swift create mode 100644 Sources/swift-doc/Extensions/HypertextLiteral+Extensions.swift create mode 100644 Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift create mode 100644 Sources/swift-doc/Supporting Types/Components/Declaration.swift create mode 100644 Sources/swift-doc/Supporting Types/Helpers.swift create mode 100644 Sources/swift-doc/Supporting Types/Layout.swift diff --git a/Package.resolved b/Package.resolved index b3793851..8987c4c4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,13 +15,31 @@ "repositoryURL": "https://github.com/SwiftDocOrg/GraphViz.git", "state": { "branch": null, - "revision": "6371e3aa683a312b5ecd58520228269bd5e691b1", - "version": "0.0.1" + "revision": "ce0327a62c4b5a5ddd4741406ea54f164f228a54", + "version": null + } + }, + { + "package": "HypertextLiteral", + "repositoryURL": "https://github.com/NSHipster/HypertextLiteral.git", + "state": { + "branch": null, + "revision": "3e45da849e507d171c7264146176bb834a01be4f", + "version": "0.0.2" + } + }, + { + "package": "Markup", + "repositoryURL": "https://github.com/SwiftDocOrg/Markup.git", + "state": { + "branch": null, + "revision": "bcc9bff98749f8ed92221375591a1afd61b02f1a", + "version": null } }, { "package": "swift-argument-parser", - "repositoryURL": "https://github.com/apple/swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser.git", "state": { "branch": null, "revision": "35b76bf577d3cc74820f8991894ce3bcdf024ddc", @@ -37,6 +55,15 @@ "version": "0.28.3+20200110.2a76603" } }, + { + "package": "HTMLEntities", + "repositoryURL": "https://github.com/IBM-Swift/swift-html-entities.git", + "state": { + "branch": null, + "revision": "744c094976355aa96ca61b9b60ef0a38e979feb7", + "version": "3.0.14" + } + }, { "package": "SwiftSyntax", "repositoryURL": "https://github.com/apple/swift-syntax.git", @@ -51,8 +78,8 @@ "repositoryURL": "https://github.com/SwiftDocOrg/SwiftMarkup.git", "state": { "branch": null, - "revision": "8e82d625b0342fc80525956c22f9f0defa0cffce", - "version": "0.0.4" + "revision": "431f418ae1833a312646e934a2891e778c1b03b0", + "version": "0.0.5" } }, { @@ -63,6 +90,15 @@ "revision": "3bb09e896a813cf0a72ec88a19dc0a7db49577cd", "version": null } + }, + { + "package": "SwiftSyntaxHighlighter", + "repositoryURL": "https://github.com/NSHipster/SwiftSyntaxHighlighter.git", + "state": { + "branch": null, + "revision": "fe39b4ec07e1e37872adf4b506d223ab27cf8cea", + "version": null + } } ] }, diff --git a/Package.swift b/Package.swift index c618bb1a..41e5e2fb 100644 --- a/Package.swift +++ b/Package.swift @@ -9,16 +9,19 @@ let package = Package( .package(url: "https://github.com/apple/swift-syntax.git", .revision("swift-5.2-DEVELOPMENT-SNAPSHOT-2020-03-09-a")), .package(url: "https://github.com/SwiftDocOrg/SwiftSemantics.git", .branch("swift-5.2")), .package(url: "https://github.com/SwiftDocOrg/CommonMark.git", .branch("master")), - .package(url: "https://github.com/SwiftDocOrg/SwiftMarkup.git", .upToNextMinor(from: "0.0.4")), - .package(url: "https://github.com/SwiftDocOrg/GraphViz.git", .upToNextMinor(from: "0.0.1")), - .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "0.0.2")), + .package(url: "https://github.com/SwiftDocOrg/SwiftMarkup.git", .upToNextMinor(from: "0.0.5")), + .package(url: "https://github.com/SwiftDocOrg/GraphViz.git", .revision("ce0327a62c4b5a5ddd4741406ea54f164f228a54")), + .package(url: "https://github.com/NSHipster/HypertextLiteral.git", .upToNextMinor(from: "0.0.2")), + .package(url: "https://github.com/SwiftDocOrg/Markup.git", .revision("bcc9bff98749f8ed92221375591a1afd61b02f1a")), + .package(url: "https://github.com/NSHipster/SwiftSyntaxHighlighter.git", .revision("fe39b4ec07e1e37872adf4b506d223ab27cf8cea")), + .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.0.2")), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "swift-doc", - dependencies: ["ArgumentParser", "SwiftDoc", "SwiftSemantics", "SwiftMarkup", "CommonMarkBuilder", "DCOV", "GraphViz"] + dependencies: ["ArgumentParser", "SwiftDoc", "SwiftSemantics", "SwiftMarkup", "CommonMarkBuilder", "HypertextLiteral", "Markup", "DCOV", "GraphViz", "SwiftSyntaxHighlighter"] ), .target( name: "DCOV", diff --git a/Sources/SwiftDoc/Extensions/Array+Parallel.swift b/Sources/SwiftDoc/Extensions/Array+Parallel.swift index 7a3d6eb0..1e98a7bc 100644 --- a/Sources/SwiftDoc/Extensions/Array+Parallel.swift +++ b/Sources/SwiftDoc/Extensions/Array+Parallel.swift @@ -1,17 +1,21 @@ import Dispatch -extension Array { +public extension RandomAccessCollection { func parallelMap(_ transform: (Element) throws -> T) throws -> [T] { guard count > 1 else { return try map(transform) } - var results = [(index: Int, result: Result)]() + let indices = Array(self.indices) + + var results = [(index: Index, result: Result)]() results.reserveCapacity(count) - let queue = DispatchQueue(label: "org.swiftdoc.swift-doc.parallelMap") + let queue = DispatchQueue(label: #function) withoutActuallyEscaping(transform) { escapingtransform in - DispatchQueue.concurrentPerform(iterations: count) { (index) in + DispatchQueue.concurrentPerform(iterations: count) { (iteration) in + let index = indices[iteration] + do { let transformed = try escapingtransform(self[index]) queue.sync { @@ -36,4 +40,8 @@ extension Array { func parallelFlatMap(transform: (Element) throws -> [T]) throws -> [T] { return try parallelMap(transform).flatMap { $0 } } + + func parallelForEach(_ body: (Element) throws -> Void) throws { + _ = try parallelMap(body) + } } diff --git a/Sources/SwiftDoc/Interface.swift b/Sources/SwiftDoc/Interface.swift index 1157b474..57a714d4 100644 --- a/Sources/SwiftDoc/Interface.swift +++ b/Sources/SwiftDoc/Interface.swift @@ -13,16 +13,20 @@ public final class Interface: Codable { // MARK: - - private lazy var symbolsByIdentifier: [Symbol.ID: [Symbol]] = { + public lazy var symbolsGroupedByIdentifier: [Symbol.ID: [Symbol]] = { return Dictionary(grouping: symbols, by: { $0.id }) }() + public lazy var symbolsGroupedByName: [String: [Symbol]] = { + return Dictionary(grouping: symbols, by: { $0.name }) + }() + public private(set) lazy var topLevelSymbols: [Symbol] = { - return symbols.filter { $0.declaration is Type || $0.id.pathComponents.isEmpty } + return symbols.filter { $0.api is Type || $0.id.pathComponents.isEmpty } }() public private(set) lazy var baseClasses: [Symbol] = { - return symbols.filter { $0.declaration is Class && + return symbols.filter { $0.api is Class && typesInherited(by: $0).isEmpty } }() @@ -42,6 +46,12 @@ public final class Interface: Codable { return classClusters }() + private lazy var extensionsByExtendedType: [String: [Extension]] = { + return Dictionary(grouping: symbols.flatMap { $0.context.compactMap { $0 as? Extension } }) { + $0.extendedType + } + }() + public private(set) lazy var relationships: [Relationship] = { var relationships: Set = [] for symbol in symbols { @@ -50,9 +60,9 @@ public final class Interface: Codable { if let container = symbol.context.compactMap({ $0 as? Symbol }).last { let predicate: Relationship.Predicate - switch container.declaration { + switch container.api { case is Protocol: - if symbol.declaration.modifiers.contains(where: { $0.name == "optional" }) { + if symbol.api.modifiers.contains(where: { $0.name == "optional" }) { predicate = .optionalRequirementOf } else { predicate = .requirementOf @@ -65,9 +75,10 @@ public final class Interface: Codable { } if let `extension` = `extension` { - for extended in symbols.filter({ $0.declaration is Type && $0.id.matches(`extension`.extendedType) }) { + if let extended = symbols.first(where: { $0.api is Type && $0.id.matches(`extension`.extendedType) }) { + let predicate: Relationship.Predicate - switch extended.declaration { + switch extended.api { case is Protocol: predicate = .defaultImplementationOf default: @@ -78,17 +89,26 @@ public final class Interface: Codable { } } - if let type = symbol.declaration as? Type { - let inheritance = Set((type.inheritance + (`extension`?.inheritance ?? [])).flatMap { $0.split(separator: "&").map { $0.trimmingCharacters(in: .whitespaces) } }) - for name in inheritance { - let inheritedTypes = symbols.filter({ ($0.declaration is Class || $0.declaration is Protocol) && $0.id.matches(name) }) + if let type = symbol.api as? Type { + var inheritedTypeNames: Set = [] + inheritedTypeNames.formUnion(type.inheritance.flatMap { $0.split(separator: "&").map { $0.trimmingCharacters(in: .whitespaces) } + }) + + for `extension` in extensionsByExtendedType[symbol.id.description] ?? [] { + inheritedTypeNames.formUnion(`extension`.inheritance) + } + + inheritedTypeNames = Set(inheritedTypeNames.flatMap { $0.split(separator: "&").map { $0.trimmingCharacters(in: .whitespaces) } }) + + for name in inheritedTypeNames { + let inheritedTypes = symbols.filter({ ($0.api is Class || $0.api is Protocol) && $0.id.matches(name) }) if inheritedTypes.isEmpty { - let inherited = Symbol(declaration: Unknown(name: name), context: [], documentation: nil, sourceLocation: nil) + let inherited = Symbol(api: Unknown(name: name), context: [], declaration: nil, documentation: nil, sourceLocation: nil) relationships.insert(Relationship(subject: symbol, predicate: .inheritsFrom, object: inherited)) } else { for inherited in inheritedTypes { let predicate: Relationship.Predicate - if symbol.declaration is Class, inherited.declaration is Class { + if symbol.api is Class, inherited.api is Class { predicate = .inheritsFrom } else { predicate = .conformsTo @@ -104,26 +124,26 @@ public final class Interface: Codable { return Array(relationships) }() - private lazy var relationshipsBySubject: [Symbol.ID: [Relationship]] = { + public private(set) lazy var relationshipsBySubject: [Symbol.ID: [Relationship]] = { Dictionary(grouping: relationships, by: { $0.subject.id }) }() - private lazy var relationshipsByObject: [Symbol.ID: [Relationship]] = { + public private(set) lazy var relationshipsByObject: [Symbol.ID: [Relationship]] = { Dictionary(grouping: relationships, by: { $0.object.id }) }() // MARK: - public func members(of symbol: Symbol) -> [Symbol] { - return relationshipsByObject[symbol.id]?.filter { $0.predicate == .memberOf }.map { $0.subject } ?? [] + return relationshipsByObject[symbol.id]?.filter { $0.predicate == .memberOf }.map { $0.subject }.sorted() ?? [] } public func requirements(of symbol: Symbol) -> [Symbol] { - return relationshipsByObject[symbol.id]?.filter { $0.predicate == .requirementOf }.map { $0.subject } ?? [] + return relationshipsByObject[symbol.id]?.filter { $0.predicate == .requirementOf }.map { $0.subject }.sorted() ?? [] } public func optionalRequirements(of symbol: Symbol) -> [Symbol] { - return relationshipsByObject[symbol.id]?.filter { $0.predicate == .optionalRequirementOf }.map { $0.subject } ?? [] + return relationshipsByObject[symbol.id]?.filter { $0.predicate == .optionalRequirementOf }.map { $0.subject }.sorted() ?? [] } public func typesInherited(by symbol: Symbol) -> [Symbol] { @@ -143,6 +163,6 @@ public final class Interface: Codable { } public func conditionalCounterparts(of symbol: Symbol) -> [Symbol] { - return symbolsByIdentifier[symbol.id]?.filter { $0 != symbol } ?? [] + return symbolsGroupedByIdentifier[symbol.id]?.filter { $0 != symbol }.sorted() ?? [] } } diff --git a/Sources/SwiftDoc/Module.swift b/Sources/SwiftDoc/Module.swift index 511a7cc7..3791a297 100644 --- a/Sources/SwiftDoc/Module.swift +++ b/Sources/SwiftDoc/Module.swift @@ -8,7 +8,9 @@ public final class Module: Codable { public let sourceFiles: [SourceFile] public lazy var interface: Interface = { - Interface(imports: sourceFiles.flatMap { $0.imports }, symbols: sourceFiles.flatMap { $0.symbols }) + let imports = sourceFiles.flatMap { $0.imports } + let symbols = sourceFiles.flatMap { $0.symbols } + return Interface(imports: imports, symbols: symbols) }() public required init(name: String = "Anonymous", sourceFiles: [SourceFile]) { diff --git a/Sources/SwiftDoc/SourceFile.swift b/Sources/SwiftDoc/SourceFile.swift index 63cff6bc..f5541d1b 100644 --- a/Sources/SwiftDoc/SourceFile.swift +++ b/Sources/SwiftDoc/SourceFile.swift @@ -35,9 +35,12 @@ public struct SourceFile: Hashable, Codable { var visitedSymbols: [Symbol] = [] var visitedImports: [Import] = [] + let fileURL: URL let sourceLocationConverter: SourceLocationConverter init(file url: URL, relativeTo directory: URL) throws { + self.fileURL = url + let tree = try SyntaxParser.parse(url) sourceLocationConverter = SourceLocationConverter(file: url.path(relativeTo: directory), tree: tree) super.init() @@ -48,21 +51,22 @@ public struct SourceFile: Hashable, Codable { } func symbol(_ type: Declaration.Type, _ node: Node) -> Symbol? where Declaration: API & ExpressibleBySyntax, Node == Declaration.Syntax { - guard let declaration = Declaration(node) else { return nil } - return symbol(node, declaration: declaration) + guard let api = Declaration(node) else { return nil } + return symbol(node, api: api) } - func symbol(_ node: Node, declaration: API) -> Symbol? { + func symbol(_ node: Node, api: API) -> Symbol? { guard let documentation = try? Documentation.parse(node.documentation) else { return nil } let sourceLocation = sourceLocationConverter.location(for: node.position) - return Symbol(declaration: declaration, context: context, documentation: documentation, sourceLocation: sourceLocation) + + return Symbol(api: api, context: context, declaration: "\(api)", documentation: documentation, sourceLocation: sourceLocation) } func push(_ symbol: Symbol?) { guard let symbol = symbol else { return } visitedSymbols.append(symbol) - switch symbol.declaration { + switch symbol.api { case is Class, is Enumeration, is Protocol, @@ -108,7 +112,7 @@ public struct SourceFile: Hashable, Codable { override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { for `case` in Enumeration.Case.cases(from: node) { - push(symbol(node, declaration: `case`)) + push(symbol(node, api: `case`)) } return .skipChildren } @@ -175,7 +179,7 @@ public struct SourceFile: Hashable, Codable { override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { for variable in Variable.variables(from: node) { - push(symbol(node, declaration: variable)) + push(symbol(node, api: variable)) } return .skipChildren } @@ -183,11 +187,11 @@ public struct SourceFile: Hashable, Codable { // MARK: - override func visitPost(_ node: ClassDeclSyntax) { - assert((pop() as? Symbol)?.declaration is Class) + assert((pop() as? Symbol)?.api is Class) } override func visitPost(_ node: EnumDeclSyntax) { - assert((pop() as? Symbol)?.declaration is Enumeration) + assert((pop() as? Symbol)?.api is Enumeration) } override func visitPost(_ node: ExtensionDeclSyntax) { @@ -199,11 +203,11 @@ public struct SourceFile: Hashable, Codable { } override func visitPost(_ node: ProtocolDeclSyntax) { - assert((pop() as? Symbol)?.declaration is Protocol) + assert((pop() as? Symbol)?.api is Protocol) } override func visitPost(_ node: StructDeclSyntax) { - assert((pop() as? Symbol)?.declaration is Structure) + assert((pop() as? Symbol)?.api is Structure) } } } diff --git a/Sources/SwiftDoc/Symbol.swift b/Sources/SwiftDoc/Symbol.swift index e7683707..5272c1e8 100644 --- a/Sources/SwiftDoc/Symbol.swift +++ b/Sources/SwiftDoc/Symbol.swift @@ -5,23 +5,25 @@ import SwiftSemantics public final class Symbol { public typealias ID = Identifier - public let declaration: API + public let api: API public let context: [Contextual] + public let declaration: String public let documentation: Documentation? public let sourceLocation: SourceLocation? public private(set) lazy var `extension`: Extension? = context.compactMap { $0 as? Extension }.first public private(set) lazy var conditions: [CompilationCondition] = context.compactMap { $0 as? CompilationCondition } - init(declaration: API, context: [Contextual], documentation: Documentation?, sourceLocation: SourceLocation?) { - self.declaration = declaration + init(api: API, context: [Contextual], declaration: String?, documentation: Documentation?, sourceLocation: SourceLocation?) { + self.api = api self.context = context + self.declaration = declaration ?? "\(api)" self.documentation = documentation self.sourceLocation = sourceLocation } public var name: String { - return declaration.name + return api.name } public private(set) lazy var id: ID = { @@ -31,7 +33,7 @@ public final class Symbol { }() public var isPublic: Bool { - if declaration.modifiers.contains(where: { $0.name == "public" || $0.name == "open" }) { + if api.modifiers.contains(where: { $0.name == "public" || $0.name == "open" }) { return true } @@ -42,13 +44,13 @@ public final class Symbol { } if let symbol = context.compactMap({ $0 as? Symbol }).first, - symbol.declaration.modifiers.contains(where: { $0.name == "public" }) + symbol.api.modifiers.contains(where: { $0.name == "public" }) { - switch symbol.declaration { + switch symbol.api { case is Enumeration: - return declaration is Enumeration.Case + return api is Enumeration.Case case is Protocol: - return declaration is Function || declaration is Variable + return api is Function || api is Variable default: break } @@ -84,7 +86,7 @@ extension Symbol: Equatable { } } - switch (lhs.declaration, rhs.declaration) { + switch (lhs.api, rhs.api) { case let (ls, rs) as (AssociatedType, AssociatedType): return ls == rs case let (ls, rs) as (Class, Class): @@ -123,7 +125,11 @@ extension Symbol: Equatable { extension Symbol: Comparable { public static func < (lhs: Symbol, rhs: Symbol) -> Bool { - return lhs.name < rhs.name + if let lsl = lhs.sourceLocation, let rsl = rhs.sourceLocation { + return lsl < rsl + } else { + return lhs.name < rhs.name + } } } @@ -133,7 +139,7 @@ extension Symbol: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(documentation) hasher.combine(sourceLocation) - switch declaration { + switch api { case let api as AssociatedType: hasher.combine(api) case let api as Class: @@ -172,6 +178,7 @@ extension Symbol: Hashable { extension Symbol: Codable { private enum CodingKeys: String, CodingKey { + case declaration case documentation case sourceLocation @@ -194,79 +201,80 @@ extension Symbol: Codable { public convenience init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let declaration: API + let api: API if container.contains(.associatedType) { - declaration = try container.decode(AssociatedType.self, forKey: .associatedType) + api = try container.decode(AssociatedType.self, forKey: .associatedType) } else if container.contains(.`case`) { - declaration = try container.decode(Enumeration.Case.self, forKey: .case) + api = try container.decode(Enumeration.Case.self, forKey: .case) } else if container.contains(.`class`) { - declaration = try container.decode(Class.self, forKey: .class) + api = try container.decode(Class.self, forKey: .class) } else if container.contains(.enumeration) { - declaration = try container.decode(Enumeration.self, forKey: .enumeration) + api = try container.decode(Enumeration.self, forKey: .enumeration) } else if container.contains(.function) { - declaration = try container.decode(Function.self, forKey: .function) + api = try container.decode(Function.self, forKey: .function) } else if container.contains(.initializer) { - declaration = try container.decode(Initializer.self, forKey: .initializer) + api = try container.decode(Initializer.self, forKey: .initializer) } else if container.contains(.`operator`) { - declaration = try container.decode(Operator.self, forKey: .operator) + api = try container.decode(Operator.self, forKey: .operator) } else if container.contains(.precedenceGroup) { - declaration = try container.decode(PrecedenceGroup.self, forKey: .precedenceGroup) + api = try container.decode(PrecedenceGroup.self, forKey: .precedenceGroup) } else if container.contains(.`protocol`) { - declaration = try container.decode(Protocol.self, forKey: .protocol) + api = try container.decode(Protocol.self, forKey: .protocol) } else if container.contains(.structure) { - declaration = try container.decode(Structure.self, forKey: .structure) + api = try container.decode(Structure.self, forKey: .structure) } else if container.contains(.`subscript`) { - declaration = try container.decode(Subscript.self, forKey: .subscript) + api = try container.decode(Subscript.self, forKey: .subscript) } else if container.contains(.`typealias`) { - declaration = try container.decode(Typealias.self, forKey: .typealias) + api = try container.decode(Typealias.self, forKey: .typealias) } else if container.contains(.variable) { - declaration = try container.decode(Variable.self, forKey: .variable) + api = try container.decode(Variable.self, forKey: .variable) } else if container.contains(.unknown) { - declaration = try container.decode(Unknown.self, forKey: .variable) + api = try container.decode(Unknown.self, forKey: .variable) } else { let context = DecodingError.Context(codingPath: container.codingPath, debugDescription: "missing declaration") throw DecodingError.dataCorrupted(context) } - let documentation = try container.decode(Documentation.self, forKey: .documentation) - let sourceLocation = try container.decode(SourceLocation.self, forKey: .sourceLocation) + let declaration = try container.decodeIfPresent(String.self, forKey: .declaration) + let documentation = try container.decodeIfPresent(Documentation.self, forKey: .documentation) + let sourceLocation = try container.decodeIfPresent(SourceLocation.self, forKey: .sourceLocation) - self.init(declaration: declaration, context: [] /* TODO */, documentation: documentation, sourceLocation: sourceLocation) + self.init(api: api, context: [] /* TODO */, declaration: declaration, documentation: documentation, sourceLocation: sourceLocation) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - if let declaration = declaration as? AssociatedType { + if let declaration = api as? AssociatedType { try container.encode(declaration, forKey: .associatedType) - } else if let declaration = declaration as? Class { + } else if let declaration = api as? Class { try container.encode(declaration, forKey: .class) - } else if let declaration = declaration as? Enumeration { + } else if let declaration = api as? Enumeration { try container.encode(declaration, forKey: .enumeration) - } else if let declaration = declaration as? Enumeration.Case { + } else if let declaration = api as? Enumeration.Case { try container.encode(declaration, forKey: .case) - } else if let declaration = declaration as? Function { + } else if let declaration = api as? Function { try container.encode(declaration, forKey: .function) - } else if let declaration = declaration as? Initializer { + } else if let declaration = api as? Initializer { try container.encode(declaration, forKey: .initializer) - } else if let declaration = declaration as? Operator { + } else if let declaration = api as? Operator { try container.encode(declaration, forKey: .operator) - } else if let declaration = declaration as? PrecedenceGroup { + } else if let declaration = api as? PrecedenceGroup { try container.encode(declaration, forKey: .precedenceGroup) - } else if let declaration = declaration as? Protocol { + } else if let declaration = api as? Protocol { try container.encode(declaration, forKey: .protocol) - } else if let declaration = declaration as? Structure { + } else if let declaration = api as? Structure { try container.encode(declaration, forKey: .structure) - } else if let declaration = declaration as? Subscript { + } else if let declaration = api as? Subscript { try container.encode(declaration, forKey: .subscript) - } else if let declaration = declaration as? Typealias { + } else if let declaration = api as? Typealias { try container.encode(declaration, forKey: .typealias) - } else if let declaration = declaration as? Variable { + } else if let declaration = api as? Variable { try container.encode(declaration, forKey: .variable) - } else if let declaration = declaration as? Unknown { + } else if let declaration = api as? Unknown { try container.encode(declaration, forKey: .unknown) } else { let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "unhandled declaration type") - throw EncodingError.invalidValue(declaration, context) + throw EncodingError.invalidValue(api, context) } try container.encode(documentation, forKey: .documentation) diff --git a/Sources/swift-doc/Extensions/CommonMark+Extensions.swift b/Sources/swift-doc/Extensions/CommonMark+Extensions.swift new file mode 100644 index 00000000..ccc7d332 --- /dev/null +++ b/Sources/swift-doc/Extensions/CommonMark+Extensions.swift @@ -0,0 +1,8 @@ +import CommonMark +import HypertextLiteral + +extension CommonMark.Document: HypertextLiteralConvertible { + public var html: HypertextLiteral.HTML { + HypertextLiteral.HTML(render(format: .html, options: .unsafe)) + } +} diff --git a/Sources/swift-doc/Extensions/DCOV+Extensions.swift b/Sources/swift-doc/Extensions/DCOV+Extensions.swift index 78704a7d..fbd423cd 100644 --- a/Sources/swift-doc/Extensions/DCOV+Extensions.swift +++ b/Sources/swift-doc/Extensions/DCOV+Extensions.swift @@ -5,7 +5,7 @@ import SwiftSemantics extension Entry { public init(_ symbol: Symbol) { let name = symbol.id.description - let type = String(describing: Swift.type(of: symbol.declaration)) + let type = String(describing: Swift.type(of: symbol.api)) let documented = symbol.isDocumented let file = symbol.sourceLocation?.file let line = symbol.sourceLocation?.line diff --git a/Sources/swift-doc/Extensions/HypertextLiteral+Extensions.swift b/Sources/swift-doc/Extensions/HypertextLiteral+Extensions.swift new file mode 100644 index 00000000..367e2258 --- /dev/null +++ b/Sources/swift-doc/Extensions/HypertextLiteral+Extensions.swift @@ -0,0 +1,9 @@ +import HypertextLiteral +import CommonMark + +extension HypertextLiteral.HTML.StringInterpolation { + mutating func appendInterpolation(commonmark: String) { + guard let document = try? Document(commonmark) else { return } + appendLiteral(document.render(format: .html, options: [.unsafe])) + } +} diff --git a/Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift b/Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift new file mode 100644 index 00000000..df5cc207 --- /dev/null +++ b/Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift @@ -0,0 +1,76 @@ +import SwiftDoc +import SwiftSemantics +import GraphViz +import DOT +import struct Foundation.URL + +extension Symbol { + var node: Node { + var node = Node(id.description) + node.fontName = "Menlo" + + if !(api is Unknown) { + node.href = "/" + path(for: self) + } + + switch api { + case let `class` as Class: + node.shape = .ellipse + if `class`.modifiers.contains(where: { $0.name == "final" }) { + node.strokeWidth = 2.0 + } + case is Structure: + node.shape = .box + node.style = .rounded + case is Protocol: + node.shape = .ellipse + node.style = .dashed + default: + break + } + + return node + } + + func graph(in module: Module) -> Graph { + var graph = Graph(directed: true) + + let relationships = module.interface.relationships.filter { + ($0.predicate == .inheritsFrom || $0.predicate == .conformsTo) && + ($0.subject == self || $0.object == self) + } + + var symbolNode = self.node + symbolNode.strokeWidth = 3.0 + + graph.append(symbolNode) + + for node in Set(relationships.flatMap { [$0.subject.node, $0.object.node] }) where node.id != symbolNode.id { + graph.append(node) + } + + for relationship in relationships { + let edge = relationship.edge + graph.append(edge) + } + + return graph + } +} + +extension Relationship { + var edge: Edge { + let from = subject.node + let to = object.node + + var edge = Edge(from: from.id, to: to.id) + switch predicate { + case .conformsTo: + edge.style = .dashed + default: + break + } + + return edge + } +} diff --git a/Sources/swift-doc/Subcommands/Diagram.swift b/Sources/swift-doc/Subcommands/Diagram.swift index 4b0f6edf..50c72443 100644 --- a/Sources/swift-doc/Subcommands/Diagram.swift +++ b/Sources/swift-doc/Subcommands/Diagram.swift @@ -5,6 +5,7 @@ import SwiftSemantics import GraphViz import DOT + extension SwiftDoc { struct Diagram: ParsableCommand { struct Options: ParsableArguments { @@ -36,7 +37,7 @@ fileprivate func diagram(of module: Module) -> String { var subclassNode = Node("\(subclass.id)") subclassNode.shape = .box - if subclass.declaration.modifiers.contains(where: { $0.name == "final" }) { + if subclass.api.modifiers.contains(where: { $0.name == "final" }) { subclassNode.strokeWidth = 2.0 } @@ -60,7 +61,7 @@ fileprivate func diagram(of module: Module) -> String { } - for symbol in (module.interface.symbols.filter { $0.isPublic && $0.declaration is Type }) { + for symbol in (module.interface.symbols.filter { $0.isPublic && $0.api is Type }) { let symbolNode = Node("\(symbol.id)") graph.append(symbolNode) diff --git a/Sources/swift-doc/Subcommands/Generate.swift b/Sources/swift-doc/Subcommands/Generate.swift index 89269f8c..1fbd1f47 100644 --- a/Sources/swift-doc/Subcommands/Generate.swift +++ b/Sources/swift-doc/Subcommands/Generate.swift @@ -7,14 +7,28 @@ import SwiftDoc extension SwiftDoc { struct Generate: ParsableCommand { + enum Format: String, ExpressibleByArgument { + case commonmark + case html + } + struct Options: ParsableArguments { @Argument(help: "One or more paths to Swift files") var inputs: [String] + @Option(name: [.long, .customShort("n")], + help: "The name of the module") + var moduleName: String + @Option(name: .shortAndLong, default: ".build/documentation", help: "The path for generated output") var output: String + + @Option(name: .shortAndLong, + default: .commonmark, + help: "The output format") + var format: Format } static var configuration = CommandConfiguration(abstract: "Generates Swift documentation") @@ -23,29 +37,32 @@ extension SwiftDoc { var options: Options func run() throws { - let module = try Module(paths: options.inputs) + let module = try Module(name: options.moduleName, paths: options.inputs) let outputDirectoryURL = URL(fileURLWithPath: options.output) try fileManager.createDirectory(at: outputDirectoryURL, withIntermediateDirectories: true, attributes: fileAttributes) do { - try HomePage(module: module).write(to: outputDirectoryURL.appendingPathComponent("Home.md")) - try SidebarPage(module: module).write(to: outputDirectoryURL.appendingPathComponent("_Sidebar.md")) - try FooterPage().write(to: outputDirectoryURL.appendingPathComponent("_Footer.md")) + let format = options.format + + var pages: [String: Page] = [:] + + switch format { + case .commonmark: + pages["Home"] = HomePage(module: module) + pages["_Sidebar"] = SidebarPage(module: module) + pages["_Footer"] = FooterPage() + case .html: + pages["Home"] = HomePage(module: module) + } var globals: [String: [Symbol]] = [:] for symbol in module.interface.topLevelSymbols.filter({ $0.isPublic }) { - switch symbol.declaration { - case is Class: - try TypePage(module: module, symbol: symbol).write(to: outputDirectoryURL.appendingPathComponent("\(path(for: symbol.id.description)).md")) - case is Enumeration: - try TypePage(module: module, symbol: symbol).write(to: outputDirectoryURL.appendingPathComponent("\(path(for: symbol.id.description)).md")) - case is Structure: - try TypePage(module: module, symbol: symbol).write(to: outputDirectoryURL.appendingPathComponent("\(path(for: symbol.id.description)).md")) - case let `protocol` as Protocol: - try TypePage(module: module, symbol: symbol).write(to: outputDirectoryURL.appendingPathComponent("\(path(for: `protocol`.name)).md")) + switch symbol.api { + case is Class, is Enumeration, is Structure, is Protocol: + pages[path(for: symbol)] = TypePage(module: module, symbol: symbol) case let `typealias` as Typealias: - try TypealiasPage(module: module, symbol: symbol).write(to: outputDirectoryURL.appendingPathComponent("\(path(for: `typealias`.name)).md")) + pages[path(for: `typealias`.name)] = TypealiasPage(module: module, symbol: symbol) case let function as Function where !function.isOperator: globals[function.name, default: []] += [symbol] case let variable as Variable: @@ -56,7 +73,22 @@ extension SwiftDoc { } for (name, symbols) in globals { - try GlobalPage(module: module, name: name, symbols: symbols).write(to: outputDirectoryURL.appendingPathComponent("\(path(for: name)).md")) + pages[path(for: name)] = GlobalPage(module: module, name: name, symbols: symbols) + } + + try pages.map { $0 }.parallelForEach { + let filename: String + switch format { + case .commonmark: + filename = "\($0.key).md" + case .html where $0.key == "Home": + filename = "index.html" + case .html: + filename = "\($0.key)/index.html" + } + + let url = outputDirectoryURL.appendingPathComponent(filename) + try $0.value.write(to: url, format: format) } } } diff --git a/Sources/swift-doc/Supporting Types/Component.swift b/Sources/swift-doc/Supporting Types/Component.swift index 8bf7fa22..d940b7db 100644 --- a/Sources/swift-doc/Supporting Types/Component.swift +++ b/Sources/swift-doc/Supporting Types/Component.swift @@ -1,11 +1,13 @@ import CommonMarkBuilder +import HypertextLiteral -public protocol Component: BlockConvertible { - var body: Fragment { get } +public protocol Component: BlockConvertible, HypertextLiteralConvertible { + var fragment: Fragment { get } + var html: HypertextLiteral.HTML { get } } extension Component { public var blockValue: [Block & Node] { - return body.blockValue + return fragment.blockValue } } diff --git a/Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift b/Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift index f1a34c73..aeae24e5 100644 --- a/Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift +++ b/Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift @@ -2,21 +2,22 @@ import CommonMarkBuilder import SwiftDoc import SwiftMarkup import SwiftSemantics +import HypertextLiteral struct ConformingTypes: Component { var symbol: Symbol var module: Module init(to symbol: Symbol, in module: Module) { - precondition(symbol.declaration is Protocol) + precondition(symbol.api is Protocol) self.symbol = symbol self.module = module } // MARK: - Component - var body: Fragment { - guard symbol.declaration is Protocol else { return Fragment { "" } } + var fragment: Fragment { + guard symbol.api is Protocol else { return Fragment { "" } } let conformingTypes = module.interface.typesConforming(to: symbol) guard !conformingTypes.isEmpty else { return Fragment { "" }} @@ -27,10 +28,10 @@ struct ConformingTypes: Component { Fragment { #""" \#(conformingTypes.map { type in - if type.declaration is Unknown { + if type.api is Unknown { return "`\(type.id)`" } else { - return "[`\(type.id)`](\(path(for: type.id)))" + return "[`\(type.id)`](\(path(for: type)))" } }.joined(separator: ", ")) """# @@ -38,4 +39,10 @@ struct ConformingTypes: Component { } } } + + var html: HypertextLiteral.HTML { + return #""" + + """# + } } diff --git a/Sources/swift-doc/Supporting Types/Components/Declaration.swift b/Sources/swift-doc/Supporting Types/Components/Declaration.swift new file mode 100644 index 00000000..d6fe455e --- /dev/null +++ b/Sources/swift-doc/Supporting Types/Components/Declaration.swift @@ -0,0 +1,33 @@ +import CommonMarkBuilder +import SwiftDoc +import SwiftMarkup +import SwiftSemantics +import HypertextLiteral +import SwiftSyntaxHighlighter +import Xcode + +struct Declaration: Component { + var symbol: Symbol + var module: Module + + init(of symbol: Symbol, in module: Module) { + self.symbol = symbol + self.module = module + } + + // MARK: - Component + + var fragment: Fragment { + Fragment { + CodeBlock("swift") { + symbol.declaration.trimmingCharacters(in: .whitespacesAndNewlines) + } + } + } + + var html: HypertextLiteral.HTML { + var html = try! highlight(symbol.declaration, using: Xcode.self) + html = linkCodeElements(of: html, for: symbol, in: module) + return HTML(html) + } +} diff --git a/Sources/swift-doc/Supporting Types/Components/Documentation.swift b/Sources/swift-doc/Supporting Types/Components/Documentation.swift index 27156682..6a8fcf82 100644 --- a/Sources/swift-doc/Supporting Types/Components/Documentation.swift +++ b/Sources/swift-doc/Supporting Types/Components/Documentation.swift @@ -1,17 +1,23 @@ +import Foundation import SwiftDoc import SwiftMarkup import CommonMarkBuilder +import HypertextLiteral +import SwiftSyntaxHighlighter +import Xcode struct Documentation: Component { var symbol: Symbol + var module: Module - init(for symbol: Symbol) { + init(for symbol: Symbol, in module: Module) { self.symbol = symbol + self.module = module } // MARK: - Component - var body: Fragment { + var fragment: Fragment { guard let documentation = symbol.documentation else { return Fragment { "" } } return Fragment { @@ -30,9 +36,7 @@ struct Documentation: Component { Fragment { "\(documentation.summary!)" } } - CodeBlock("swift") { - "\(symbol.declaration)".trimmingCharacters(in: .whitespacesAndNewlines) - } + Declaration(of: symbol, in: module) ForEach(in: documentation.discussionParts) { part in if part is SwiftMarkup.Documentation.Callout { @@ -77,6 +81,86 @@ struct Documentation: Component { } } + var html: HypertextLiteral.HTML { + guard let documentation = symbol.documentation else { return "" } + + var fragments: [HypertextLiteralConvertible] = [] + + fragments.append(Declaration(of: symbol, in: module)) + + if let summary = documentation.summary { + fragments.append(#""" +
+ \#(commonmark: summary) +
+ """# as HypertextLiteral.HTML) + } + + if !documentation.discussionParts.isEmpty { + fragments.append(#""" +
+ \#(documentation.discussionParts.compactMap { part -> HypertextLiteral.HTML? in + if let part = part as? SwiftMarkup.Documentation.Callout { + return Callout(part).html + } else if let part = part as? String { + if part.starts(with: "```"), + let codeBlock = (try? CommonMark.Document(part))?.children.compactMap({ $0 as? CodeBlock }).first, + (codeBlock.fenceInfo ?? "") == "" || + codeBlock.fenceInfo?.compare("swift", options: .caseInsensitive) == .orderedSame, + let source = codeBlock.literal + { + var html = try! highlight(source, using: Xcode.self) + html = linkCodeElements(of: html, for: symbol, in: module) + return HTML(html) + } else { + var html = (try! CommonMark.Document(part)).render(format: .html, options: [.unsafe]) + html = linkCodeElements(of: html, for: symbol, in: module) + return HTML(html) + } + } else { + return nil + } + }) +
+ """# as HypertextLiteral.HTML) + } + + if !documentation.parameters.isEmpty { + fragments.append(#""" +

Parameters

+ +
+ \#(documentation.parameters.map { parameter in + #""" +
\#(parameter.name)
+
\#(commonmark: parameter.description)
+ """# as HypertextLiteral.HTML + }) +
+ """# as HypertextLiteral.HTML) + } + + if let `throws` = documentation.throws { + fragments.append(#""" +

Throws

+ \#(commonmark: `throws`) + """# as HypertextLiteral.HTML) + } + + if let `returns` = documentation.returns { + fragments.append(#""" +

Returns

+ \#(commonmark: `returns`) + """# as HypertextLiteral.HTML) + } + + return #""" + \#(fragments.map { $0.html }) + """# + } +} + +extension Documentation { struct Callout: Component { var callout: SwiftMarkup.Documentation.Callout @@ -86,12 +170,20 @@ struct Documentation: Component { // MARK: - Component - var body: Fragment { + var fragment: Fragment { Fragment { """ > \(callout.delimiter.rawValue.capitalized): \(callout.content) """ } } + + var html: HypertextLiteral.HTML { + return #""" + + """# + } } } diff --git a/Sources/swift-doc/Supporting Types/Components/Inheritance.swift b/Sources/swift-doc/Supporting Types/Components/Inheritance.swift index e5a6e624..7bb288a6 100644 --- a/Sources/swift-doc/Supporting Types/Components/Inheritance.swift +++ b/Sources/swift-doc/Supporting Types/Components/Inheritance.swift @@ -3,6 +3,9 @@ import SwiftDoc import SwiftMarkup import SwiftSemantics import Foundation +import HypertextLiteral +import GraphViz +import DOT extension StringBuilder { // MARK: buildIf @@ -25,16 +28,17 @@ extension StringBuilder { struct Inheritance: Component { var module: Module var symbol: Symbol + var inheritedTypes: [Symbol] init(of symbol: Symbol, in module: Module) { self.module = module self.symbol = symbol + self.inheritedTypes = module.interface.typesInherited(by: symbol) + module.interface.typesConformed(by: symbol) } // MARK: - Component - var body: Fragment { - let inheritedTypes = module.interface.typesInherited(by: symbol) + module.interface.typesConformed(by: symbol) + var fragment: Fragment { guard !inheritedTypes.isEmpty else { return Fragment { "" } } return Fragment { @@ -44,10 +48,10 @@ struct Inheritance: Component { Fragment { #""" \#(inheritedTypes.map { type in - if type.declaration is Unknown { + if type.api is Unknown { return "`\(type.id)`" } else { - return "[`\(type.id)`](\(path(for: type.id)))" + return "[`\(type.id)`](\(path(for: type)))" } }.joined(separator: ", ")) """# @@ -55,4 +59,21 @@ struct Inheritance: Component { } } } + + var html: HypertextLiteral.HTML { + let graph = symbol.graph(in: module) + guard !graph.edges.isEmpty else { return "" } + + let svg = try! HTML(String(data: graph.render(using: .dot, to: .svg), encoding: .utf8) ?? "") + + return #""" +
+
+ \#(svg) + + +
+
+ """# + } } diff --git a/Sources/swift-doc/Supporting Types/Components/Members.swift b/Sources/swift-doc/Supporting Types/Components/Members.swift index e3278ac9..acd859c4 100644 --- a/Sources/swift-doc/Supporting Types/Components/Members.swift +++ b/Sources/swift-doc/Supporting Types/Components/Members.swift @@ -2,36 +2,48 @@ import CommonMarkBuilder import SwiftDoc import SwiftMarkup import SwiftSemantics +import HypertextLiteral struct Members: Component { var symbol: Symbol var module: Module + var members: [Symbol] + + var typealiases: [Symbol] + var cases: [Symbol] + var initializers: [Symbol] + var properties: [Symbol] + var methods: [Symbol] + var genericallyConstrainedMembers: [[GenericRequirement] : [Symbol]] + init(of symbol: Symbol, in module: Module) { self.symbol = symbol self.module = module - } - - // MARK: - Component + self.members = module.interface.members(of: symbol).filter { $0.extension?.genericRequirements.isEmpty != false } - var body: Fragment { - let members = module.interface.members(of: symbol).filter { $0.extension?.genericRequirements.isEmpty != false } - guard !members.isEmpty else { return Fragment { "" } } - - let typealiases = members.filter { $0.declaration is Typealias } - let cases = members.filter { $0.declaration is Enumeration.Case } - let initializers = members.filter { $0.declaration is Initializer } - let properties = members.filter { $0.declaration is Variable } - let methods = members.filter { $0.declaration is Function } - let genericallyConstrainedMembers = Dictionary(grouping: members) { $0.`extension`?.genericRequirements ?? [] }.filter { !$0.key.isEmpty } + self.typealiases = members.filter { $0.api is Typealias } + self.cases = members.filter { $0.api is Enumeration.Case } + self.initializers = members.filter { $0.api is Initializer } + self.properties = members.filter { $0.api is Variable } + self.methods = members.filter { $0.api is Function } + self.genericallyConstrainedMembers = Dictionary(grouping: members) { $0.`extension`?.genericRequirements ?? [] }.filter { !$0.key.isEmpty } + } - let sections: [(title: String, members: [Symbol])] = [ - (symbol.declaration is Protocol ? "Associated Types" : "Nested Type Aliases", typealiases), + var sections: [(title: String, members: [Symbol])] { + return [ + (symbol.api is Protocol ? "Associated Types" : "Nested Type Aliases", typealiases), ("Enumeration Cases", cases), ("Initializers", initializers), ("Properties", properties), ("Methods", methods) ].filter { !$0.members.isEmpty } + } + + // MARK: - Component + + var fragment: Fragment { + guard !members.isEmpty else { return Fragment { "" } } return Fragment { ForEach(in: sections) { section -> BlockConvertible in @@ -39,7 +51,7 @@ struct Members: Component { Heading { section.title } ForEach(in: section.members) { member in Heading { member.name } - Documentation(for: member) + Documentation(for: member, in: module) } } } @@ -55,7 +67,7 @@ struct Members: Component { Section { ForEach(in: members) { member in Heading { member.name } - Documentation(for: member) + Documentation(for: member, in: module) } } } @@ -64,4 +76,48 @@ struct Members: Component { } } } + + var html: HypertextLiteral.HTML { + return #""" + \#(sections.map { section -> HypertextLiteral.HTML in + #""" +
+

\#(section.title)

+ \#(section.members.map { member -> HypertextLiteral.HTML in + #""" +
+ + \#(softbreak(member.name)) + + \#(Documentation(for: member, in: module).html) +
+ """# + }) +
+ """# + }) + + \#((genericallyConstrainedMembers.isEmpty ? "" : + #""" +
+

Generically Constrained Members

+ + \#(genericallyConstrainedMembers.map { (requirements, members) -> HypertextLiteral.HTML in + #""" +
+

where \#(requirements.map { $0.description }.joined(separator: ", "))

+ \#(members.map { member -> HypertextLiteral.HTML in + #""" +

\#(softbreak(member.name))

+ \#(Documentation(for: member, in: module).html) + """# + }) +
+ """# + }) +
+ """# + ) as HypertextLiteral.HTML) + """# + } } diff --git a/Sources/swift-doc/Supporting Types/Components/NestedTypes.swift b/Sources/swift-doc/Supporting Types/Components/NestedTypes.swift index a30ea908..572b7e46 100644 --- a/Sources/swift-doc/Supporting Types/Components/NestedTypes.swift +++ b/Sources/swift-doc/Supporting Types/Components/NestedTypes.swift @@ -2,21 +2,24 @@ import CommonMarkBuilder import SwiftDoc import SwiftMarkup import SwiftSemantics +import HypertextLiteral struct NestedTypes: Component { var symbol: Symbol var module: Module + var nestedTypes: [Symbol] + init(of symbol: Symbol, in module: Module) { - precondition(symbol.declaration is Type) + precondition(symbol.api is Type) self.symbol = symbol self.module = module + self.nestedTypes = module.interface.members(of: symbol).filter { $0.api is Type } } // MARK: - Component - var body: Fragment { - let nestedTypes = module.interface.members(of: symbol).filter { $0.declaration is Type } + var fragment: Fragment { guard !nestedTypes.isEmpty else { return Fragment { "" }} return Fragment { @@ -26,7 +29,7 @@ struct NestedTypes: Component { Fragment { #""" \#(nestedTypes.map { type in - if type.declaration is Unknown { + if type.api is Unknown { return "`\(type.id)`" } else { return "[`\(type.id)`](\(path(for: type.id)))" @@ -37,4 +40,10 @@ struct NestedTypes: Component { } } } + + var html: HypertextLiteral.HTML { + return #""" + + """# + } } diff --git a/Sources/swift-doc/Supporting Types/Components/Requirements.swift b/Sources/swift-doc/Supporting Types/Components/Requirements.swift index aec203c5..bf96ce78 100644 --- a/Sources/swift-doc/Supporting Types/Components/Requirements.swift +++ b/Sources/swift-doc/Supporting Types/Components/Requirements.swift @@ -2,6 +2,7 @@ import CommonMarkBuilder import SwiftDoc import SwiftMarkup import SwiftSemantics +import HypertextLiteral struct Requirements: Component { var symbol: Symbol @@ -14,7 +15,7 @@ struct Requirements: Component { // MARK: - Component - var body: Fragment { + var fragment: Fragment { let sections: [(title: String, requirements: [Symbol])] = [ ("Requirements", module.interface.requirements(of: symbol)), ("Optional Requirements", module.interface.optionalRequirements(of: symbol)) @@ -27,10 +28,16 @@ struct Requirements: Component { Heading { section.title } ForEach(in: section.requirements) { requirement in Heading { requirement.name } - Documentation(for: requirement) + Documentation(for: requirement, in: module) } } } } } + + var html: HypertextLiteral.HTML { + return #""" + + """# + } } diff --git a/Sources/swift-doc/Supporting Types/Helpers.swift b/Sources/swift-doc/Supporting Types/Helpers.swift new file mode 100644 index 00000000..a95ff115 --- /dev/null +++ b/Sources/swift-doc/Supporting Types/Helpers.swift @@ -0,0 +1,92 @@ +import Foundation +import SwiftDoc +import HTML + +fileprivate let regex = try! NSRegularExpression(pattern: #"(?:([a-z]{2,})([A-Z]+))"#, options: []) + +public func linkCodeElements(of html: String, for symbol: Symbol, in module: Module) -> String { + let document = try! Document(string: html.description)! + for element in document.search(xpath: "//code | //pre/code//span[contains(@class,'type')]") { + guard let name = element.content else { continue } + +// let nameWithSoftBreaks = regex.stringByReplacingMatches(in: name, options: [], range: NSRange(name.startIndex.. String { + let toc = Element(name: "ol") + + let document = try! Document(string: html.description)! + for h2 in document.search(xpath: "//h2") { + guard let section = h2.parent as? Element else { continue } + + let li = Element(name: "li") + + var className: String? = nil + switch section["id"]?.lowercased() { + case "initializers": + className = "initializer" + case "enumeration cases": + className = "case" + case "methods": + className = "method" + case "properties": + className = "property" + case "nested type aliases": + className = "typealias" + default: + break + } + + let a = Element(name: "a") + a["href"] = "#\(section["id"]!)" + a.content = h2.text + + li.insert(child: a) + + let nestedItems = section.search(xpath: "./details/summary").compactMap { summary -> Element? in + guard let article = summary.parent as? Element else { return nil } + + let li = Element(name: "li") + + if let className = className { + li["class"] = className + } + + let a = Element(name: "a") + a["href"] = "#\(article["id"]!)" + a.content = summary.text + + li.insert(child: a) + return li + } + + if !nestedItems.isEmpty { + let ul = Element(name: "ul") + nestedItems.forEach { ul.insert(child: $0) } + li.insert(child: ul) + } + + + toc.insert(child: li) + } + + return toc.description +} + +public func softbreak(_ string: String) -> String { + return string.replacingOccurrences(of: ".", with: ".\u{200B}") + .replacingOccurrences(of: ":", with: ":\u{200B}") +} diff --git a/Sources/swift-doc/Supporting Types/Layout.swift b/Sources/swift-doc/Supporting Types/Layout.swift new file mode 100644 index 00000000..b0cf85f3 --- /dev/null +++ b/Sources/swift-doc/Supporting Types/Layout.swift @@ -0,0 +1,1146 @@ +import HypertextLiteral + +func layout(_ page: Page) -> HTML { + let html = page.html + + return #""" + + + + + + \#(page.module.name) - \#(page.title) + + + +
+ + \#(page.module.name) + +
+ + + + \#(page is HomePage ? "" : + #""" + + """# as HypertextLiteral.HTML + ) + +
+
+ \#(html) +
+
+ +
+ \#(FooterPage().html) +
+ + + + """# +} + +let css = #""" +:root { + --system-red: rgb(255, 59, 48); + --system-orange: rgb(255, 149, 0); + --system-yellow: rgb(255, 204, 0); + --system-green: rgb(52, 199, 89); + --system-teal: rgb(90, 200, 250); + --system-blue: rgb(0, 122, 255); + --system-indigo: rgb(88, 86, 214); + --system-purple: rgb(175, 82, 222); + --system-pink: rgb(255, 45, 85); + --system-gray: rgb(142, 142, 147); + --system-gray2: rgb(174, 174, 178); + --system-gray3: rgb(199, 199, 204); + --system-gray4: rgb(209, 209, 214); + --system-gray5: rgb(229, 229, 234); + --system-gray6: rgb(242, 242, 247); + + --label: rgb(0, 0, 0); + --secondary-label: rgb(60, 60, 67); + --tertiary-label: rgb(60, 60, 67); + --quaternary-label: rgb(60, 60, 67); + --placeholder-text: rgb(60, 60, 67); + --link: rgb(0, 122, 255); + --separator: rgb(60, 60, 67); + --opaque-separator: rgb(198, 198, 200); + --system-fill: rgb(120, 120, 128); + --secondary-system-fill: rgb(120, 120, 128); + --tertiary-system-fill: rgb(118, 118, 128); + --quaternary-system-fill: rgb(116, 116, 128); + --system-background: rgb(255, 255, 255); + --secondary-system-background: rgb(242, 242, 247); + --tertiary-system-background: rgb(255, 255, 255); + --system-grouped-background: rgb(242, 242, 247); + --secondary-system-grouped-background: rgb(255, 255, 255); + --tertiary-system-grouped-background: rgb(242, 242, 247); +} + +@supports (color: color(display-p3 1 1 1)) { + :root { + --system-red: color(display-p3 1 0.2314 0.1882); + --system-orange: color(display-p3 1 0.5843 0); + --system-yellow: color(display-p3 1 0.8 0); + --system-green: color(display-p3 0.2039 0.7804 0.349); + --system-teal: color(display-p3 0.3529 0.7843 0.9804); + --system-blue: color(display-p3 0 0.4784 1); + --system-indigo: color(display-p3 0.3451 0.3373 0.8392); + --system-purple: color(display-p3 0.6863 0.3216 0.8706); + --system-pink: color(display-p3 1 0.1765 0.3333); + --system-gray: color(display-p3 0.5569 0.5569 0.5765); + --system-gray2: color(display-p3 0.6824 0.6824 0.698); + --system-gray3: color(display-p3 0.7804 0.7804 0.8); + --system-gray4: color(display-p3 0.8196 0.8196 0.8392); + --system-gray5: color(display-p3 0.898 0.898 0.9176); + --system-gray6: color(display-p3 0.949 0.949 0.9686); + + --label: color(display-p3 0 0 0); + --secondary-label: color(display-p3 0.2353 0.2353 0.2627); + --tertiary-label: color(display-p3 0.2353 0.2353 0.2627); + --quaternary-label: color(display-p3 0.2353 0.2353 0.2627); + --placeholder-text: color(display-p3 0.2353 0.2353 0.2627); + --link: color(display-p3 0 0.4784 1); + --separator: color(display-p3 0.2353 0.2353 0.2627); + --opaque-separator: color(display-p3 0.7765 0.7765 0.7843); + --system-fill: color(display-p3 0.4706 0.4706 0.502); + --secondary-system-fill: color(display-p3 0.4706 0.4706 0.502); + --tertiary-system-fill: color(display-p3 0.4627 0.4627 0.502); + --quaternary-system-fill: color(display-p3 0.4549 0.4549 0.502); + --system-background: color(display-p3 1 1 1); + --secondary-system-background: color(display-p3 0.949 0.949 0.9686); + --tertiary-system-background: color(display-p3 1 1 1); + --system-grouped-background: color(display-p3 0.949 0.949 0.9686); + --secondary-system-grouped-background: color(display-p3 1 1 1); + --tertiary-system-grouped-background: color( + display-p3 0.949 0.949 0.9686 + ); + } +} + +/* +@media (prefers-color-scheme: dark) { + :root { + --system-red: rgb(255, 69, 58); + --system-orange: rgb(255, 159, 10); + --system-yellow: rgb(255, 214, 10); + --system-green: rgb(48, 209, 88); + --system-teal: rgb(100, 210, 255); + --system-blue: rgb(10, 132, 255); + --system-indigo: rgb(94, 92, 230); + --system-purple: rgb(191, 90, 242); + --system-pink: rgb(255, 55, 95); + --system-gray: rgb(142, 142, 147); + --system-gray2: rgb(99, 99, 102); + --system-gray3: rgb(72, 72, 74); + --system-gray4: rgb(58, 58, 60); + --system-gray5: rgb(44, 44, 46); + --system-gray6: rgb(28, 28, 30); + + --label: rgb(255, 255, 255); + --secondary-label: rgb(235, 235, 245); + --tertiary-label: rgb(235, 235, 245); + --quaternary-label: rgb(235, 235, 245); + --placeholder-text: rgb(235, 235, 245); + --link: rgb(9, 132, 255); + --separator: rgb(84, 84, 88); + --opaque-separator: rgb(56, 56, 58); + --system-fill: rgb(120, 120, 128); + --secondary-system-fill: rgb(120, 120, 128); + --tertiary-system-fill: rgb(118, 118, 128); + --quaternary-system-fill: rgb(118, 118, 128); + --system-background: rgb(0, 0, 0); + --secondary-system-background: rgb(28, 28, 30); + --tertiary-system-background: rgb(44, 44, 46); + --system-grouped-background: rgb(0, 0, 0); + --secondary-system-grouped-background: rgb(28, 28, 30); + --tertiary-system-grouped-background: rgb(44, 44, 46); + } + + @supports (color: color(display-p3 1 1 1)) { + :root { + --system-red: color(display-p3 1 0.4118 0.3804); + --system-orange: color(display-p3 1 0.702 0.251); + --system-yellow: color(display-p3 1 0.8314 0.149); + --system-green: color(display-p3 0.1882 0.8588 0.3569); + --system-teal: color(display-p3 0.4392 0.8431 1); + --system-blue: color(display-p3 0.251 0.6118 1); + --system-indigo: color(display-p3 0.4902 0.4784 1); + --system-purple: color(display-p3 0.8549 0.5608 1); + --system-pink: color(display-p3 1 0.3922 0.5098); + --system-gray: color(display-p3 0.6824 0.6824 0.698); + --system-gray2: color(display-p3 0.4863 0.4863 0.502); + --system-gray3: color(display-p3 0.3294 0.3294 0.3373); + --system-gray4: color(display-p3 0.2667 0.2667 0.2745); + --system-gray5: color(display-p3 0.2118 0.2118 0.2196); + --system-gray6: color(display-p3 0.1412 0.1412 0.149); + + --label: color(display-p3 1 1 1); + --secondary-label: color(display-p3 0.9216 0.9216 0.9608); + --tertiary-label: color(display-p3 0.9216 0.9216 0.9608); + --quaternary-label: color(display-p3 0.9216 0.9216 0.9608); + --placeholder-text: color(display-p3 0.9216 0.9216 0.9608); + --link: color(display-p3 0.03529 0.5176 1); + --separator: color(display-p3 0.3294 0.3294 0.3451); + --opaque-separator: color(display-p3 0.2196 0.2196 0.2275); + --system-fill: color(display-p3 0.4706 0.4706 0.502); + --secondary-system-fill: color(display-p3 0.4706 0.4706 0.502); + --tertiary-system-fill: color(display-p3 0.4627 0.4627 0.502); + --quaternary-system-fill: color(display-p3 0.4627 0.4627 0.502); + --system-background: color(display-p3 0 0 0); + --secondary-system-background: color( + display-p3 0.1412 0.1412 0.149 + ); + --tertiary-system-background: color( + display-p3 0.2118 0.2118 0.2196 + ); + --system-grouped-background: color(display-p3 0 0 0); + --secondary-system-grouped-background: color( + display-p3 0.1412 0.1412 0.149 + ); + --tertiary-system-grouped-background: color( + display-p3 0.2118 0.2118 0.2196 + ); + } + } +} */ + +:root { + --large-title: 400 34pt / 41pt sans-serif; + --title-1: 400 28pt / 34pt sans-serif; + --title-2: 400 22pt / 28pt sans-serif; + --title-3: 400 20pt / 25pt sans-serif; + --headline: 600 17pt / 22pt sans-serif; + --body: 400 17pt / 22pt sans-serif; + --callout: 400 16pt / 21pt sans-serif; + --subhead: 400 15pt / 21pt sans-serif; + --footnote: 400 13pt / 18pt sans-serif; + --caption-1: 400 12pt / 16pt sans-serif; + --caption-2: 400 11pt / 13pt sans-serif; +} + +:root { + --icon-case: url('data:image/svg+xml;utf8,'); + --icon-class: url('data:image/svg+xml;utf8,'); + --icon-enumeration: url('data:image/svg+xml;utf8,'); + --icon-extension: url('data:image/svg+xml;utf8,'); + --icon-function: url('data:image/svg+xml;utf8,'); + --icon-method: url('data:image/svg+xml;utf8,'); + --icon-property: url('data:image/svg+xml;utf8,'); + --icon-protocol: url('data:image/svg+xml;utf8,'); + --icon-structure: url('data:image/svg+xml;utf8,'); + --icon-typealias: url('data:image/svg+xml;utf8,'); + --icon-variable: url('data:image/svg+xml;utf8,'); +} + +/************/ + +body, +input, +textarea, +select, +button { + font-synthesis: none; + -moz-font-feature-settings: "kern"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + direction: ltr; + text-align: left; +} + +:matches(h1, h2, h3, h4, h5, h6) img { + margin: 0 0.5em 0.2em 0; + vertical-align: middle; + display: inline-block; +} + +img + h1 { + margin-top: 0.5em; +} + +img + :matches(h2, h3, h4, h5, h6) { + margin-top: 0.3em; +} + +:matches(h1, h2, h3, h4, h5, h6) + * { + margin-top: 0.8em; +} + +:matches(h1, h2, h3, h4, h5, h6) + :matches(h1, h2, h3, h4, h5, h6) { + margin-top: 0.4em; +} + +:matches(p, ul, ol) + :matches(h1, h2, h3, h4, h5, h6) { + margin-top: 1.6em; +} + +:matches(p, ul, ol) + * { + margin-top: 0.8em; +} + +ul, +ol { + margin-left: 1.17647em; +} + +:matches(ul, ol) :matches(ul, ol) { + margin-top: 0; + margin-bottom: 0; +} + +nav ul, +nav ol { + margin: 0; + list-style: none; +} + +li li { + font-size: 1em; +} + +a:link, +a:visited { + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a:active { + text-decoration: none; +} + +p + a { + display: inline-block; +} + +b, +strong { + font-weight: 600; +} + +em, +i, +cite, +dfn { + font-style: italic; +} + +sup { + font-size: 0.6em; + vertical-align: top; + position: relative; + bottom: -0.2em; +} + +:matches(h1, h2, h3) sup { + font-size: 0.4em; +} + +sup a { + vertical-align: inherit; + color: inherit; +} + +sup a:hover { + color: var(--link); + text-decoration: none; +} + +sub { + line-height: 1; +} + +abbr { + border: 0; +} + +:lang(ja), +:lang(ko), +:lang(th), +:lang(zh) { + font-style: normal; +} + +:lang(ko) { + word-break: keep-all; +} + +form fieldset { + width: 95%; + margin: 1em auto; + max-width: 450px; +} + +form label { + position: relative; + display: block; + margin-bottom: 14px; + width: 100%; + font-size: 1em; + font-weight: 400; + line-height: 1.5em; +} + +input[type="text"], +input[type="email"], +input[type="number"], +input[type="password"], +input[type="tel"], +input[type="url"], +textarea { + margin: 0; + width: 100%; + height: 34px; + font-family: inherit; + font-size: 100%; + font-weight: 400; + border: 1px solid var(--opaque-separator); + border-radius: 4px; + padding: 0 1em 0; + position: relative; + z-index: 1; + color: #333333; + vertical-align: top; +} + +input[type="text"], +input[type="text"]:focus, +input[type="email"], +input[type="email"]:focus, +input[type="number"], +input[type="number"]:focus, +input[type="password"], +input[type="password"]:focus, +input[type="tel"], +input[type="tel"]:focus, +input[type="url"], +input[type="url"]:focus, +textarea, +textarea:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +input[type="text"]:focus, +input[type="email"]:focus, +input[type="number"]:focus, +input[type="password"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +textarea:focus { + border-color: #0088cc; + outline: 0; + -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + z-index: 9; +} + +input[type="text"]:-moz-read-only, +input[type="text"]:-moz-read-only, +input[type="email"]:-moz-read-only, +input[type="email"]:-moz-read-only, +input[type="number"]:-moz-read-only, +input[type="number"]:-moz-read-only, +input[type="password"]:-moz-read-only, +input[type="password"]:-moz-read-only, +input[type="tel"]:-moz-read-only, +input[type="tel"]:-moz-read-only, +input[type="url"]:-moz-read-only, +input[type="url"]:-moz-read-only { + background: none; + border: none; + box-shadow: none; + padding-left: 0; +} + +input[type="text"]:-moz-read-only, +input[type="text"]:read-only, +input[type="email"]:-moz-read-only, +input[type="email"]:read-only, +input[type="number"]:-moz-read-only, +input[type="number"]:read-only, +input[type="password"]:-moz-read-only, +input[type="password"]:read-only, +input[type="tel"]:-moz-read-only, +input[type="tel"]:read-only, +input[type="url"]:-moz-read-only, +input[type="url"]:read-only { + background: none; + border: none; + box-shadow: none; + padding-left: 0; +} + +::-webkit-input-placeholder, +:-moz-placeholder, +::-moz-placeholder, +:-ms-input-placeholder { + color: var(--placeholder-text); +} + +textarea { + min-height: 134px; + line-height: 1.4737; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + resize: vertical; +} +textarea, +textarea:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +select { + background: transparent; + width: 100%; + height: 34px; + padding: 0 1em; + font-size: 1em; + font-family: inherit; + border-radius: 4px; + border: none; + margin: 0; + cursor: pointer; +} +select, +select:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +select:focus { + border-color: #0088cc; + outline: 0; + -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + z-index: 9; +} + +input[type="file"] { + margin: 0; + font-family: inherit; + font-size: 100%; + background: #fafafa; + width: 100%; + height: 34px; + border-radius: 4px; + padding: 6px 1em; + position: relative; + z-index: 1; + color: #333333; + vertical-align: top; + cursor: pointer; +} + +input[type="file"]:focus { + border-color: #0088cc; + outline: 0; + -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + z-index: 9; +} +input[type="file"]:focus, +input[type="file"]:focus:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +button, +button:focus, +input[type="reset"], +input[type="reset"]:focus, +input[type="submit"], +input[type="submit"]:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +:matches(button, input[type="reset"], input[type="submit"]) { + background-color: #e3e3e3; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(white), + to(#e3e3e3) + ); + background: linear-gradient(white, #e3e3e3); + border-color: #d6d6d6; + color: #0070c9; +} +:matches(button, input[type="reset"], input[type="submit"]):hover { + background-color: #eeeeee; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(white), + to(#eeeeee) + ); + background: linear-gradient(white, #eeeeee); + border-color: #d9d9d9; +} +:matches(button, input[type="reset"], input[type="submit"]):active { + background-color: gainsboro; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(#f7f7f7), + to(gainsboro) + ); + background: linear-gradient(#f7f7f7, gainsboro); + border-color: #d0d0d0; +} +:matches(button, input[type="reset"], input[type="submit"]):disabled { + background-color: #e3e3e3; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(white), + to(#e3e3e3) + ); + background: linear-gradient(white, #e3e3e3); + border-color: #d6d6d6; + color: #0070c9; +} + +/* */ + +body { + background: var(--system-grouped-background); + font: var(--body); + font-family: ui-system, -apple-system, BlinkMacSystemFont, sans-serif; + color: var(--label); +} + +h1 { + font: var(--large-title); +} + +h2 { + font: var(--title-1); +} + +h3 { + font: var(--title-2); +} + +h4, +h5, +h6 { + font: var(--title-3); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 500; +} + +/* strong, + th, + dt { + font: var(--headline); + } */ + +a { + color: var(--link); +} + +label { + font: var(--callout); +} + +label, +input { + display: block; +} + +input { + margin-bottom: 1em; +} + +/* button, + input[type="submit"] { + color: var(--link); + background: transparent; + border: none; + padding: 0.5em; + } */ + +/*********************/ + +article article { + background: var(--secondary-system-background); + max-width: 70ch; + margin: 1em auto; + padding: 1em; +} + +hr { + border: none; + border-top: 1px var(--opaque-separator) solid; + margin: 1em 0; +} + +table { + width: 100%; + font: var(--caption-1); + caption-side: bottom; + margin-bottom: 2em; +} + +th, +td { + border-bottom-width: 1px; + border-bottom-style: solid; + padding: 0.5em 0; +} + +th { + font-weight: 500; + text-align: left; + border-bottom-color: var(--separator); +} + +td { + border-bottom-color: var(--opaque-separator); + color: var(--secondary-label); +} + +caption { + text-align: left; + margin-top: 2em; + font: var(--caption-2); + color: var(--tertiary-label); +} + +.graph > polygon { + display: none; +} + +.graph text { + font-family: monospace; + fill: currentColor !important; +} + +.graph path, +.graph ellipse, +.graph rect, +.graph polygon { + stroke: currentColor !important; +} + +body { + width: 90vw; + max-width: 1280px; + margin: 1em auto; + display: grid; + grid-template-areas: + "header search" + "main nav" + "footer footer"; + grid-template-columns: 3fr 1fr; + gap: 1em; +} + +body > header { + font: var(--large-title); +} + +body.HomePage { + grid-template-areas: + "header search" + "main main" + "footer footer"; +} +@media screen and (min-width: 769px) { + body.HomePage ul { + column-count: 2; + column-gap: 2em; + padding: 0 0 1em 0; + margin: 0 0 1em 0; + overflow-wrap: break-word; + } +} + +@media screen and (max-width: 768px) { + :root { + --large-title: 400 31pt / 38pt sans-serif; + --title-1: 400 25pt / 31pt sans-serif; + --title-2: 400 19pt / 24pt sans-serif; + --title-3: 400 17pt / 22pt sans-serif; + --headline: 600 14pt / 19pt sans-serif; + --body: 400 14pt / 19pt sans-serif; + --callout: 400 13pt / 18pt sans-serif; + --subhead: 400 12pt / 16pt sans-serif; + --footnote: 400 12pt / 16pt sans-serif; + --caption-1: 400 11pt / 13pt sans-serif; + --caption-2: 400 11pt / 13pt sans-serif; + } + + body { + width: 96vw; + max-width: 100%; + display: block; + } + + body > header { + font: var(--large-title); + } + + body > nav { + display: none; + } + + body > main { + padding: 0 1em; + } + + .parameters { + display: block !important; + } + + .parameters dt { + text-align: initial !important; + margin: 0.5em 0; + } + + .parameters dd { + margin-bottom: 1em; + } +} + +body > header { + grid-area: header; + padding: 0.5em 0; +} + +main, +nav { + overflow-x: scroll; +} + +main { + background: var(--system-background); + border-radius: 8px; +} + +form.search { + grid-area: search; +} + +body > footer { + grid-area: footer; + font-size: smaller; + color: var(--tertiary-label); +} + +main { + padding: 0 2em; + grid-area: main; + /* max-width: 66ch; */ +} + +nav { + grid-area: nav; + overflow-x: scroll; + padding: 1em; +} + +nav a { + color: var(--tertiary-label); +} + +nav ul a { + color: var(--quaternary-label); +} + +nav ol { + padding: 0; +} + +nav ul { + padding: 0; + margin-bottom: 1em; + font-size: smaller; +} + +nav ol > li > a { + font-weight: 500; + display: block; + margin: 0.5em 0; +} + +nav li { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +nav div { + position: sticky; + top: 1em; +} + +main > section { + margin-bottom: 2em; +} + +main section { + margin-bottom: 1em; +} + +/* main :matches(h1, h2, h3) { + position: sticky; + top: 0px; +} + +main h1 { + z-index: 1; +} + +main h2 { + z-index: 2; +} + +main h3 { + z-index: 3; +} */ + +article > .summary { + margin-bottom: 2em; + padding-bottom: 1em; + border-bottom: 1px var(--opaque-separator) solid; +} + +article > .summary:last-child { + border-bottom: none; +} + +.parameters { + display: grid; + grid-template-columns: 1fr 3fr; + gap: 0.5em; +} + +.parameters dt { + text-align: right; +} + +.parameters dd { + color: var(--secondary-label); +} + +dd p { + margin-top: 0; +} + +.highlight { + background: var(--secondary-system-background); + border-radius: 8px; + font-size: smaller; + overflow-x: scroll; + white-space: pre-line; + padding: 1em; + padding-left: 3em; + text-indent: -2em; +} + +.highlight .p { + white-space: nowrap; +} + +.highlight .placeholder { + color: var(--label); +} + +.highlight a { + text-decoration: underline; + color: var(--placeholder-text); +} + +.highlight .literal, +.highlight .keyword, +.highlight .attribute { + color: var(--system-purple); +} + +.highlight .number { + color: var(--system-blue); +} + +.highlight .declaration { + color: var(--system-teal); +} + +.highlight .type { + color: var(--system-indigo); +} + +.highlight .directive { + color: var(--system-orange); +} + +.highlight .comment { + color: var(--system-gray); +} + +main details { + margin-bottom: 1em; + padding: 1em 1em 1em 2em; + background: var(--system-background); + border-bottom: 1px var(--opaque-separator) solid; +} + +main details:last-of-type { + border-bottom: none; +} + +main summary { + cursor: pointer; + font-weight: 500; +} + +main summary::-webkit-details-marker { + color: transparent; + position: absolute; + width: 1em; + height: 1em; + margin-left: -2em; + margin-top: 0.125em; + outline: 0 !important; +} + +#initializers summary::-webkit-details-marker, +#methods summary::-webkit-details-marker { + background: var(--icon-method); +} + +#properties summary::-webkit-details-marker { + background: var(--icon-property); +} + +main summary:hover { + text-decoration: underline; +} + +figure { + padding: 1em 0; +} + +figure svg { + max-width: 100%; + margin: 0 auto; + display: block; +} + +h1 small { + font-size: 0.5em; + line-height: 1.5; + display: block; + font-weight: normal; + color: var(--quaternary-label); +} + +h1 code { + font-family: ui-system, -apple-system, BlinkMacSystemFont, sans-serif; +} + +p code, +dd code, +li code { + font-size: smaller; + color: var(--secondary-label); +} + +a code { + text-decoration: underline; +} + +li[class] { + list-style: none; + background-repeat: no-repeat; + background-position: left top; + background-size: 1em; + padding-left: 3em; + text-indent: -1em; + margin-bottom: 1em; +} + +nav li[class] { + background-position: left 0.2em; +} + +li.case { + background-image: var(--icon-case); +} + +li.class { + background-image: var(--icon-class); +} + +li.enumeration { + background-image: var(--icon-enumeration); +} + +li.extension { + background-image: var(--icon-extension); +} + +li.function { + background-image: var(--icon-function); +} + +li.method, +li.initializer { + background-image: var(--icon-method); +} + +li.property { + background-image: var(--icon-property); +} + +li.protocol { + background-image: var(--icon-protocol); +} + +li.structure { + background-image: var(--icon-structure); +} + +li.typealias { + background-image: var(--icon-typealias); +} + +li.variable { + background-image: var(--icon-variable); +} + +"""# diff --git a/Sources/swift-doc/Supporting Types/Page.swift b/Sources/swift-doc/Supporting Types/Page.swift index e323b730..6ed37aa8 100644 --- a/Sources/swift-doc/Supporting Types/Page.swift +++ b/Sources/swift-doc/Supporting Types/Page.swift @@ -2,21 +2,44 @@ import Foundation import SwiftDoc import SwiftMarkup import SwiftSemantics -import CommonMark import struct SwiftSemantics.Protocol +import CommonMark +import HypertextLiteral -protocol Page { - var body: Document { get } +protocol Page: HypertextLiteralConvertible { + var module: Module { get } + var title: String { get } + var document: CommonMark.Document { get } + var html: HypertextLiteral.HTML { get } +} + +extension Page { + var module: Module { fatalError("unimplemented") } + var title: String { fatalError("unimplemented") } } extension Page { - func write(to url: URL) throws { - let data = body.render(format: .commonmark).data(using: .utf8) + func write(to url: URL, format: SwiftDoc.Generate.Format) throws { + let data: Data? + switch format { + case .commonmark: + data = document.render(format: .commonmark).data(using: .utf8) + case .html: + data = layout(self).description.data(using: .utf8) + } + + let fileManager = FileManager.default + try fileManager.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: [.posixPermissions: 0o744]) + try data?.write(to: url) - try FileManager.default.setAttributes([.posixPermissions: 0o744], ofItemAtPath: url.path) + try fileManager.setAttributes([.posixPermissions: 0o744], ofItemAtPath: url.path) } } +func path(for symbol: Symbol) -> String { + return path(for: symbol.id.description) +} + func path(for identifier: CustomStringConvertible) -> String { return "\(identifier)".replacingOccurrences(of: ".", with: "_") } diff --git a/Sources/swift-doc/Supporting Types/Pages/FooterPage.swift b/Sources/swift-doc/Supporting Types/Pages/FooterPage.swift index 31c0531e..bc17928b 100644 --- a/Sources/swift-doc/Supporting Types/Pages/FooterPage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/FooterPage.swift @@ -1,23 +1,43 @@ import Foundation import CommonMarkBuilder +import HypertextLiteral fileprivate let dateFormatter: DateFormatter = { + var dateFormatter = DateFormatter() + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .none + return dateFormatter +}() + +fileprivate let timestampDateFormatter: DateFormatter = { var dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" return dateFormatter }() -struct FooterPage: Page { +fileprivate let href = "https://github.com/SwiftDocOrg/swift-doc" +struct FooterPage: Page { // MARK: - Page - var body: Document { - let timestamp = dateFormatter.string(from: Date()) + var document: CommonMark.Document { + let timestamp = timestampDateFormatter.string(from: Date()) return Document { Fragment { - "Generated at \(timestamp) using [swift-doc](https://github.com/SwiftDocOrg/swift-doc)." + "Generated at \(timestamp) using [swift-doc](\(href))." } } } + + var html: HypertextLiteral.HTML { + let timestamp = timestampDateFormatter.string(from: Date()) + let dateString = dateFormatter.string(from: Date()) + + return #""" +

+ Generated on using swift-doc. +

+ """# + } } diff --git a/Sources/swift-doc/Supporting Types/Pages/GlobalPage.swift b/Sources/swift-doc/Supporting Types/Pages/GlobalPage.swift index 60a5353c..e661295b 100644 --- a/Sources/swift-doc/Supporting Types/Pages/GlobalPage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/GlobalPage.swift @@ -1,6 +1,7 @@ import SwiftSemantics import SwiftDoc import CommonMarkBuilder +import HypertextLiteral struct GlobalPage: Page { let module: Module @@ -14,17 +15,44 @@ struct GlobalPage: Page { } // MARK: - Page + + var title: String { + return name + } - var body: Document { + var document: CommonMark.Document { return Document { Heading { name } Section { ForEach(in: symbols) { symbol in Heading { symbol.id.description } - Documentation(for: symbol) + Documentation(for: symbol, in: module) } } } } + + var html: HypertextLiteral.HTML { + let description: String + + let descriptions = Set(symbols.map { String(describing: type(of: $0.api)) }) + if descriptions.count == 1 { + description = descriptions.first! + } else { + description = "Global" + } + + + return #""" +

+ \#(description) + \#(softbreak(name)) +

+ + \#(symbols.map { symbol in + Documentation(for: symbol, in: module).html + }) + """# + } } diff --git a/Sources/swift-doc/Supporting Types/Pages/HomePage.swift b/Sources/swift-doc/Supporting Types/Pages/HomePage.swift index 91c75994..d44c2765 100644 --- a/Sources/swift-doc/Supporting Types/Pages/HomePage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/HomePage.swift @@ -1,34 +1,29 @@ import CommonMarkBuilder import SwiftDoc import SwiftSemantics +import HypertextLiteral struct HomePage: Page { var module: Module + var types: [Symbol] = [] + var protocols: [Symbol] = [] + var operatorNames: Set = [] + var globalTypealiasNames: Set = [] + var globalFunctionNames: Set = [] + var globalVariableNames: Set = [] + init(module: Module) { self.module = module - } - - // MARK: - Page - - var body: Document { - var typeNames: Set = [] - var protocolNames: Set = [] - var operatorNames: Set = [] - var globalTypealiasNames: Set = [] - var globalFunctionNames: Set = [] - var globalVariableNames: Set = [] for symbol in module.interface.topLevelSymbols.filter({ $0.isPublic }) { - switch symbol.declaration { - case is Class: - typeNames.insert(symbol.id.description) - case is Enumeration: - typeNames.insert(symbol.id.description) - case is Structure: - typeNames.insert(symbol.id.description) - case let `protocol` as Protocol: - protocolNames.insert(`protocol`.name) + switch symbol.api { + case is Class, + is Enumeration, + is Structure: + types.append(symbol) + case is Protocol: + protocols.append(symbol) case let `typealias` as Typealias: globalTypealiasNames.insert(`typealias`.name) case let `operator` as Operator: @@ -43,6 +38,17 @@ struct HomePage: Page { continue } } + } + + // MARK: - Page + + var title: String { + return module.name + } + + var document: CommonMark.Document { + let typeNames = Set(types.map { $0.id.description }) + let protocolNames = Set(protocols.map { $0.id.description }) return Document { ForEach(in: [ @@ -74,7 +80,7 @@ struct HomePage: Page { Heading { heading } List(of: names.sorted()) { name in - Link(urlString: path(for: name), text: name) + Link(urlString: path(for: name), text: softbreak(name)) } } } @@ -83,7 +89,30 @@ struct HomePage: Page { } } - var lines: [String] { - body.description.split(separator: "\n", omittingEmptySubsequences: false).map { String($0) } + var html: HypertextLiteral.HTML { + return #""" + \#([ + ("Types", types), + ("Protocols", protocols), + ].compactMap { (heading, symbols) -> HypertextLiteral.HTML? in + guard !symbols.isEmpty else { return nil } + + return #""" +

\#(heading)

+
    + \#(symbols.sorted().map { symbol -> HypertextLiteral.HTML in + let descriptor = String(describing: type(of: symbol.api)) + return #""" +
  • + + \#(symbol.id.description) + +
  • + """# as HypertextLiteral.HTML + }) +
+ """# as HypertextLiteral.HTML + }) + """# } } diff --git a/Sources/swift-doc/Supporting Types/Pages/SidebarPage.swift b/Sources/swift-doc/Supporting Types/Pages/SidebarPage.swift index a74558c3..4ac2961f 100644 --- a/Sources/swift-doc/Supporting Types/Pages/SidebarPage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/SidebarPage.swift @@ -1,26 +1,23 @@ import SwiftSemantics import SwiftDoc import CommonMarkBuilder +import HypertextLiteral struct SidebarPage: Page { var module: Module + var typeNames: Set = [] + var protocolNames: Set = [] + var operatorNames: Set = [] + var globalTypealiasNames: Set = [] + var globalFunctionNames: Set = [] + var globalVariableNames: Set = [] + init(module: Module) { self.module = module - } - - // MARK: - Page - - var body: Document { - var typeNames: Set = [] - var protocolNames: Set = [] - var operatorNames: Set = [] - var globalTypealiasNames: Set = [] - var globalFunctionNames: Set = [] - var globalVariableNames: Set = [] for symbol in module.interface.topLevelSymbols.filter({ $0.isPublic }) { - switch symbol.declaration { + switch symbol.api { case is Class: typeNames.insert(symbol.id.description) case is Enumeration: @@ -43,7 +40,11 @@ struct SidebarPage: Page { continue } } + } + + // MARK: - Page + var document: CommonMark.Document { return Document { ForEach(in: ( [ @@ -58,17 +59,23 @@ struct SidebarPage: Page { // FIXME: This should be an HTML block Fragment { #""" -
+
\#(section.title) """# } List(of: section.names.sorted()) { name in - Link(urlString: path(for: name), text: name) + Link(urlString: "/" + path(for: name), text: name) } Fragment { "
" } } } } + + var html: HypertextLiteral.HTML { + #""" + \#(document) + """# + } } diff --git a/Sources/swift-doc/Supporting Types/Pages/TypePage.swift b/Sources/swift-doc/Supporting Types/Pages/TypePage.swift index d1f75c83..4ab71068 100644 --- a/Sources/swift-doc/Supporting Types/Pages/TypePage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/TypePage.swift @@ -1,30 +1,35 @@ import SwiftSemantics import SwiftDoc import CommonMarkBuilder +import HypertextLiteral struct TypePage: Page { let module: Module let symbol: Symbol init(module: Module, symbol: Symbol) { - precondition(symbol.declaration is Type) + precondition(symbol.api is Type) self.module = module self.symbol = symbol } // MARK: - Page - var body: Document { + var title: String { + return symbol.id.description + } + + var document: CommonMark.Document { return Document { Heading { symbol.id.description } - Documentation(for: symbol) + Documentation(for: symbol, in: module) Inheritance(of: symbol, in: module) - if symbol.declaration is Protocol { + if symbol.api is Protocol { ConformingTypes(to: symbol, in: module) - } else if symbol.declaration is Type { + } else if symbol.api is Type { NestedTypes(of: symbol, in: module) } @@ -32,4 +37,20 @@ struct TypePage: Page { Requirements(of: symbol, in: module) } } + + var html: HypertextLiteral.HTML { +// print(symbol.api, module.interface.relationshipsBySubject[symbol.id]) +// print("") + return #""" +

+ \#(String(describing: type(of: symbol.api))) + \#(softbreak(symbol.id.description)) +

+ + \#(Documentation(for: symbol, in: module).html) + \#(Inheritance(of: symbol, in: module).html) + \#(Members(of: symbol, in: module).html) + \#(Requirements(of: symbol, in: module).html) + """# + } } diff --git a/Sources/swift-doc/Supporting Types/Pages/TypealiasPage.swift b/Sources/swift-doc/Supporting Types/Pages/TypealiasPage.swift index 0ca2b7a5..a342fa0a 100644 --- a/Sources/swift-doc/Supporting Types/Pages/TypealiasPage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/TypealiasPage.swift @@ -1,23 +1,39 @@ import SwiftSemantics import SwiftDoc import CommonMarkBuilder +import HypertextLiteral struct TypealiasPage: Page { let module: Module let symbol: Symbol init(module: Module, symbol: Symbol) { - precondition(symbol.declaration is Typealias) + precondition(symbol.api is Typealias) self.module = module self.symbol = symbol } // MARK: - Page - var body: Document { + var title: String { + return symbol.id.description + } + + var document: CommonMark.Document { Document { Heading { symbol.id.description } - Documentation(for: symbol) + Documentation(for: symbol, in: module) } } + + var html: HypertextLiteral.HTML { + #""" +

+ \#(String(describing: type(of: symbol.api))) + \#(softbreak(symbol.id.description)) +

+ + \#(Documentation(for: symbol, in: module).html) + """# + } } diff --git a/Tests/SwiftDocTests/SourceFileTests.swift b/Tests/SwiftDocTests/SourceFileTests.swift index 6346117c..a3ffb714 100644 --- a/Tests/SwiftDocTests/SourceFileTests.swift +++ b/Tests/SwiftDocTests/SourceFileTests.swift @@ -59,18 +59,18 @@ final class SourceFileTests: XCTestCase { XCTAssertEqual(sourceFile.symbols.count, 12) for symbol in sourceFile.symbols { - XCTAssert(symbol.isPublic, "\(symbol.declaration) isn't public") + XCTAssert(symbol.isPublic, "\(symbol.api) isn't public") } do { let `protocol` = sourceFile.symbols[0] - XCTAssert(`protocol`.declaration is Protocol) + XCTAssert(`protocol`.api is Protocol) XCTAssertEqual(`protocol`.documentation?.summary, "Protocol") do { let function = sourceFile.symbols[1] - XCTAssert(function.declaration is Function) + XCTAssert(function.api is Function) XCTAssertEqual(function.context.count, 1) XCTAssert(function.context.first is Symbol) @@ -82,7 +82,7 @@ final class SourceFileTests: XCTestCase { do { let property = sourceFile.symbols[2] - XCTAssert(property.declaration is Variable) + XCTAssert(property.api is Variable) XCTAssertEqual(property.context.count, 1) XCTAssert(property.context.first is Symbol) @@ -94,13 +94,13 @@ final class SourceFileTests: XCTestCase { do { let enumeration = sourceFile.symbols[3] - XCTAssert(enumeration.declaration is Enumeration) + XCTAssert(enumeration.api is Enumeration) XCTAssertEqual(enumeration.documentation?.summary, "Enumeration") do { let `case` = sourceFile.symbols[4] - XCTAssert(`case`.declaration is Enumeration.Case) + XCTAssert(`case`.api is Enumeration.Case) XCTAssertEqual(`case`.context.count, 1) XCTAssert(`case`.context.first is Symbol) @@ -112,7 +112,7 @@ final class SourceFileTests: XCTestCase { do { let structure = sourceFile.symbols[5] - XCTAssert(structure.declaration is Structure) + XCTAssert(structure.api is Structure) XCTAssertEqual(structure.documentation?.summary, "Structure") } @@ -120,7 +120,7 @@ final class SourceFileTests: XCTestCase { do { let function = sourceFile.symbols[6] - XCTAssert(function.declaration is Function) + XCTAssert(function.api is Function) XCTAssertEqual(function.context.count, 1) XCTAssert(function.context.first is Extension) @@ -132,7 +132,7 @@ final class SourceFileTests: XCTestCase { do { let property = sourceFile.symbols[7] - XCTAssert(property.declaration is Variable) + XCTAssert(property.api is Variable) XCTAssertEqual(property.context.count, 1) XCTAssert(property.context.first is Extension) @@ -144,13 +144,13 @@ final class SourceFileTests: XCTestCase { do { let `class` = sourceFile.symbols[8] - XCTAssert(`class`.declaration is Class) + XCTAssert(`class`.api is Class) XCTAssertEqual(`class`.documentation?.summary, "Class") do { let function = sourceFile.symbols[9] - XCTAssert(function.declaration is Function) + XCTAssert(function.api is Function) XCTAssertEqual(function.context.count, 1) XCTAssert(function.context.first is Symbol) @@ -162,7 +162,7 @@ final class SourceFileTests: XCTestCase { do { let property = sourceFile.symbols[10] - XCTAssert(property.declaration is Variable) + XCTAssert(property.api is Variable) XCTAssertEqual(property.context.count, 1) XCTAssert(property.context.first is Symbol) @@ -174,8 +174,8 @@ final class SourceFileTests: XCTestCase { do { let `class` = sourceFile.symbols[11] - XCTAssert(`class`.declaration is Class) - XCTAssertEqual((`class`.declaration as? Class)?.inheritance, ["C"]) + XCTAssert(`class`.api is Class) + XCTAssertEqual((`class`.api as? Class)?.inheritance, ["C"]) XCTAssertEqual(`class`.documentation?.summary, "Subclass") } } From b2942403029d2e97c838883b1ae667828d785731 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 31 Mar 2020 09:07:51 -0700 Subject: [PATCH 02/11] Update layout and style --- Package.resolved | 12 +- Package.swift | 2 +- Sources/SwiftDoc/Interface.swift | 8 +- Sources/SwiftDoc/Symbol.swift | 2 +- .../Extensions/SwiftDoc+Extensions.swift | 27 +- Sources/swift-doc/Supporting Types/CSS.swift | 1153 +++++++++++++++++ .../Components/ConformingTypes.swift | 48 - .../Components/Documentation.swift | 52 +- .../Components/Inheritance.swift | 79 -- .../Supporting Types/Components/Members.swift | 31 +- .../Components/NestedTypes.swift | 49 - .../Components/Relationships.swift | 127 ++ .../swift-doc/Supporting Types/Helpers.swift | 36 +- .../swift-doc/Supporting Types/Layout.swift | 1105 +--------------- .../Supporting Types/Pages/HomePage.swift | 50 +- .../Supporting Types/Pages/TypePage.swift | 16 +- 16 files changed, 1428 insertions(+), 1369 deletions(-) create mode 100644 Sources/swift-doc/Supporting Types/CSS.swift delete mode 100644 Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift delete mode 100644 Sources/swift-doc/Supporting Types/Components/Inheritance.swift delete mode 100644 Sources/swift-doc/Supporting Types/Components/NestedTypes.swift create mode 100644 Sources/swift-doc/Supporting Types/Components/Relationships.swift diff --git a/Package.resolved b/Package.resolved index 8987c4c4..7d37c37b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,7 +15,7 @@ "repositoryURL": "https://github.com/SwiftDocOrg/GraphViz.git", "state": { "branch": null, - "revision": "ce0327a62c4b5a5ddd4741406ea54f164f228a54", + "revision": "03405c13dc1c31f50c08bbec6e7587cbee1c7fb3", "version": null } }, @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/apple/swift-argument-parser.git", "state": { "branch": null, - "revision": "35b76bf577d3cc74820f8991894ce3bcdf024ddc", - "version": "0.0.2" + "revision": "8d31a0905c346a45c87773ad50862b5b3df8dff6", + "version": "0.0.4" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/SwiftDocOrg/swift-cmark.git", "state": { "branch": null, - "revision": "2a766030bee955b4806044fd7aca1b6884475138", - "version": "0.28.3+20200110.2a76603" + "revision": "1168665f6b36be747ffe6b7b90bc54cfc17f42b7", + "version": "0.28.3+20200207.1168665" } }, { @@ -87,7 +87,7 @@ "repositoryURL": "https://github.com/SwiftDocOrg/SwiftSemantics.git", "state": { "branch": "swift-5.2", - "revision": "3bb09e896a813cf0a72ec88a19dc0a7db49577cd", + "revision": "4fdc48bddbbb8311079ed111e5a4f2b92423b05c", "version": null } }, diff --git a/Package.swift b/Package.swift index 41e5e2fb..3ffa5817 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,7 @@ let package = Package( .package(url: "https://github.com/SwiftDocOrg/SwiftSemantics.git", .branch("swift-5.2")), .package(url: "https://github.com/SwiftDocOrg/CommonMark.git", .branch("master")), .package(url: "https://github.com/SwiftDocOrg/SwiftMarkup.git", .upToNextMinor(from: "0.0.5")), - .package(url: "https://github.com/SwiftDocOrg/GraphViz.git", .revision("ce0327a62c4b5a5ddd4741406ea54f164f228a54")), + .package(url: "https://github.com/SwiftDocOrg/GraphViz.git", .revision("03405c13dc1c31f50c08bbec6e7587cbee1c7fb3")), .package(url: "https://github.com/NSHipster/HypertextLiteral.git", .upToNextMinor(from: "0.0.2")), .package(url: "https://github.com/SwiftDocOrg/Markup.git", .revision("bcc9bff98749f8ed92221375591a1afd61b02f1a")), .package(url: "https://github.com/NSHipster/SwiftSyntaxHighlighter.git", .revision("fe39b4ec07e1e37872adf4b506d223ab27cf8cea")), diff --git a/Sources/SwiftDoc/Interface.swift b/Sources/SwiftDoc/Interface.swift index 57a714d4..9ca8b626 100644 --- a/Sources/SwiftDoc/Interface.swift +++ b/Sources/SwiftDoc/Interface.swift @@ -17,8 +17,8 @@ public final class Interface: Codable { return Dictionary(grouping: symbols, by: { $0.id }) }() - public lazy var symbolsGroupedByName: [String: [Symbol]] = { - return Dictionary(grouping: symbols, by: { $0.name }) + public lazy var symbolsGroupedByQualifiedName: [String: [Symbol]] = { + return Dictionary(grouping: symbols, by: { $0.id.description }) }() public private(set) lazy var topLevelSymbols: [Symbol] = { @@ -101,10 +101,10 @@ public final class Interface: Codable { inheritedTypeNames = Set(inheritedTypeNames.flatMap { $0.split(separator: "&").map { $0.trimmingCharacters(in: .whitespaces) } }) for name in inheritedTypeNames { - let inheritedTypes = symbols.filter({ ($0.api is Class || $0.api is Protocol) && $0.id.matches(name) }) + let inheritedTypes = symbols.filter({ ($0.api is Class || $0.api is Protocol) && $0.id.description == name }) if inheritedTypes.isEmpty { let inherited = Symbol(api: Unknown(name: name), context: [], declaration: nil, documentation: nil, sourceLocation: nil) - relationships.insert(Relationship(subject: symbol, predicate: .inheritsFrom, object: inherited)) + relationships.insert(Relationship(subject: symbol, predicate: .conformsTo, object: inherited)) } else { for inherited in inheritedTypes { let predicate: Relationship.Predicate diff --git a/Sources/SwiftDoc/Symbol.swift b/Sources/SwiftDoc/Symbol.swift index 5272c1e8..c41f5933 100644 --- a/Sources/SwiftDoc/Symbol.swift +++ b/Sources/SwiftDoc/Symbol.swift @@ -43,7 +43,7 @@ public final class Symbol { return true } - if let symbol = context.compactMap({ $0 as? Symbol }).first, + if let symbol = context.compactMap({ $0 as? Symbol }).last, symbol.api.modifiers.contains(where: { $0.name == "public" }) { switch symbol.api { diff --git a/Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift b/Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift index df5cc207..6a0c306a 100644 --- a/Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift +++ b/Sources/swift-doc/Extensions/SwiftDoc+Extensions.swift @@ -8,6 +8,12 @@ extension Symbol { var node: Node { var node = Node(id.description) node.fontName = "Menlo" + node.shape = .box + node.style = .rounded + + node.width = 3 + node.height = 0.5 + node.fixedSize = .shape if !(api is Unknown) { node.href = "/" + path(for: self) @@ -15,16 +21,18 @@ extension Symbol { switch api { case let `class` as Class: - node.shape = .ellipse + node.class = "class" if `class`.modifiers.contains(where: { $0.name == "final" }) { node.strokeWidth = 2.0 } + case is Enumeration: + node.class = "enumeration" case is Structure: - node.shape = .box - node.style = .rounded + node.class = "structure" case is Protocol: - node.shape = .ellipse - node.style = .dashed + node.class = "protocol" + case is Unknown: + node.class = "unknown" default: break } @@ -42,6 +50,7 @@ extension Symbol { var symbolNode = self.node symbolNode.strokeWidth = 3.0 + symbolNode.class = [symbolNode.class, "current"].compactMap { $0 }.joined(separator: " ") graph.append(symbolNode) @@ -64,12 +73,8 @@ extension Relationship { let to = object.node var edge = Edge(from: from.id, to: to.id) - switch predicate { - case .conformsTo: - edge.style = .dashed - default: - break - } + edge.class = predicate.rawValue + edge.preferredEdgeLength = 1.5 return edge } diff --git a/Sources/swift-doc/Supporting Types/CSS.swift b/Sources/swift-doc/Supporting Types/CSS.swift new file mode 100644 index 00000000..9430d823 --- /dev/null +++ b/Sources/swift-doc/Supporting Types/CSS.swift @@ -0,0 +1,1153 @@ +let css = #""" +:root { + --system-red: rgb(255, 59, 48); + --system-orange: rgb(255, 149, 0); + --system-yellow: rgb(255, 204, 0); + --system-green: rgb(52, 199, 89); + --system-teal: rgb(90, 200, 250); + --system-blue: rgb(0, 122, 255); + --system-indigo: rgb(88, 86, 214); + --system-purple: rgb(175, 82, 222); + --system-pink: rgb(255, 45, 85); + --system-gray: rgb(142, 142, 147); + --system-gray2: rgb(174, 174, 178); + --system-gray3: rgb(199, 199, 204); + --system-gray4: rgb(209, 209, 214); + --system-gray5: rgb(229, 229, 234); + --system-gray6: rgb(242, 242, 247); + + --label: rgb(0, 0, 0); + --secondary-label: rgb(60, 60, 67); + --tertiary-label: rgb(72, 72, 74); + --quaternary-label: rgb(99, 99, 102); + --placeholder-text: rgb(142, 142, 147); + --link: rgb(0, 122, 255); + --separator: rgb(229, 229, 234); + --opaque-separator: rgb(198, 198, 200); + --system-fill: rgb(120, 120, 128); + --secondary-system-fill: rgb(120, 120, 128); + --tertiary-system-fill: rgb(118, 118, 128); + --quaternary-system-fill: rgb(116, 116, 128); + --system-background: rgb(255, 255, 255); + --secondary-system-background: rgb(242, 242, 247); + --tertiary-system-background: rgb(255, 255, 255); + --system-grouped-background: rgb(242, 242, 247); + --secondary-system-grouped-background: rgb(255, 255, 255); + --tertiary-system-grouped-background: rgb(242, 242, 247); +} + +@supports (color: color(display-p3 1 1 1)) { + :root { + --system-red: color(display-p3 1 0.2314 0.1882); + --system-orange: color(display-p3 1 0.5843 0); + --system-yellow: color(display-p3 1 0.8 0); + --system-green: color(display-p3 0.2039 0.7804 0.349); + --system-teal: color(display-p3 0.3529 0.7843 0.9804); + --system-blue: color(display-p3 0 0.4784 1); + --system-indigo: color(display-p3 0.3451 0.3373 0.8392); + --system-purple: color(display-p3 0.6863 0.3216 0.8706); + --system-pink: color(display-p3 1 0.1765 0.3333); + --system-gray: color(display-p3 0.5569 0.5569 0.5765); + --system-gray2: color(display-p3 0.6824 0.6824 0.698); + --system-gray3: color(display-p3 0.7804 0.7804 0.8); + --system-gray4: color(display-p3 0.8196 0.8196 0.8392); + --system-gray5: color(display-p3 0.898 0.898 0.9176); + --system-gray6: color(display-p3 0.949 0.949 0.9686); + + --label: color(display-p3 0 0 0); + --secondary-label: color(display-p3 0.2353 0.2353 0.2627); + --tertiary-label: color(display-p3 0.2823 0.2823 0.2901); + --quaternary-label: color(display-p3 0.4627 0.4627 0.5019); + --placeholder-text: color(display-p3 0.5568 0.5568 0.5764); + --link: color(display-p3 0 0.4784 1); + --separator: color(display-p3 0.898 0.898 0.9176); + --opaque-separator: color(display-p3 0.7765 0.7765 0.7843); + --system-fill: color(display-p3 0.4706 0.4706 0.502); + --secondary-system-fill: color(display-p3 0.4706 0.4706 0.502); + --tertiary-system-fill: color(display-p3 0.4627 0.4627 0.502); + --quaternary-system-fill: color(display-p3 0.4549 0.4549 0.502); + --system-background: color(display-p3 1 1 1); + --secondary-system-background: color(display-p3 0.949 0.949 0.9686); + --tertiary-system-background: color(display-p3 1 1 1); + --system-grouped-background: color(display-p3 0.949 0.949 0.9686); + --secondary-system-grouped-background: color(display-p3 1 1 1); + --tertiary-system-grouped-background: color( + display-p3 0.949 0.949 0.9686 + ); + } +} + +/* +@media (prefers-color-scheme: dark) { + :root { + --system-red: rgb(255, 69, 58); + --system-orange: rgb(255, 159, 10); + --system-yellow: rgb(255, 214, 10); + --system-green: rgb(48, 209, 88); + --system-teal: rgb(100, 210, 255); + --system-blue: rgb(10, 132, 255); + --system-indigo: rgb(94, 92, 230); + --system-purple: rgb(191, 90, 242); + --system-pink: rgb(255, 55, 95); + --system-gray: rgb(142, 142, 147); + --system-gray2: rgb(99, 99, 102); + --system-gray3: rgb(72, 72, 74); + --system-gray4: rgb(58, 58, 60); + --system-gray5: rgb(44, 44, 46); + --system-gray6: rgb(28, 28, 30); + + --label: rgb(255, 255, 255); + --secondary-label: rgb(235, 235, 245); + --tertiary-label: rgb(235, 235, 245); + --quaternary-label: rgb(235, 235, 245); + --placeholder-text: rgb(235, 235, 245); + --link: rgb(9, 132, 255); + --separator: rgb(44, 44, 46); + --opaque-separator: rgb(56, 56, 58); + --system-fill: rgb(120, 120, 128); + --secondary-system-fill: rgb(120, 120, 128); + --tertiary-system-fill: rgb(118, 118, 128); + --quaternary-system-fill: rgb(118, 118, 128); + --system-background: rgb(0, 0, 0); + --secondary-system-background: rgb(28, 28, 30); + --tertiary-system-background: rgb(44, 44, 46); + --system-grouped-background: rgb(0, 0, 0); + --secondary-system-grouped-background: rgb(28, 28, 30); + --tertiary-system-grouped-background: rgb(44, 44, 46); + } + + @supports (color: color(display-p3 1 1 1)) { + :root { + --system-red: color(display-p3 1 0.4118 0.3804); + --system-orange: color(display-p3 1 0.702 0.251); + --system-yellow: color(display-p3 1 0.8314 0.149); + --system-green: color(display-p3 0.1882 0.8588 0.3569); + --system-teal: color(display-p3 0.4392 0.8431 1); + --system-blue: color(display-p3 0.251 0.6118 1); + --system-indigo: color(display-p3 0.4902 0.4784 1); + --system-purple: color(display-p3 0.8549 0.5608 1); + --system-pink: color(display-p3 1 0.3922 0.5098); + --system-gray: color(display-p3 0.6824 0.6824 0.698); + --system-gray2: color(display-p3 0.4863 0.4863 0.502); + --system-gray3: color(display-p3 0.3294 0.3294 0.3373); + --system-gray4: color(display-p3 0.2667 0.2667 0.2745); + --system-gray5: color(display-p3 0.2118 0.2118 0.2196); + --system-gray6: color(display-p3 0.1412 0.1412 0.149); + + --label: color(display-p3 1 1 1); + --secondary-label: color(display-p3 0.9216 0.9216 0.9608); + --tertiary-label: color(display-p3 0.9216 0.9216 0.9608); + --quaternary-label: color(display-p3 0.9216 0.9216 0.9608); + --placeholder-text: color(display-p3 0.9216 0.9216 0.9608); + --link: color(display-p3 0.03529 0.5176 1); + --separator: color(display-p3 0.2118 0.2118 0.2196); + --opaque-separator: color(display-p3 0.2196 0.2196 0.2275); + --system-fill: color(display-p3 0.4706 0.4706 0.502); + --secondary-system-fill: color(display-p3 0.4706 0.4706 0.502); + --tertiary-system-fill: color(display-p3 0.4627 0.4627 0.502); + --quaternary-system-fill: color(display-p3 0.4627 0.4627 0.502); + --system-background: color(display-p3 0 0 0); + --secondary-system-background: color( + display-p3 0.1412 0.1412 0.149 + ); + --tertiary-system-background: color( + display-p3 0.2118 0.2118 0.2196 + ); + --system-grouped-background: color(display-p3 0 0 0); + --secondary-system-grouped-background: color( + display-p3 0.1412 0.1412 0.149 + ); + --tertiary-system-grouped-background: color( + display-p3 0.2118 0.2118 0.2196 + ); + } + } +} */ + +:root { + --large-title: 600 32pt / 39pt sans-serif; + --title-1: 500 26pt / 32pt sans-serif; + --title-2: 500 20pt / 25pt sans-serif; + --title-3: 500 18pt / 23pt sans-serif; + --headline: 500 15pt / 20pt sans-serif; + --body: 300 15pt / 20pt sans-serif; + --callout: 300 14pt / 19pt sans-serif; + --subhead: 300 13pt / 18pt sans-serif; + --footnote: 300 12pt / 16pt sans-serif; + --caption-1: 300 11pt / 13pt sans-serif; + --caption-2: 300 11pt / 13pt sans-serif; +} + +:root { + --icon-case: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%2389c5e6' height='90' rx='8' stroke='%236bb7e1' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m20.21 50c0-20.7 11.9-32.79 30.8-32.79 16 0 28.21 10.33 28.7 25.32h-15.52c-.79-7.53-6.1-12.42-13.19-12.42-8.79 0-14.37 7.52-14.37 19.82s5.54 20 14.41 20c7.08 0 12.22-4.66 13.23-12.09h15.52c-.74 15.07-12.43 25-28.78 25-19.01-.03-30.8-12.12-30.8-32.84z' fill='%23fff'/%3E%3C/svg%3E%0A"); + --icon-class: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%239b98e6' height='90' rx='8' stroke='%235856d6' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m20.21 50c0-20.7 11.9-32.79 30.8-32.79 16 0 28.21 10.33 28.7 25.32h-15.52c-.79-7.53-6.1-12.42-13.19-12.42-8.79 0-14.37 7.52-14.37 19.82s5.54 20 14.41 20c7.08 0 12.22-4.66 13.23-12.09h15.52c-.74 15.07-12.43 25-28.78 25-19.01-.03-30.8-12.12-30.8-32.84z' fill='%23fff'/%3E%3C/svg%3E"); + --icon-enumeration: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%23eca95b' height='90' rx='8' stroke='%23e89234' stroke-miterlimit='10' stroke-width='4' width='90' x='5.17' y='5'/%3E%3Cpath d='m71.9 81.71h-43.47v-63.42h43.47v13h-27.34v12.62h25.71v11.87h-25.71v12.92h27.34z' fill='%23fff'/%3E%3C/svg%3E%0A"); + --icon-extension: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%23eca95b' height='90' rx='8' stroke='%23e89234' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cg fill='%23fff'%3E%3Cpath d='m54.43 81.93h-33.92v-63.86h33.92v12.26h-21.82v13.8h20.45v11.32h-20.45v14.22h21.82z'/%3E%3Cpath d='m68.74 74.58h-.27l-2.78 7.35h-7.28l5.59-12.61-6-12.54h8l2.74 7.3h.27l2.76-7.3h7.64l-6.14 12.54 5.89 12.61h-7.64z'/%3E%3C/g%3E%3C/svg%3E%0A"); + --icon-function: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%237ac673' height='90' rx='8' stroke='%235bb74f' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m24.25 75.66a5.47 5.47 0 0 1 5.75-5.73c1.55 0 3.55.41 6.46.41 3.19 0 4.78-1.55 5.46-6.65l1.5-10.14h-9.34a6 6 0 1 1 0-12h11.1l1.09-7.27c1.55-10.89 8.01-16.58 17.73-16.58 6.69 0 11.74 1.77 11.74 6.64a5.47 5.47 0 0 1 -5.74 5.73c-1.55 0-3.55-.41-6.46-.41-3.14 0-4.73 1.51-5.46 6.65l-.78 5.27h11.44a6 6 0 1 1 .05 12h-13.19l-1.78 12.11c-1.59 10.92-8.1 16.61-17.82 16.61-6.7 0-11.75-1.77-11.75-6.64z' fill='%23fff'/%3E%3C/svg%3E%0A"); + --icon-method: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%235a98f8' height='90' rx='8' stroke='%232974ed' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m70.61 81.71v-39.6h-.31l-15.69 39.6h-9.22l-15.65-39.6h-.35v39.6h-14.19v-63.42h18.63l16 41.44h.36l16-41.44h18.61v63.42z' fill='%23fff'/%3E%3C/svg%3E%0A"); + --icon-property: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%2389c5e6' height='90' rx='8' stroke='%236bb7e1' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m52.31 18.29c13.62 0 22.85 8.84 22.85 22.46s-9.71 22.37-23.82 22.37h-10.34v18.59h-16.16v-63.42zm-11.31 32.71h7c6.85 0 10.89-3.56 10.89-10.2s-4.08-10.16-10.89-10.16h-7z' fill='%23fff'/%3E%3C/svg%3E%0A"); + --icon-protocol: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%23ff6682' height='90' rx='8' stroke='%23ff2d55' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cg fill='%23fff'%3E%3Cpath d='m46.28 18.29c11.84 0 20 8.66 20 21.71s-8.44 21.71-20.6 21.71h-10.81v20h-12.09v-63.42zm-11.41 33.05h8.13c6.93 0 11-4 11-11.29s-4-11.25-10.93-11.25h-8.2z'/%3E%3Cpath d='m62 57.45h8v4.77h.16c.84-3.45 2.54-5.12 5.17-5.12a5.06 5.06 0 0 1 1.92.35v7.55a5.69 5.69 0 0 0 -2.39-.51c-3.08 0-4.66 1.74-4.66 5.12v12.1h-8.2z'/%3E%3C/g%3E%3C/svg%3E%0A"); + --icon-structure: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%23b57edf' height='90' rx='8' stroke='%239454c2' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m38.38 63c.74 4.53 5.62 7.16 11.82 7.16s10.37-2.81 10.37-6.68c0-3.51-2.73-5.31-10.24-6.76l-6.5-1.23c-12.66-2.35-19.21-8.49-19.21-18.21 0-12.22 10.59-20.09 25.18-20.09 16 0 25.36 7.83 25.53 19.91h-15c-.26-4.57-4.57-7.29-10.42-7.29s-9.31 2.63-9.31 6.37c0 3.34 2.9 5.18 9.8 6.5l6.5 1.23c13.56 2.6 19.71 8.09 19.71 18.09 0 12.74-10 20.83-26.72 20.83-15.82 0-26.28-7.3-26.5-19.78z' fill='%23fff'/%3E%3C/svg%3E%0A"); + --icon-typealias: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%237ac673' height='90' rx='8' stroke='%235bb74f' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m42 81.71v-50.41h-17.53v-13h51.06v13h-17.53v50.41z' fill='%23fff'/%3E%3C/svg%3E%0A"); + --icon-variable: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%237ac673' height='90' rx='8' stroke='%235bb74f' stroke-miterlimit='10' stroke-width='4' width='90' x='5' y='5'/%3E%3Cpath d='m39.85 81.71-20.22-63.42h18.37l12.18 47.64h.35l12.17-47.64h17.67l-20.22 63.42z' fill='%23fff'/%3E%3C/svg%3E%0A"); +} + +/************/ + +body, +input, +textarea, +select, +button { + font-synthesis: none; + -moz-font-feature-settings: "kern"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + direction: ltr; + text-align: left; +} + +h1:first-of-type, +h2:first-of-type, +h3:first-of-type, +h4:first-of-type, +h5:first-of-type, +h6:first-of-type { + margin-top: 0; +} + +h1 code, +h2 code, +h3 code, +h4 code, +h5 code, +h6 code { + font-family: inherit; + font-weight: inherit; +} + +h1 img, +h2 img, +h3 img, +h4 img, +h5 img, +h6 img { + margin: 0 0.5em 0.2em 0; + vertical-align: middle; + display: inline-block; +} + +img + h1 { + margin-top: 0.5em; +} + +img + h1, +img + h2, +img + h3, +img + h4, +img + h5, +img + h6 { + margin-top: 0.3em; +} + +h1 + *, +h2 + *, +h3 + *, +h4 + *, +h5 + *, +h6 + * { + margin-top: 0.8em; +} + +:is(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3, h4, h5, h6) { + margin-top: 0.4em; +} + +:matches(h1, h2, h3, h4, h5, h6) + :matches(h1, h2, h3, h4, h5, h6) { + margin-top: 0.4em; +} + +:is(p, ul, ol) + :is(h1, h2, h3, h4, h5, h6) { + margin-top: 1.6em; +} + +:matches(p, ul, ol) + :matches(h1, h2, h3, h4, h5, h6) { + margin-top: 1.6em; +} + +:is(p, ul, ol) + * { + margin-top: 0.8em; +} + +:matches(p, ul, ol) + * { + margin-top: 0.8em; +} + +ul, +ol { + margin-left: 1.17647em; +} + +:matches(ul, ol) :matches(ul, ol) { + margin-top: 0; + margin-bottom: 0; +} + +nav h2 { + color: var(--secondary-label); + text-transform: uppercase; + font-variant: small-caps; + font-weight: 600; + font-size: 1rem; +} + +nav ul, +nav ol { + margin: 0; + list-style: none; +} + +nav li li { + font-size: smaller; +} + +a:link, +a:visited { + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a:active { + text-decoration: none; +} + +p + a { + display: inline-block; +} + +b, +strong { + font-weight: 600; +} + +.summary, +.discussion { + font: var(--callout); +} + +article > .discussion { + margin-bottom: 2em; +} + +.discussion .highlight { + font: var(--caption-1); + background: transparent; + border: 1px var(--separator) solid; +} + +em, +i, +cite, +dfn { + font-style: italic; +} + +sup { + font-size: 0.6em; + vertical-align: top; + position: relative; + bottom: -0.2em; +} + +:matches(h1, h2, h3) sup { + font-size: 0.4em; +} + +sup a { + vertical-align: inherit; + color: inherit; +} + +sup a:hover { + color: var(--link); + text-decoration: none; +} + +sub { + line-height: 1; +} + +abbr { + border: 0; +} + +:lang(ja), +:lang(ko), +:lang(th), +:lang(zh) { + font-style: normal; +} + +:lang(ko) { + word-break: keep-all; +} + +form fieldset { + width: 95%; + margin: 1em auto; + max-width: 450px; +} + +form label { + position: relative; + display: block; + margin-bottom: 14px; + width: 100%; + font-size: 1em; + font-weight: 400; + line-height: 1.5em; +} + +input[type="text"], +input[type="email"], +input[type="number"], +input[type="password"], +input[type="tel"], +input[type="url"], +textarea { + margin: 0; + width: 100%; + height: 34px; + font-family: inherit; + font-size: 100%; + font-weight: 400; + border: 1px solid var(--separator); + border-radius: 4px; + padding: 0 1em 0; + position: relative; + z-index: 1; + color: #333333; + vertical-align: top; +} + +input[type="text"], +input[type="text"]:focus, +input[type="email"], +input[type="email"]:focus, +input[type="number"], +input[type="number"]:focus, +input[type="password"], +input[type="password"]:focus, +input[type="tel"], +input[type="tel"]:focus, +input[type="url"], +input[type="url"]:focus, +textarea, +textarea:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +input[type="text"]:focus, +input[type="email"]:focus, +input[type="number"]:focus, +input[type="password"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +textarea:focus { + border-color: #0088cc; + outline: 0; + -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + z-index: 9; +} + +input[type="text"]:-moz-read-only, +input[type="text"]:-moz-read-only, +input[type="email"]:-moz-read-only, +input[type="email"]:-moz-read-only, +input[type="number"]:-moz-read-only, +input[type="number"]:-moz-read-only, +input[type="password"]:-moz-read-only, +input[type="password"]:-moz-read-only, +input[type="tel"]:-moz-read-only, +input[type="tel"]:-moz-read-only, +input[type="url"]:-moz-read-only, +input[type="url"]:-moz-read-only { + background: none; + border: none; + box-shadow: none; + padding-left: 0; +} + +input[type="text"]:-moz-read-only, +input[type="text"]:read-only, +input[type="email"]:-moz-read-only, +input[type="email"]:read-only, +input[type="number"]:-moz-read-only, +input[type="number"]:read-only, +input[type="password"]:-moz-read-only, +input[type="password"]:read-only, +input[type="tel"]:-moz-read-only, +input[type="tel"]:read-only, +input[type="url"]:-moz-read-only, +input[type="url"]:read-only { + background: none; + border: none; + box-shadow: none; + padding-left: 0; +} + +::-webkit-input-placeholder, +:-moz-placeholder, +::-moz-placeholder, +:-ms-input-placeholder { + color: var(--placeholder-text); +} + +textarea { + min-height: 134px; + line-height: 1.4737; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + resize: vertical; +} +textarea, +textarea:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +select { + background: transparent; + width: 100%; + height: 34px; + padding: 0 1em; + font-size: 1em; + font-family: inherit; + border-radius: 4px; + border: none; + margin: 0; + cursor: pointer; +} +select, +select:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +select:focus { + border-color: #0088cc; + outline: 0; + -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + z-index: 9; +} + +input[type="file"] { + margin: 0; + font-family: inherit; + font-size: 100%; + background: #fafafa; + width: 100%; + height: 34px; + border-radius: 4px; + padding: 6px 1em; + position: relative; + z-index: 1; + color: #333333; + vertical-align: top; + cursor: pointer; +} + +input[type="file"]:focus { + border-color: #0088cc; + outline: 0; + -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); + z-index: 9; +} +input[type="file"]:focus, +input[type="file"]:focus:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +button, +button:focus, +input[type="reset"], +input[type="reset"]:focus, +input[type="submit"], +input[type="submit"]:focus { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; +} + +:matches(button, input[type="reset"], input[type="submit"]) { + background-color: #e3e3e3; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(white), + to(#e3e3e3) + ); + background: linear-gradient(white, #e3e3e3); + border-color: #d6d6d6; + color: #0070c9; +} +:matches(button, input[type="reset"], input[type="submit"]):hover { + background-color: #eeeeee; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(white), + to(#eeeeee) + ); + background: linear-gradient(white, #eeeeee); + border-color: #d9d9d9; +} +:matches(button, input[type="reset"], input[type="submit"]):active { + background-color: gainsboro; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(#f7f7f7), + to(gainsboro) + ); + background: linear-gradient(#f7f7f7, gainsboro); + border-color: #d0d0d0; +} +:matches(button, input[type="reset"], input[type="submit"]):disabled { + background-color: #e3e3e3; + background: -webkit-gradient( + linear, + left top, + left bottom, + from(white), + to(#e3e3e3) + ); + background: linear-gradient(white, #e3e3e3); + border-color: #d6d6d6; + color: #0070c9; +} + +/* */ + +body { + background: var(--system-grouped-background); + font: var(--body); + font-family: ui-system, -apple-system, BlinkMacSystemFont, sans-serif; + color: var(--label); +} + +h1 { + font: var(--large-title); +} + +h2 { + font: var(--title-2); +} + +h3 { + font: var(--title-3); +} + +h4, +h5, +h6 { + font: var(--headline); +} + +/* strong, + th, + dt { + font: var(--headline); + } */ + +a { + color: var(--link); +} + +label { + font: var(--callout); +} + +label, +input { + display: block; +} + +input { + margin-bottom: 1em; +} + +/* button, + input[type="submit"] { + color: var(--link); + background: transparent; + border: none; + padding: 0.5em; + } */ + +/*********************/ + +hr { + border: none; + border-top: 1px var(--separator) solid; + margin: 1em 0; +} + +table { + width: 100%; + font: var(--caption-1); + caption-side: bottom; + margin-bottom: 2em; +} + +th, +td { + padding: 0 1em; +} + +th { + font-weight: 600; + text-align: left; +} + +thead th { + border-bottom: 1px var(--separator) solid; +} + +tr:last-of-type td, +tr:last-of-type th { + border-bottom: none; +} + +th, +td { + border-bottom: 1px var(--separator) solid; + color: var(--secondary-label); +} + +caption { + text-align: left; + margin-top: 2em; + font: var(--caption-2); + color: var(--tertiary-label); +} + +code, +.graph text { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + font-weight: 300; +} + +.graph > polygon { + display: none; +} + +.graph text { + fill: currentColor !important; +} + +.graph path, +.graph ellipse, +.graph rect, +.graph polygon { + stroke: currentColor !important; +} + +body { + width: 90vw; + max-width: 1280px; + margin: 1em auto; + /* display: grid; + grid-template-areas: + "header search" + "main nav" + "footer footer"; + grid-template-columns: 4fr 2fr; + gap: 1em; */ +} + +body > header { + font: var(--title-1); +} + +body > header a { + color: var(--label); +} + +@media screen and (max-width: 768px) { + body { + width: 96vw; + max-width: 100%; + } + + body > header { + font: var(--title-1); + text-align: center; + } + + body > nav { + display: none; + } + + body > main { + padding: 0 1em; + } +} + +body > header { + grid-area: header; + padding: 0.5em 0; +} + +main, +nav { + overflow-x: scroll; +} + +main { + background: var(--system-background); + border-radius: 8px; +} + +form.search { +} + +body > footer { + clear: both; + padding: 1em 0; + font: var(--caption-1); + color: var(--secondary-label); +} + +main { + padding: 0 2em; + /* max-width: 66ch; */ +} + +nav { + width: 20vw; + float: right; + overflow: scroll; + padding: 0 1em 3em 1em; + margin-left: 1em; + position: sticky; + top: 1em; + max-height: 100vh; +} + +nav a { + color: var(--secondary-label); +} + +nav ul a { + color: var(--tertiary-label); +} + +nav ol { + padding: 0; +} + +nav ul { + padding: 0; + margin-bottom: 1em; + font: var(--callout); +} + +nav ol > li > a { + font: var(--headline); + font-size: smaller; + display: block; + margin: 0.5em 0; +} + +nav li { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +main section { + margin-bottom: 2em; + padding-bottom: 1em; + border-bottom: 1px var(--separator) solid; +} + +main section:last-of-type { + margin-bottom: 0; + border-bottom: none; +} + +/* main :matches(h1, h2, h3) { + position: sticky; + top: 0px; +} + +main h1 { + z-index: 1; +} + +main h2 { + z-index: 2; +} + +main h3 { + z-index: 3; +} */ + +blockquote { + border-left: 4px var(--separator) solid; + padding-left: 2em; + margin-left: 0; + font-size: smaller; + color: var(--secondary-label); + --link: var(--secondary-label); +} + +blockquote a { + text-decoration: underline; +} + +article { + padding: 2em 0 1em 0; +} + +article > .summary { + margin-bottom: 2em; + padding-bottom: 1em; + border-bottom: 1px var(--separator) solid; +} + +article > .summary:last-child { + border-bottom: none; +} + +.parameters th { + text-align: right; +} + +.parameters td { + color: var(--secondary-label); +} + +.parameters th + td { + text-align: center; +} + +dl { + padding-bottom: 1em; +} + +dt { + font: var(--headline); +} + +dd { + margin-left: 2em; + margin-bottom: 1em; +} + +dd p { + margin-top: 0; +} + +.highlight { + background: var(--secondary-system-background); + border-radius: 8px; + font-size: smaller; + overflow-x: scroll; + white-space: pre-line; + padding: 1em; + padding-left: 3em; + text-indent: -2em; + margin-bottom: 2em; +} + +.highlight .p { + white-space: nowrap; +} + +.highlight .placeholder { + color: var(--label); +} + +.highlight a { + text-decoration: underline; + color: var(--placeholder-text); +} + +.highlight .literal, +.highlight .keyword, +.highlight .attribute { + color: var(--system-purple); +} + +.highlight .number { + color: var(--system-blue); +} + +.highlight .declaration { + color: var(--system-teal); +} + +.highlight .type { + color: var(--system-indigo); +} + +.highlight .directive { + color: var(--system-orange); +} + +.highlight .comment { + color: var(--system-gray); +} + +main summary:hover { + text-decoration: underline; +} + +figure { + margin: 2em 0; + padding: 1em 0; +} + +figure svg { + max-width: 100%; + height: auto !important; + margin: 0 auto; + display: block; +} + +h1 small { + font-size: 0.5em; + line-height: 1.5; + display: block; + font-weight: normal; + color: var(--quaternary-label); +} + +p code, +dd code, +li code { + font-size: smaller; + color: var(--secondary-label); +} + +a code { + text-decoration: underline; +} + +section > [role="article"][class], +nav li[class], +dl dt[class] { + background-image: var(--background-image); + background-size: 1em; + background-repeat: no-repeat; + background-position: left 0.25em; + padding-left: 3em; +} + +dl dt[class] { + background-position-y: 0.125em; +} + +section > [role="article"] { + margin-bottom: 1em; + border-bottom: 1px var(--separator) solid; + padding-left: 2em !important; +} + +section > [role="article"]:last-of-type { + border-bottom: none; + margin-bottom: 0; +} + +nav li[class], +dl dt[class] { + list-style: none; + text-indent: -1em; + margin-bottom: 0.5em; +} + +nav li[class] { + padding-left: 2.5em; +} + +.case, +.enumeration_case { + --background-image: var(--icon-case); + --link: var(--system-teal); +} + +.class { + --background-image: var(--icon-class); + --link: var(--system-indigo); +} + +.enumeration { + --background-image: var(--icon-enumeration); + --link: var(--system-orange); +} + +.extension { + --background-image: var(--icon-extension); + --link: var(--system-orange); +} + +.function { + --background-image: var(--icon-function); + --link: var(--system-green); +} + +.method, +.initializer { + --background-image: var(--icon-method); + --link: var(--system-blue); +} + +.property { + --background-image: var(--icon-property); + --link: var(--system-teal); +} + +.protocol { + --background-image: var(--icon-protocol); + --link: var(--system-pink); +} + +.structure { + --background-image: var(--icon-structure); + --link: var(--system-purple); +} + +.typealias { + --background-image: var(--icon-typealias); + --link: var(--system-green); +} + +.variable { + --background-image: var(--icon-variable); + --link: var(--system-green); +} + +.unknown { + --link: var(--quaternary-label); + color: var(--link); +} + +"""# diff --git a/Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift b/Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift deleted file mode 100644 index aeae24e5..00000000 --- a/Sources/swift-doc/Supporting Types/Components/ConformingTypes.swift +++ /dev/null @@ -1,48 +0,0 @@ -import CommonMarkBuilder -import SwiftDoc -import SwiftMarkup -import SwiftSemantics -import HypertextLiteral - -struct ConformingTypes: Component { - var symbol: Symbol - var module: Module - - init(to symbol: Symbol, in module: Module) { - precondition(symbol.api is Protocol) - self.symbol = symbol - self.module = module - } - - // MARK: - Component - - var fragment: Fragment { - guard symbol.api is Protocol else { return Fragment { "" } } - let conformingTypes = module.interface.typesConforming(to: symbol) - guard !conformingTypes.isEmpty else { return Fragment { "" }} - - return Fragment { - Section { - Heading { "Conforming Types" } - - Fragment { - #""" - \#(conformingTypes.map { type in - if type.api is Unknown { - return "`\(type.id)`" - } else { - return "[`\(type.id)`](\(path(for: type)))" - } - }.joined(separator: ", ")) - """# - } - } - } - } - - var html: HypertextLiteral.HTML { - return #""" - - """# - } -} diff --git a/Sources/swift-doc/Supporting Types/Components/Documentation.swift b/Sources/swift-doc/Supporting Types/Components/Documentation.swift index 6a8fcf82..f16cea61 100644 --- a/Sources/swift-doc/Supporting Types/Components/Documentation.swift +++ b/Sources/swift-doc/Supporting Types/Components/Documentation.swift @@ -1,5 +1,6 @@ import Foundation import SwiftDoc +import SwiftSemantics import SwiftMarkup import CommonMarkBuilder import HypertextLiteral @@ -126,17 +127,52 @@ struct Documentation: Component { } if !documentation.parameters.isEmpty { + let typedParameters: [(name: String, type: String?, description: String)] = documentation.parameters.map { entry in + let type: String? + switch symbol.api { + case let function as Function: + type = function.signature.input.first(where: { $0.firstName == entry.name || $0.secondName == entry.name })?.type + case let initializer as Initializer: + type = initializer.parameters.first(where: { $0.firstName == entry.name || $0.secondName == entry.name })?.type + case let `subscript` as Subscript: + type = `subscript`.indices.first(where: { $0.firstName == entry.name || $0.secondName == entry.name })?.type + default: + type = nil + } + + return (entry.name, type, entry.description) + } + fragments.append(#"""

Parameters

-
- \#(documentation.parameters.map { parameter in - #""" -
\#(parameter.name)
-
\#(commonmark: parameter.description)
- """# as HypertextLiteral.HTML - }) -
+ + + + + + + + + + \#(typedParameters.map { entry -> HypertextLiteral.HTML in + let typeCell: HypertextLiteral.HTML + if let type = entry.type { + typeCell = #""# as HypertextLiteral.HTML + } else { + typeCell = "" as HypertextLiteral.HTML + } + + return #""" + + + \#(typeCell) + + + """# as HypertextLiteral.HTML + }) + +
\#(softbreak(type))
\#(softbreak(entry.name))\#(commonmark: entry.description)
"""# as HypertextLiteral.HTML) } diff --git a/Sources/swift-doc/Supporting Types/Components/Inheritance.swift b/Sources/swift-doc/Supporting Types/Components/Inheritance.swift deleted file mode 100644 index 7bb288a6..00000000 --- a/Sources/swift-doc/Supporting Types/Components/Inheritance.swift +++ /dev/null @@ -1,79 +0,0 @@ -import CommonMarkBuilder -import SwiftDoc -import SwiftMarkup -import SwiftSemantics -import Foundation -import HypertextLiteral -import GraphViz -import DOT - -extension StringBuilder { - // MARK: buildIf - - public static func buildIf(_ string: String?) -> String { - return string ?? "" - } - - // MARK: buildEither - - public static func buildEither(first: String) -> String { - return first - } - - public static func buildEither(second: String) -> String { - return second - } -} - -struct Inheritance: Component { - var module: Module - var symbol: Symbol - var inheritedTypes: [Symbol] - - init(of symbol: Symbol, in module: Module) { - self.module = module - self.symbol = symbol - self.inheritedTypes = module.interface.typesInherited(by: symbol) + module.interface.typesConformed(by: symbol) - } - - // MARK: - Component - - var fragment: Fragment { - guard !inheritedTypes.isEmpty else { return Fragment { "" } } - - return Fragment { - Section { - Heading { "Inheritance" } - - Fragment { - #""" - \#(inheritedTypes.map { type in - if type.api is Unknown { - return "`\(type.id)`" - } else { - return "[`\(type.id)`](\(path(for: type)))" - } - }.joined(separator: ", ")) - """# - } - } - } - } - - var html: HypertextLiteral.HTML { - let graph = symbol.graph(in: module) - guard !graph.edges.isEmpty else { return "" } - - let svg = try! HTML(String(data: graph.render(using: .dot, to: .svg), encoding: .utf8) ?? "") - - return #""" -
-
- \#(svg) - - -
-
- """# - } -} diff --git a/Sources/swift-doc/Supporting Types/Components/Members.swift b/Sources/swift-doc/Supporting Types/Components/Members.swift index acd859c4..9692433e 100644 --- a/Sources/swift-doc/Supporting Types/Components/Members.swift +++ b/Sources/swift-doc/Supporting Types/Components/Members.swift @@ -81,19 +81,22 @@ struct Members: Component { return #""" \#(sections.map { section -> HypertextLiteral.HTML in #""" -
-

\#(section.title)

- \#(section.members.map { member -> HypertextLiteral.HTML in - #""" -
- - \#(softbreak(member.name)) - - \#(Documentation(for: member, in: module).html) -
- """# - }) -
+
+

\#(section.title)

+ + \#(section.members.map { member -> HypertextLiteral.HTML in + let descriptor = String(describing: type(of: symbol.api)).lowercased() + + return #""" +
+

+ \#(softbreak(member.name)) +

+ \#(Documentation(for: member, in: module).html) +
+ """# + }) +
"""# }) @@ -105,7 +108,7 @@ struct Members: Component { \#(genericallyConstrainedMembers.map { (requirements, members) -> HypertextLiteral.HTML in #"""
-

where \#(requirements.map { $0.description }.joined(separator: ", "))

+

where \#(requirements.map { softbreak($0.description) }.joined(separator: ", "))

\#(members.map { member -> HypertextLiteral.HTML in #"""

\#(softbreak(member.name))

diff --git a/Sources/swift-doc/Supporting Types/Components/NestedTypes.swift b/Sources/swift-doc/Supporting Types/Components/NestedTypes.swift deleted file mode 100644 index 572b7e46..00000000 --- a/Sources/swift-doc/Supporting Types/Components/NestedTypes.swift +++ /dev/null @@ -1,49 +0,0 @@ -import CommonMarkBuilder -import SwiftDoc -import SwiftMarkup -import SwiftSemantics -import HypertextLiteral - -struct NestedTypes: Component { - var symbol: Symbol - var module: Module - - var nestedTypes: [Symbol] - - init(of symbol: Symbol, in module: Module) { - precondition(symbol.api is Type) - self.symbol = symbol - self.module = module - self.nestedTypes = module.interface.members(of: symbol).filter { $0.api is Type } - } - - // MARK: - Component - - var fragment: Fragment { - guard !nestedTypes.isEmpty else { return Fragment { "" }} - - return Fragment { - Section { - Heading { "Nested Types" } - - Fragment { - #""" - \#(nestedTypes.map { type in - if type.api is Unknown { - return "`\(type.id)`" - } else { - return "[`\(type.id)`](\(path(for: type.id)))" - } - }.joined(separator: ", ")) - """# - } - } - } - } - - var html: HypertextLiteral.HTML { - return #""" - - """# - } -} diff --git a/Sources/swift-doc/Supporting Types/Components/Relationships.swift b/Sources/swift-doc/Supporting Types/Components/Relationships.swift new file mode 100644 index 00000000..9615dc49 --- /dev/null +++ b/Sources/swift-doc/Supporting Types/Components/Relationships.swift @@ -0,0 +1,127 @@ +import CommonMarkBuilder +import SwiftDoc +import SwiftMarkup +import SwiftSemantics +import Foundation +import HypertextLiteral +import GraphViz +import DOT + +extension StringBuilder { + // MARK: buildIf + + public static func buildIf(_ string: String?) -> String { + return string ?? "" + } + + // MARK: buildEither + + public static func buildEither(first: String) -> String { + return first + } + + public static func buildEither(second: String) -> String { + return second + } +} + +struct Relationships: Component { + var module: Module + var symbol: Symbol + var inheritedTypes: [Symbol] + + init(of symbol: Symbol, in module: Module) { + self.module = module + self.symbol = symbol + self.inheritedTypes = module.interface.typesInherited(by: symbol) + module.interface.typesConformed(by: symbol) + } + + + var sections: [(title: String, symbols: [Symbol])] { + return [ + ("Member Of", [module.interface.relationshipsBySubject[symbol.id]?.filter { $0.predicate == .memberOf }.first?.object].compactMap { $0 }), + ("Nested Types", module.interface.members(of: symbol).filter { $0.api is Type }), + ("Superclass", module.interface.typesInherited(by: symbol)), + ("Subclasses", module.interface.typesInheriting(from: symbol)), + ("Conforms To", module.interface.typesConformed(by: symbol)), + ("Types Conforming to \(softbreak(symbol.id.description))", module.interface.typesConforming(to: symbol)), + ].filter { !$0.symbols.isEmpty } + } + + // MARK: - Component + + var fragment: Fragment { + guard !inheritedTypes.isEmpty else { return Fragment { "" } } + + return Fragment { + Section { + Heading { "Inheritance" } + + Fragment { + #""" + \#(inheritedTypes.map { type in + if type.api is Unknown { + return "`\(type.id)`" + } else { + return "[`\(type.id)`](\(path(for: type)))" + } + }.joined(separator: ", ")) + """# + } + } + } + } + + var html: HypertextLiteral.HTML { + var graph = symbol.graph(in: module) + guard !graph.edges.isEmpty else { return "" } + + graph.aspectRatio = 0.125 + graph.center = true + graph.overlap = "compress" + + let algorithm: LayoutAlgorithm = graph.nodes.count > 3 ? .neato : .dot + var svg: HypertextLiteral.HTML? + + do { + svg = try HypertextLiteral.HTML(String(data: graph.render(using: algorithm, to: .svg), encoding: .utf8) ?? "") + } catch { + print(error) + } + + return #""" +
+ +
+ \#(svg ?? "") + + +
+ \#(sections.compactMap { (heading, symbols) -> HypertextLiteral.HTML? in + guard !symbols.isEmpty else { return nil } + + let partitioned = symbols.filter { !($0.api is Unknown) } + symbols.filter { ($0.api is Unknown) } + + return #""" +

\#(unsafeUnescaped: heading)

+
+ \#(partitioned.map { symbol -> HypertextLiteral.HTML in + let descriptor = String(describing: type(of: symbol.api)).lowercased() + if symbol.api is Unknown { + return #""" +
\#(symbol.id)
+ """# + } else { + return #""" +
\#(symbol.id)
+
\#(commonmark: symbol.documentation?.summary ?? "")
+ """# + } + }) +
+ """# + }) +
+ """# + } +} diff --git a/Sources/swift-doc/Supporting Types/Helpers.swift b/Sources/swift-doc/Supporting Types/Helpers.swift index a95ff115..92962c96 100644 --- a/Sources/swift-doc/Supporting Types/Helpers.swift +++ b/Sources/swift-doc/Supporting Types/Helpers.swift @@ -2,19 +2,14 @@ import Foundation import SwiftDoc import HTML -fileprivate let regex = try! NSRegularExpression(pattern: #"(?:([a-z]{2,})([A-Z]+))"#, options: []) - public func linkCodeElements(of html: String, for symbol: Symbol, in module: Module) -> String { let document = try! Document(string: html.description)! for element in document.search(xpath: "//code | //pre/code//span[contains(@class,'type')]") { guard let name = element.content else { continue } -// let nameWithSoftBreaks = regex.stringByReplacingMatches(in: name, options: [], range: NSRange(name.startIndex.. String { let toc = Element(name: "ol") let document = try! Document(string: html.description)! - for h2 in document.search(xpath: "//h2") { + for h2 in document.search(xpath: "//section/h2") { guard let section = h2.parent as? Element else { continue } let li = Element(name: "li") @@ -50,13 +45,17 @@ public func sidebar(for html: String) -> String { break } - let a = Element(name: "a") - a["href"] = "#\(section["id"]!)" - a.content = h2.text + if let id = section["id"] { + let a = Element(name: "a") + a["href"] = "#\(id)" + a.content = h2.text + li.insert(child: a) + } else { + li.content = h2.text + } - li.insert(child: a) - let nestedItems = section.search(xpath: "./details/summary").compactMap { summary -> Element? in + let nestedItems = section.search(xpath: "./h3 | ./div/h3").compactMap { summary -> Element? in guard let article = summary.parent as? Element else { return nil } let li = Element(name: "li") @@ -86,7 +85,12 @@ public func sidebar(for html: String) -> String { return toc.description } +fileprivate let pattern = #"(?:([a-z]{2,})([A-Z]+))"# +fileprivate let regex = try! NSRegularExpression(pattern: pattern, options: []) + public func softbreak(_ string: String) -> String { - return string.replacingOccurrences(of: ".", with: ".\u{200B}") - .replacingOccurrences(of: ":", with: ":\u{200B}") + let string = string.replacingOccurrences(of: ".", with: ".\u{200B}") + .replacingOccurrences(of: ":", with: ":\u{200B}") + + return regex.stringByReplacingMatches(in: string, options: [], range: NSRange(string.startIndex.. HTML { let html = page.html @@ -14,7 +15,7 @@ func layout(_ page: Page) -> HTML { \#(unsafeUnescaped: css) - +
\#(page.module.name) @@ -27,15 +28,12 @@ func layout(_ page: Page) -> HTML { --> - \#(page is HomePage ? "" : - #""" -
@@ -51,1096 +49,3 @@ func layout(_ page: Page) -> HTML { """# } - -let css = #""" -:root { - --system-red: rgb(255, 59, 48); - --system-orange: rgb(255, 149, 0); - --system-yellow: rgb(255, 204, 0); - --system-green: rgb(52, 199, 89); - --system-teal: rgb(90, 200, 250); - --system-blue: rgb(0, 122, 255); - --system-indigo: rgb(88, 86, 214); - --system-purple: rgb(175, 82, 222); - --system-pink: rgb(255, 45, 85); - --system-gray: rgb(142, 142, 147); - --system-gray2: rgb(174, 174, 178); - --system-gray3: rgb(199, 199, 204); - --system-gray4: rgb(209, 209, 214); - --system-gray5: rgb(229, 229, 234); - --system-gray6: rgb(242, 242, 247); - - --label: rgb(0, 0, 0); - --secondary-label: rgb(60, 60, 67); - --tertiary-label: rgb(60, 60, 67); - --quaternary-label: rgb(60, 60, 67); - --placeholder-text: rgb(60, 60, 67); - --link: rgb(0, 122, 255); - --separator: rgb(60, 60, 67); - --opaque-separator: rgb(198, 198, 200); - --system-fill: rgb(120, 120, 128); - --secondary-system-fill: rgb(120, 120, 128); - --tertiary-system-fill: rgb(118, 118, 128); - --quaternary-system-fill: rgb(116, 116, 128); - --system-background: rgb(255, 255, 255); - --secondary-system-background: rgb(242, 242, 247); - --tertiary-system-background: rgb(255, 255, 255); - --system-grouped-background: rgb(242, 242, 247); - --secondary-system-grouped-background: rgb(255, 255, 255); - --tertiary-system-grouped-background: rgb(242, 242, 247); -} - -@supports (color: color(display-p3 1 1 1)) { - :root { - --system-red: color(display-p3 1 0.2314 0.1882); - --system-orange: color(display-p3 1 0.5843 0); - --system-yellow: color(display-p3 1 0.8 0); - --system-green: color(display-p3 0.2039 0.7804 0.349); - --system-teal: color(display-p3 0.3529 0.7843 0.9804); - --system-blue: color(display-p3 0 0.4784 1); - --system-indigo: color(display-p3 0.3451 0.3373 0.8392); - --system-purple: color(display-p3 0.6863 0.3216 0.8706); - --system-pink: color(display-p3 1 0.1765 0.3333); - --system-gray: color(display-p3 0.5569 0.5569 0.5765); - --system-gray2: color(display-p3 0.6824 0.6824 0.698); - --system-gray3: color(display-p3 0.7804 0.7804 0.8); - --system-gray4: color(display-p3 0.8196 0.8196 0.8392); - --system-gray5: color(display-p3 0.898 0.898 0.9176); - --system-gray6: color(display-p3 0.949 0.949 0.9686); - - --label: color(display-p3 0 0 0); - --secondary-label: color(display-p3 0.2353 0.2353 0.2627); - --tertiary-label: color(display-p3 0.2353 0.2353 0.2627); - --quaternary-label: color(display-p3 0.2353 0.2353 0.2627); - --placeholder-text: color(display-p3 0.2353 0.2353 0.2627); - --link: color(display-p3 0 0.4784 1); - --separator: color(display-p3 0.2353 0.2353 0.2627); - --opaque-separator: color(display-p3 0.7765 0.7765 0.7843); - --system-fill: color(display-p3 0.4706 0.4706 0.502); - --secondary-system-fill: color(display-p3 0.4706 0.4706 0.502); - --tertiary-system-fill: color(display-p3 0.4627 0.4627 0.502); - --quaternary-system-fill: color(display-p3 0.4549 0.4549 0.502); - --system-background: color(display-p3 1 1 1); - --secondary-system-background: color(display-p3 0.949 0.949 0.9686); - --tertiary-system-background: color(display-p3 1 1 1); - --system-grouped-background: color(display-p3 0.949 0.949 0.9686); - --secondary-system-grouped-background: color(display-p3 1 1 1); - --tertiary-system-grouped-background: color( - display-p3 0.949 0.949 0.9686 - ); - } -} - -/* -@media (prefers-color-scheme: dark) { - :root { - --system-red: rgb(255, 69, 58); - --system-orange: rgb(255, 159, 10); - --system-yellow: rgb(255, 214, 10); - --system-green: rgb(48, 209, 88); - --system-teal: rgb(100, 210, 255); - --system-blue: rgb(10, 132, 255); - --system-indigo: rgb(94, 92, 230); - --system-purple: rgb(191, 90, 242); - --system-pink: rgb(255, 55, 95); - --system-gray: rgb(142, 142, 147); - --system-gray2: rgb(99, 99, 102); - --system-gray3: rgb(72, 72, 74); - --system-gray4: rgb(58, 58, 60); - --system-gray5: rgb(44, 44, 46); - --system-gray6: rgb(28, 28, 30); - - --label: rgb(255, 255, 255); - --secondary-label: rgb(235, 235, 245); - --tertiary-label: rgb(235, 235, 245); - --quaternary-label: rgb(235, 235, 245); - --placeholder-text: rgb(235, 235, 245); - --link: rgb(9, 132, 255); - --separator: rgb(84, 84, 88); - --opaque-separator: rgb(56, 56, 58); - --system-fill: rgb(120, 120, 128); - --secondary-system-fill: rgb(120, 120, 128); - --tertiary-system-fill: rgb(118, 118, 128); - --quaternary-system-fill: rgb(118, 118, 128); - --system-background: rgb(0, 0, 0); - --secondary-system-background: rgb(28, 28, 30); - --tertiary-system-background: rgb(44, 44, 46); - --system-grouped-background: rgb(0, 0, 0); - --secondary-system-grouped-background: rgb(28, 28, 30); - --tertiary-system-grouped-background: rgb(44, 44, 46); - } - - @supports (color: color(display-p3 1 1 1)) { - :root { - --system-red: color(display-p3 1 0.4118 0.3804); - --system-orange: color(display-p3 1 0.702 0.251); - --system-yellow: color(display-p3 1 0.8314 0.149); - --system-green: color(display-p3 0.1882 0.8588 0.3569); - --system-teal: color(display-p3 0.4392 0.8431 1); - --system-blue: color(display-p3 0.251 0.6118 1); - --system-indigo: color(display-p3 0.4902 0.4784 1); - --system-purple: color(display-p3 0.8549 0.5608 1); - --system-pink: color(display-p3 1 0.3922 0.5098); - --system-gray: color(display-p3 0.6824 0.6824 0.698); - --system-gray2: color(display-p3 0.4863 0.4863 0.502); - --system-gray3: color(display-p3 0.3294 0.3294 0.3373); - --system-gray4: color(display-p3 0.2667 0.2667 0.2745); - --system-gray5: color(display-p3 0.2118 0.2118 0.2196); - --system-gray6: color(display-p3 0.1412 0.1412 0.149); - - --label: color(display-p3 1 1 1); - --secondary-label: color(display-p3 0.9216 0.9216 0.9608); - --tertiary-label: color(display-p3 0.9216 0.9216 0.9608); - --quaternary-label: color(display-p3 0.9216 0.9216 0.9608); - --placeholder-text: color(display-p3 0.9216 0.9216 0.9608); - --link: color(display-p3 0.03529 0.5176 1); - --separator: color(display-p3 0.3294 0.3294 0.3451); - --opaque-separator: color(display-p3 0.2196 0.2196 0.2275); - --system-fill: color(display-p3 0.4706 0.4706 0.502); - --secondary-system-fill: color(display-p3 0.4706 0.4706 0.502); - --tertiary-system-fill: color(display-p3 0.4627 0.4627 0.502); - --quaternary-system-fill: color(display-p3 0.4627 0.4627 0.502); - --system-background: color(display-p3 0 0 0); - --secondary-system-background: color( - display-p3 0.1412 0.1412 0.149 - ); - --tertiary-system-background: color( - display-p3 0.2118 0.2118 0.2196 - ); - --system-grouped-background: color(display-p3 0 0 0); - --secondary-system-grouped-background: color( - display-p3 0.1412 0.1412 0.149 - ); - --tertiary-system-grouped-background: color( - display-p3 0.2118 0.2118 0.2196 - ); - } - } -} */ - -:root { - --large-title: 400 34pt / 41pt sans-serif; - --title-1: 400 28pt / 34pt sans-serif; - --title-2: 400 22pt / 28pt sans-serif; - --title-3: 400 20pt / 25pt sans-serif; - --headline: 600 17pt / 22pt sans-serif; - --body: 400 17pt / 22pt sans-serif; - --callout: 400 16pt / 21pt sans-serif; - --subhead: 400 15pt / 21pt sans-serif; - --footnote: 400 13pt / 18pt sans-serif; - --caption-1: 400 12pt / 16pt sans-serif; - --caption-2: 400 11pt / 13pt sans-serif; -} - -:root { - --icon-case: url('data:image/svg+xml;utf8,'); - --icon-class: url('data:image/svg+xml;utf8,'); - --icon-enumeration: url('data:image/svg+xml;utf8,'); - --icon-extension: url('data:image/svg+xml;utf8,'); - --icon-function: url('data:image/svg+xml;utf8,'); - --icon-method: url('data:image/svg+xml;utf8,'); - --icon-property: url('data:image/svg+xml;utf8,'); - --icon-protocol: url('data:image/svg+xml;utf8,'); - --icon-structure: url('data:image/svg+xml;utf8,'); - --icon-typealias: url('data:image/svg+xml;utf8,'); - --icon-variable: url('data:image/svg+xml;utf8,'); -} - -/************/ - -body, -input, -textarea, -select, -button { - font-synthesis: none; - -moz-font-feature-settings: "kern"; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - direction: ltr; - text-align: left; -} - -:matches(h1, h2, h3, h4, h5, h6) img { - margin: 0 0.5em 0.2em 0; - vertical-align: middle; - display: inline-block; -} - -img + h1 { - margin-top: 0.5em; -} - -img + :matches(h2, h3, h4, h5, h6) { - margin-top: 0.3em; -} - -:matches(h1, h2, h3, h4, h5, h6) + * { - margin-top: 0.8em; -} - -:matches(h1, h2, h3, h4, h5, h6) + :matches(h1, h2, h3, h4, h5, h6) { - margin-top: 0.4em; -} - -:matches(p, ul, ol) + :matches(h1, h2, h3, h4, h5, h6) { - margin-top: 1.6em; -} - -:matches(p, ul, ol) + * { - margin-top: 0.8em; -} - -ul, -ol { - margin-left: 1.17647em; -} - -:matches(ul, ol) :matches(ul, ol) { - margin-top: 0; - margin-bottom: 0; -} - -nav ul, -nav ol { - margin: 0; - list-style: none; -} - -li li { - font-size: 1em; -} - -a:link, -a:visited { - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -a:active { - text-decoration: none; -} - -p + a { - display: inline-block; -} - -b, -strong { - font-weight: 600; -} - -em, -i, -cite, -dfn { - font-style: italic; -} - -sup { - font-size: 0.6em; - vertical-align: top; - position: relative; - bottom: -0.2em; -} - -:matches(h1, h2, h3) sup { - font-size: 0.4em; -} - -sup a { - vertical-align: inherit; - color: inherit; -} - -sup a:hover { - color: var(--link); - text-decoration: none; -} - -sub { - line-height: 1; -} - -abbr { - border: 0; -} - -:lang(ja), -:lang(ko), -:lang(th), -:lang(zh) { - font-style: normal; -} - -:lang(ko) { - word-break: keep-all; -} - -form fieldset { - width: 95%; - margin: 1em auto; - max-width: 450px; -} - -form label { - position: relative; - display: block; - margin-bottom: 14px; - width: 100%; - font-size: 1em; - font-weight: 400; - line-height: 1.5em; -} - -input[type="text"], -input[type="email"], -input[type="number"], -input[type="password"], -input[type="tel"], -input[type="url"], -textarea { - margin: 0; - width: 100%; - height: 34px; - font-family: inherit; - font-size: 100%; - font-weight: 400; - border: 1px solid var(--opaque-separator); - border-radius: 4px; - padding: 0 1em 0; - position: relative; - z-index: 1; - color: #333333; - vertical-align: top; -} - -input[type="text"], -input[type="text"]:focus, -input[type="email"], -input[type="email"]:focus, -input[type="number"], -input[type="number"]:focus, -input[type="password"], -input[type="password"]:focus, -input[type="tel"], -input[type="tel"]:focus, -input[type="url"], -input[type="url"]:focus, -textarea, -textarea:focus { - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; -} - -input[type="text"]:focus, -input[type="email"]:focus, -input[type="number"]:focus, -input[type="password"]:focus, -input[type="tel"]:focus, -input[type="url"]:focus, -textarea:focus { - border-color: #0088cc; - outline: 0; - -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); - box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); - z-index: 9; -} - -input[type="text"]:-moz-read-only, -input[type="text"]:-moz-read-only, -input[type="email"]:-moz-read-only, -input[type="email"]:-moz-read-only, -input[type="number"]:-moz-read-only, -input[type="number"]:-moz-read-only, -input[type="password"]:-moz-read-only, -input[type="password"]:-moz-read-only, -input[type="tel"]:-moz-read-only, -input[type="tel"]:-moz-read-only, -input[type="url"]:-moz-read-only, -input[type="url"]:-moz-read-only { - background: none; - border: none; - box-shadow: none; - padding-left: 0; -} - -input[type="text"]:-moz-read-only, -input[type="text"]:read-only, -input[type="email"]:-moz-read-only, -input[type="email"]:read-only, -input[type="number"]:-moz-read-only, -input[type="number"]:read-only, -input[type="password"]:-moz-read-only, -input[type="password"]:read-only, -input[type="tel"]:-moz-read-only, -input[type="tel"]:read-only, -input[type="url"]:-moz-read-only, -input[type="url"]:read-only { - background: none; - border: none; - box-shadow: none; - padding-left: 0; -} - -::-webkit-input-placeholder, -:-moz-placeholder, -::-moz-placeholder, -:-ms-input-placeholder { - color: var(--placeholder-text); -} - -textarea { - min-height: 134px; - line-height: 1.4737; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - overflow-y: auto; - -webkit-overflow-scrolling: touch; - resize: vertical; -} -textarea, -textarea:focus { - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; -} - -select { - background: transparent; - width: 100%; - height: 34px; - padding: 0 1em; - font-size: 1em; - font-family: inherit; - border-radius: 4px; - border: none; - margin: 0; - cursor: pointer; -} -select, -select:focus { - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; -} - -select:focus { - border-color: #0088cc; - outline: 0; - -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); - box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); - z-index: 9; -} - -input[type="file"] { - margin: 0; - font-family: inherit; - font-size: 100%; - background: #fafafa; - width: 100%; - height: 34px; - border-radius: 4px; - padding: 6px 1em; - position: relative; - z-index: 1; - color: #333333; - vertical-align: top; - cursor: pointer; -} - -input[type="file"]:focus { - border-color: #0088cc; - outline: 0; - -webkit-box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); - box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); - z-index: 9; -} -input[type="file"]:focus, -input[type="file"]:focus:focus { - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; -} - -button, -button:focus, -input[type="reset"], -input[type="reset"]:focus, -input[type="submit"], -input[type="submit"]:focus { - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; -} - -:matches(button, input[type="reset"], input[type="submit"]) { - background-color: #e3e3e3; - background: -webkit-gradient( - linear, - left top, - left bottom, - from(white), - to(#e3e3e3) - ); - background: linear-gradient(white, #e3e3e3); - border-color: #d6d6d6; - color: #0070c9; -} -:matches(button, input[type="reset"], input[type="submit"]):hover { - background-color: #eeeeee; - background: -webkit-gradient( - linear, - left top, - left bottom, - from(white), - to(#eeeeee) - ); - background: linear-gradient(white, #eeeeee); - border-color: #d9d9d9; -} -:matches(button, input[type="reset"], input[type="submit"]):active { - background-color: gainsboro; - background: -webkit-gradient( - linear, - left top, - left bottom, - from(#f7f7f7), - to(gainsboro) - ); - background: linear-gradient(#f7f7f7, gainsboro); - border-color: #d0d0d0; -} -:matches(button, input[type="reset"], input[type="submit"]):disabled { - background-color: #e3e3e3; - background: -webkit-gradient( - linear, - left top, - left bottom, - from(white), - to(#e3e3e3) - ); - background: linear-gradient(white, #e3e3e3); - border-color: #d6d6d6; - color: #0070c9; -} - -/* */ - -body { - background: var(--system-grouped-background); - font: var(--body); - font-family: ui-system, -apple-system, BlinkMacSystemFont, sans-serif; - color: var(--label); -} - -h1 { - font: var(--large-title); -} - -h2 { - font: var(--title-1); -} - -h3 { - font: var(--title-2); -} - -h4, -h5, -h6 { - font: var(--title-3); -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-weight: 500; -} - -/* strong, - th, - dt { - font: var(--headline); - } */ - -a { - color: var(--link); -} - -label { - font: var(--callout); -} - -label, -input { - display: block; -} - -input { - margin-bottom: 1em; -} - -/* button, - input[type="submit"] { - color: var(--link); - background: transparent; - border: none; - padding: 0.5em; - } */ - -/*********************/ - -article article { - background: var(--secondary-system-background); - max-width: 70ch; - margin: 1em auto; - padding: 1em; -} - -hr { - border: none; - border-top: 1px var(--opaque-separator) solid; - margin: 1em 0; -} - -table { - width: 100%; - font: var(--caption-1); - caption-side: bottom; - margin-bottom: 2em; -} - -th, -td { - border-bottom-width: 1px; - border-bottom-style: solid; - padding: 0.5em 0; -} - -th { - font-weight: 500; - text-align: left; - border-bottom-color: var(--separator); -} - -td { - border-bottom-color: var(--opaque-separator); - color: var(--secondary-label); -} - -caption { - text-align: left; - margin-top: 2em; - font: var(--caption-2); - color: var(--tertiary-label); -} - -.graph > polygon { - display: none; -} - -.graph text { - font-family: monospace; - fill: currentColor !important; -} - -.graph path, -.graph ellipse, -.graph rect, -.graph polygon { - stroke: currentColor !important; -} - -body { - width: 90vw; - max-width: 1280px; - margin: 1em auto; - display: grid; - grid-template-areas: - "header search" - "main nav" - "footer footer"; - grid-template-columns: 3fr 1fr; - gap: 1em; -} - -body > header { - font: var(--large-title); -} - -body.HomePage { - grid-template-areas: - "header search" - "main main" - "footer footer"; -} -@media screen and (min-width: 769px) { - body.HomePage ul { - column-count: 2; - column-gap: 2em; - padding: 0 0 1em 0; - margin: 0 0 1em 0; - overflow-wrap: break-word; - } -} - -@media screen and (max-width: 768px) { - :root { - --large-title: 400 31pt / 38pt sans-serif; - --title-1: 400 25pt / 31pt sans-serif; - --title-2: 400 19pt / 24pt sans-serif; - --title-3: 400 17pt / 22pt sans-serif; - --headline: 600 14pt / 19pt sans-serif; - --body: 400 14pt / 19pt sans-serif; - --callout: 400 13pt / 18pt sans-serif; - --subhead: 400 12pt / 16pt sans-serif; - --footnote: 400 12pt / 16pt sans-serif; - --caption-1: 400 11pt / 13pt sans-serif; - --caption-2: 400 11pt / 13pt sans-serif; - } - - body { - width: 96vw; - max-width: 100%; - display: block; - } - - body > header { - font: var(--large-title); - } - - body > nav { - display: none; - } - - body > main { - padding: 0 1em; - } - - .parameters { - display: block !important; - } - - .parameters dt { - text-align: initial !important; - margin: 0.5em 0; - } - - .parameters dd { - margin-bottom: 1em; - } -} - -body > header { - grid-area: header; - padding: 0.5em 0; -} - -main, -nav { - overflow-x: scroll; -} - -main { - background: var(--system-background); - border-radius: 8px; -} - -form.search { - grid-area: search; -} - -body > footer { - grid-area: footer; - font-size: smaller; - color: var(--tertiary-label); -} - -main { - padding: 0 2em; - grid-area: main; - /* max-width: 66ch; */ -} - -nav { - grid-area: nav; - overflow-x: scroll; - padding: 1em; -} - -nav a { - color: var(--tertiary-label); -} - -nav ul a { - color: var(--quaternary-label); -} - -nav ol { - padding: 0; -} - -nav ul { - padding: 0; - margin-bottom: 1em; - font-size: smaller; -} - -nav ol > li > a { - font-weight: 500; - display: block; - margin: 0.5em 0; -} - -nav li { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -nav div { - position: sticky; - top: 1em; -} - -main > section { - margin-bottom: 2em; -} - -main section { - margin-bottom: 1em; -} - -/* main :matches(h1, h2, h3) { - position: sticky; - top: 0px; -} - -main h1 { - z-index: 1; -} - -main h2 { - z-index: 2; -} - -main h3 { - z-index: 3; -} */ - -article > .summary { - margin-bottom: 2em; - padding-bottom: 1em; - border-bottom: 1px var(--opaque-separator) solid; -} - -article > .summary:last-child { - border-bottom: none; -} - -.parameters { - display: grid; - grid-template-columns: 1fr 3fr; - gap: 0.5em; -} - -.parameters dt { - text-align: right; -} - -.parameters dd { - color: var(--secondary-label); -} - -dd p { - margin-top: 0; -} - -.highlight { - background: var(--secondary-system-background); - border-radius: 8px; - font-size: smaller; - overflow-x: scroll; - white-space: pre-line; - padding: 1em; - padding-left: 3em; - text-indent: -2em; -} - -.highlight .p { - white-space: nowrap; -} - -.highlight .placeholder { - color: var(--label); -} - -.highlight a { - text-decoration: underline; - color: var(--placeholder-text); -} - -.highlight .literal, -.highlight .keyword, -.highlight .attribute { - color: var(--system-purple); -} - -.highlight .number { - color: var(--system-blue); -} - -.highlight .declaration { - color: var(--system-teal); -} - -.highlight .type { - color: var(--system-indigo); -} - -.highlight .directive { - color: var(--system-orange); -} - -.highlight .comment { - color: var(--system-gray); -} - -main details { - margin-bottom: 1em; - padding: 1em 1em 1em 2em; - background: var(--system-background); - border-bottom: 1px var(--opaque-separator) solid; -} - -main details:last-of-type { - border-bottom: none; -} - -main summary { - cursor: pointer; - font-weight: 500; -} - -main summary::-webkit-details-marker { - color: transparent; - position: absolute; - width: 1em; - height: 1em; - margin-left: -2em; - margin-top: 0.125em; - outline: 0 !important; -} - -#initializers summary::-webkit-details-marker, -#methods summary::-webkit-details-marker { - background: var(--icon-method); -} - -#properties summary::-webkit-details-marker { - background: var(--icon-property); -} - -main summary:hover { - text-decoration: underline; -} - -figure { - padding: 1em 0; -} - -figure svg { - max-width: 100%; - margin: 0 auto; - display: block; -} - -h1 small { - font-size: 0.5em; - line-height: 1.5; - display: block; - font-weight: normal; - color: var(--quaternary-label); -} - -h1 code { - font-family: ui-system, -apple-system, BlinkMacSystemFont, sans-serif; -} - -p code, -dd code, -li code { - font-size: smaller; - color: var(--secondary-label); -} - -a code { - text-decoration: underline; -} - -li[class] { - list-style: none; - background-repeat: no-repeat; - background-position: left top; - background-size: 1em; - padding-left: 3em; - text-indent: -1em; - margin-bottom: 1em; -} - -nav li[class] { - background-position: left 0.2em; -} - -li.case { - background-image: var(--icon-case); -} - -li.class { - background-image: var(--icon-class); -} - -li.enumeration { - background-image: var(--icon-enumeration); -} - -li.extension { - background-image: var(--icon-extension); -} - -li.function { - background-image: var(--icon-function); -} - -li.method, -li.initializer { - background-image: var(--icon-method); -} - -li.property { - background-image: var(--icon-property); -} - -li.protocol { - background-image: var(--icon-protocol); -} - -li.structure { - background-image: var(--icon-structure); -} - -li.typealias { - background-image: var(--icon-typealias); -} - -li.variable { - background-image: var(--icon-variable); -} - -"""# diff --git a/Sources/swift-doc/Supporting Types/Pages/HomePage.swift b/Sources/swift-doc/Supporting Types/Pages/HomePage.swift index d44c2765..e6417d95 100644 --- a/Sources/swift-doc/Supporting Types/Pages/HomePage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/HomePage.swift @@ -6,7 +6,9 @@ import HypertextLiteral struct HomePage: Page { var module: Module - var types: [Symbol] = [] + var classes: [Symbol] = [] + var enumerations: [Symbol] = [] + var structures: [Symbol] = [] var protocols: [Symbol] = [] var operatorNames: Set = [] var globalTypealiasNames: Set = [] @@ -18,10 +20,12 @@ struct HomePage: Page { for symbol in module.interface.topLevelSymbols.filter({ $0.isPublic }) { switch symbol.api { - case is Class, - is Enumeration, - is Structure: - types.append(symbol) + case is Class: + classes.append(symbol) + case is Enumeration: + enumerations.append(symbol) + case is Structure: + structures.append(symbol) case is Protocol: protocols.append(symbol) case let `typealias` as Typealias: @@ -47,6 +51,7 @@ struct HomePage: Page { } var document: CommonMark.Document { + let types = classes + enumerations + structures let typeNames = Set(types.map { $0.id.description }) let protocolNames = Set(protocols.map { $0.id.description }) @@ -92,25 +97,32 @@ struct HomePage: Page { var html: HypertextLiteral.HTML { return #""" \#([ - ("Types", types), + ("Classes", classes), + ("Structures", structures), + ("Enumerations", enumerations), ("Protocols", protocols), ].compactMap { (heading, symbols) -> HypertextLiteral.HTML? in guard !symbols.isEmpty else { return nil } return #""" -

\#(heading)

-
    - \#(symbols.sorted().map { symbol -> HypertextLiteral.HTML in - let descriptor = String(describing: type(of: symbol.api)) - return #""" -
  • - - \#(symbol.id.description) - -
  • - """# as HypertextLiteral.HTML - }) -
+
+

\#(heading)

+
+ \#(symbols.sorted().map { symbol -> HypertextLiteral.HTML in + let descriptor = String(describing: type(of: symbol.api)).lowercased() + return #""" +
+ + \#(symbol.id.description) + +
+
+ \#(commonmark: symbol.documentation?.summary ?? "") +
+ """# as HypertextLiteral.HTML + }) +
+
"""# as HypertextLiteral.HTML }) """# diff --git a/Sources/swift-doc/Supporting Types/Pages/TypePage.swift b/Sources/swift-doc/Supporting Types/Pages/TypePage.swift index 4ab71068..6ed5f348 100644 --- a/Sources/swift-doc/Supporting Types/Pages/TypePage.swift +++ b/Sources/swift-doc/Supporting Types/Pages/TypePage.swift @@ -20,27 +20,17 @@ struct TypePage: Page { } var document: CommonMark.Document { - return Document { + return CommonMark.Document { Heading { symbol.id.description } Documentation(for: symbol, in: module) - - Inheritance(of: symbol, in: module) - - if symbol.api is Protocol { - ConformingTypes(to: symbol, in: module) - } else if symbol.api is Type { - NestedTypes(of: symbol, in: module) - } - + Relationships(of: symbol, in: module) Members(of: symbol, in: module) Requirements(of: symbol, in: module) } } var html: HypertextLiteral.HTML { -// print(symbol.api, module.interface.relationshipsBySubject[symbol.id]) -// print("") return #"""

\#(String(describing: type(of: symbol.api))) @@ -48,7 +38,7 @@ struct TypePage: Page {

\#(Documentation(for: symbol, in: module).html) - \#(Inheritance(of: symbol, in: module).html) + \#(Relationships(of: symbol, in: module).html) \#(Members(of: symbol, in: module).html) \#(Requirements(of: symbol, in: module).html) """# From 0f5993cd6ed2c49c50fa9170e23c20fdcc6152b2 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 31 Mar 2020 10:50:45 -0700 Subject: [PATCH 03/11] Run CI on Swift 5.2 --- .github/workflows/ci.yml | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7083cd44..1027d0a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,23 +3,29 @@ name: CI on: [push] jobs: - # macos: - # runs-on: macOS-latest + macos: + runs-on: macOS-latest - # steps: - # - name: Checkout - # uses: actions/checkout@v1 - # - name: Build and Test - # run: swift test + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Build and Test + run: swift test + env: + DEVELOPER_DIR: /Applications/Xcode_11.4.app/Contents/Developer linux: runs-on: ubuntu-latest container: - image: swiftlang/swift:nightly-5.2-xenial + image: swift:5.2 steps: - name: Checkout uses: actions/checkout@v1 - - name: Build and Run - run: swift run swift-doc Sources/SwiftDoc + - name: Install System Dependencies + run: | + apt-get update + apt-get install -y libxml2-dev + - name: Build and Test + run: swift test --enable-test-discovery From 0b49debec97c762a86f64de68653fa0aac35d8bd Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 31 Mar 2020 10:52:22 -0700 Subject: [PATCH 04/11] Add swift-doc executable and SwiftDoc library to package products --- Package.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Package.swift b/Package.swift index 3ffa5817..fbb191fb 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,10 @@ import PackageDescription let package = Package( name: "swift-doc", + products: [ + .executable(name: "swift-doc", targets: ["swift-doc"]), + .library(name: "SwiftDoc", targets: ["SwiftDoc"]) + ], dependencies: [ .package(url: "https://github.com/apple/swift-syntax.git", .revision("swift-5.2-DEVELOPMENT-SNAPSHOT-2020-03-09-a")), .package(url: "https://github.com/SwiftDocOrg/SwiftSemantics.git", .branch("swift-5.2")), From a6befa6e8f2e13f3776da7b5e0c65db3eac5e539 Mon Sep 17 00:00:00 2001 From: Mattt Date: Tue, 31 Mar 2020 10:55:30 -0700 Subject: [PATCH 05/11] Update Markup dependency to 0.0.3 --- Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index 7d37c37b..4f904232 100644 --- a/Package.resolved +++ b/Package.resolved @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/SwiftDocOrg/Markup.git", "state": { "branch": null, - "revision": "bcc9bff98749f8ed92221375591a1afd61b02f1a", - "version": null + "revision": "9a429d0011d738059bc94f5f92ee406689597a91", + "version": "0.0.3" } }, { diff --git a/Package.swift b/Package.swift index fbb191fb..d5d7b9b4 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( .package(url: "https://github.com/SwiftDocOrg/SwiftMarkup.git", .upToNextMinor(from: "0.0.5")), .package(url: "https://github.com/SwiftDocOrg/GraphViz.git", .revision("03405c13dc1c31f50c08bbec6e7587cbee1c7fb3")), .package(url: "https://github.com/NSHipster/HypertextLiteral.git", .upToNextMinor(from: "0.0.2")), - .package(url: "https://github.com/SwiftDocOrg/Markup.git", .revision("bcc9bff98749f8ed92221375591a1afd61b02f1a")), + .package(url: "https://github.com/SwiftDocOrg/Markup.git", .upToNextMinor(from: "0.0.3")), .package(url: "https://github.com/NSHipster/SwiftSyntaxHighlighter.git", .revision("fe39b4ec07e1e37872adf4b506d223ab27cf8cea")), .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.0.2")), ], From 3fc0d1fdd39f7c45591fdd21e05f948645cb656a Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 1 Apr 2020 05:24:18 -0700 Subject: [PATCH 06/11] Fix 'error: ProcessInfo initializer is inaccessible due to internal protection level' --- Tests/SwiftDocTests/Helpers/temporaryFile.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SwiftDocTests/Helpers/temporaryFile.swift b/Tests/SwiftDocTests/Helpers/temporaryFile.swift index 782e7727..453910bb 100644 --- a/Tests/SwiftDocTests/Helpers/temporaryFile.swift +++ b/Tests/SwiftDocTests/Helpers/temporaryFile.swift @@ -1,10 +1,10 @@ import Foundation func temporaryFile(path: String? = nil, contents: String) throws -> URL { - let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(ProcessInfo().globallyUniqueString) + let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString) try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: [.posixPermissions: 0o766]) - let path = path ?? ProcessInfo().globallyUniqueString + let path = path ?? ProcessInfo.processInfo.globallyUniqueString let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(path) try contents.data(using: .utf8)?.write(to: temporaryFileURL) From f6b53073755238af5b9011f6dc1f4099d7e653bb Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 1 Apr 2020 08:28:46 -0700 Subject: [PATCH 07/11] Update to SwiftSyntaxHighlighter 1.0.0 --- Package.resolved | 12 ++++++------ Package.swift | 2 +- .../Supporting Types/Components/Declaration.swift | 2 +- .../Supporting Types/Components/Documentation.swift | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Package.resolved b/Package.resolved index 77d5ddc7..b01d7a92 100644 --- a/Package.resolved +++ b/Package.resolved @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/NSHipster/HypertextLiteral.git", "state": { "branch": null, - "revision": "8d31a0905c346a45c87773ad50862b5b3df8dff6", - "version": "0.0.4" + "revision": "3e45da849e507d171c7264146176bb834a01be4f", + "version": "0.0.2" } }, { @@ -86,17 +86,17 @@ "package": "SwiftSemantics", "repositoryURL": "https://github.com/SwiftDocOrg/SwiftSemantics.git", "state": { - "branch": "swift-5.2", + "branch": null, "revision": "4fdc48bddbbb8311079ed111e5a4f2b92423b05c", - "version": null + "version": "0.1.0" } }, { "package": "SwiftSyntaxHighlighter", "repositoryURL": "https://github.com/NSHipster/SwiftSyntaxHighlighter.git", "state": { - "branch": null, - "revision": "fe39b4ec07e1e37872adf4b506d223ab27cf8cea", + "branch": "1.0.0", + "revision": "4a20d10bba17241b66650d99081801536146b43c", "version": null } } diff --git a/Package.swift b/Package.swift index e47eaa5e..667833a4 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( .package(url: "https://github.com/SwiftDocOrg/GraphViz.git", .revision("03405c13dc1c31f50c08bbec6e7587cbee1c7fb3")), .package(url: "https://github.com/NSHipster/HypertextLiteral.git", .upToNextMinor(from: "0.0.2")), .package(url: "https://github.com/SwiftDocOrg/Markup.git", .upToNextMinor(from: "0.0.3")), - .package(url: "https://github.com/NSHipster/SwiftSyntaxHighlighter.git", .revision("fe39b4ec07e1e37872adf4b506d223ab27cf8cea")), + .package(url: "https://github.com/NSHipster/SwiftSyntaxHighlighter.git", .revision("1.0.0")), .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.0.2")), ], targets: [ diff --git a/Sources/swift-doc/Supporting Types/Components/Declaration.swift b/Sources/swift-doc/Supporting Types/Components/Declaration.swift index d6fe455e..94acc437 100644 --- a/Sources/swift-doc/Supporting Types/Components/Declaration.swift +++ b/Sources/swift-doc/Supporting Types/Components/Declaration.swift @@ -26,7 +26,7 @@ struct Declaration: Component { } var html: HypertextLiteral.HTML { - var html = try! highlight(symbol.declaration, using: Xcode.self) + var html = try! SwiftSyntaxHighlighter.highlight(source: symbol.declaration, using: Xcode.self) html = linkCodeElements(of: html, for: symbol, in: module) return HTML(html) } diff --git a/Sources/swift-doc/Supporting Types/Components/Documentation.swift b/Sources/swift-doc/Supporting Types/Components/Documentation.swift index f16cea61..132ffc89 100644 --- a/Sources/swift-doc/Supporting Types/Components/Documentation.swift +++ b/Sources/swift-doc/Supporting Types/Components/Documentation.swift @@ -110,7 +110,7 @@ struct Documentation: Component { codeBlock.fenceInfo?.compare("swift", options: .caseInsensitive) == .orderedSame, let source = codeBlock.literal { - var html = try! highlight(source, using: Xcode.self) + var html = try! SwiftSyntaxHighlighter.highlight(source: source, using: Xcode.self) html = linkCodeElements(of: html, for: symbol, in: module) return HTML(html) } else { From 54618bc2fe98d3da364424262afd4c3f7d4bfdde Mon Sep 17 00:00:00 2001 From: Mattt Date: Wed, 1 Apr 2020 09:21:58 -0700 Subject: [PATCH 08/11] Improve styling and formatting --- Sources/swift-doc/Supporting Types/CSS.swift | 51 +++++++++++++------ .../swift-doc/Supporting Types/Layout.swift | 10 ++-- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/Sources/swift-doc/Supporting Types/CSS.swift b/Sources/swift-doc/Supporting Types/CSS.swift index 9430d823..d9354a9c 100644 --- a/Sources/swift-doc/Supporting Types/CSS.swift +++ b/Sources/swift-doc/Supporting Types/CSS.swift @@ -166,8 +166,8 @@ let css = #""" :root { --large-title: 600 32pt / 39pt sans-serif; - --title-1: 500 26pt / 32pt sans-serif; - --title-2: 500 20pt / 25pt sans-serif; + --title-1: 600 26pt / 32pt sans-serif; + --title-2: 600 20pt / 25pt sans-serif; --title-3: 500 18pt / 23pt sans-serif; --headline: 500 15pt / 20pt sans-serif; --body: 300 15pt / 20pt sans-serif; @@ -353,12 +353,12 @@ dfn { font-style: italic; } -sup { +/* sup { font-size: 0.6em; vertical-align: top; position: relative; bottom: -0.2em; -} +} */ :matches(h1, h2, h3) sup { font-size: 0.4em; @@ -773,13 +773,6 @@ body { width: 90vw; max-width: 1280px; margin: 1em auto; - /* display: grid; - grid-template-areas: - "header search" - "main nav" - "footer footer"; - grid-template-columns: 4fr 2fr; - gap: 1em; */ } body > header { @@ -790,6 +783,18 @@ body > header a { color: var(--label); } +body > header span { + font-weight: normal; +} + +body > header sup { + text-transform: uppercase; + font-size: small; + font-weight: 300; + color: var(--secondary-label); + letter-spacing: 0.1ch; +} + @media screen and (max-width: 768px) { body { width: 96vw; @@ -797,8 +802,9 @@ body > header a { } body > header { - font: var(--title-1); - text-align: center; + font: var(--title-3); + text-align: left; + padding: 1em 0; } body > nav { @@ -808,10 +814,21 @@ body > header a { body > main { padding: 0 1em; } + + #relationships figure { + display: none; + } + + section > [role="article"][class] pre { + margin-left: -2.5em; + } + + section > [role="article"][class] div { + margin-left: -2em; + } } body > header { - grid-area: header; padding: 0.5em 0; } @@ -950,7 +967,7 @@ article > .summary:last-child { } dl { - padding-bottom: 1em; + padding-top: 1em; } dt { @@ -1068,13 +1085,15 @@ dl dt[class] { section > [role="article"] { margin-bottom: 1em; + padding-bottom: 1em; border-bottom: 1px var(--separator) solid; padding-left: 2em !important; } section > [role="article"]:last-of-type { - border-bottom: none; margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; } nav li[class], diff --git a/Sources/swift-doc/Supporting Types/Layout.swift b/Sources/swift-doc/Supporting Types/Layout.swift index 91aedf6d..74e5e4e6 100644 --- a/Sources/swift-doc/Supporting Types/Layout.swift +++ b/Sources/swift-doc/Supporting Types/Layout.swift @@ -17,9 +17,13 @@ func layout(_ page: Page) -> HTML {
- - \#(page.module.name) - + + + \#(page.module.name) + + Documentation + + Beta