diff --git a/Sources/Markdown/Base/Markup.swift b/Sources/Markdown/Base/Markup.swift index e26ff1ef..b19bb6b3 100644 --- a/Sources/Markdown/Base/Markup.swift +++ b/Sources/Markdown/Base/Markup.swift @@ -75,6 +75,8 @@ func makeMarkup(_ data: _MarkupData) -> Markup { return DoxygenDiscussion(data) case .doxygenNote: return DoxygenNote(data) + case .doxygenAbstract: + return DoxygenAbstract(data) case .doxygenParam: return DoxygenParameter(data) case .doxygenReturns: diff --git a/Sources/Markdown/Base/RawMarkup.swift b/Sources/Markdown/Base/RawMarkup.swift index b6b231c6..a4dabe20 100644 --- a/Sources/Markdown/Base/RawMarkup.swift +++ b/Sources/Markdown/Base/RawMarkup.swift @@ -54,6 +54,7 @@ enum RawMarkupData: Equatable { case doxygenDiscussion case doxygenNote + case doxygenAbstract case doxygenParam(name: String) case doxygenReturns } @@ -352,6 +353,10 @@ final class RawMarkup: ManagedBuffer { return .create(data: .doxygenNote, parsedRange: parsedRange, children: children) } + static func doxygenAbstract(parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup { + return .create(data: .doxygenAbstract, parsedRange: parsedRange, children: children) + } + static func doxygenParam(name: String, parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup { return .create(data: .doxygenParam(name: name), parsedRange: parsedRange, children: children) } diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift new file mode 100644 index 00000000..96679bcd --- /dev/null +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift @@ -0,0 +1,57 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Foundation + +/// A parsed Doxygen `\abstract` command. +/// +/// The Doxygen support in Swift-Markdown parses `\abstract` commands of the form +/// `\abstract description`, where `description` continues until the next blank +/// line or parsed command. +/// +/// ```markdown +/// \abstract This object can give other objects in your program magical powers. +/// ``` +public struct DoxygenAbstract: BlockContainer { + public var _data: _MarkupData + + init(_ raw: RawMarkup) throws { + guard case .doxygenAbstract = raw.data else { + throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenAbstract.self) + } + let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) + self.init(_MarkupData(absoluteRaw)) + } + + init(_ data: _MarkupData) { + self._data = data + } + + public func accept(_ visitor: inout V) -> V.Result { + return visitor.visitDoxygenAbstract(self) + } +} + +public extension DoxygenAbstract { + /// Create a new Doxygen abstract definition. + /// + /// - Parameter children: Block child elements. + init(children: Children) where Children.Element == BlockMarkup { + try! self.init(.doxygenAbstract(parsedRange: nil, children.map({ $0.raw.markup }))) + } + + /// Create a new Doxygen abstract definition. + /// + /// - Parameter children: Block child elements. + init(children: BlockMarkup...) { + self.init(children: children) + } +} + diff --git a/Sources/Markdown/CMakeLists.txt b/Sources/Markdown/CMakeLists.txt index 53e1054f..4985d675 100644 --- a/Sources/Markdown/CMakeLists.txt +++ b/Sources/Markdown/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(Markdown Base/RawMarkup.swift "Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift" "Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift" + "Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift" "Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift" "Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift" "Block Nodes/Block Container Blocks/BlockDirective.swift" diff --git a/Sources/Markdown/Markdown.docc/Markdown/DoxygenCommands.md b/Sources/Markdown/Markdown.docc/Markdown/DoxygenCommands.md index 17c839b3..9437bfba 100644 --- a/Sources/Markdown/Markdown.docc/Markdown/DoxygenCommands.md +++ b/Sources/Markdown/Markdown.docc/Markdown/DoxygenCommands.md @@ -44,6 +44,7 @@ Doxygen commands are not parsed within code blocks or block directive content. ### Commands +- ``DoxygenAbstract`` - ``DoxygenDiscussion`` - ``DoxygenNote`` - ``DoxygenParameter`` diff --git a/Sources/Markdown/Parser/BlockDirectiveParser.swift b/Sources/Markdown/Parser/BlockDirectiveParser.swift index 5e20b8f0..ccfc24fd 100644 --- a/Sources/Markdown/Parser/BlockDirectiveParser.swift +++ b/Sources/Markdown/Parser/BlockDirectiveParser.swift @@ -226,6 +226,7 @@ struct PendingDoxygenCommand { enum CommandKind { case discussion case note + case abstract case param(name: Substring) case returns @@ -235,6 +236,8 @@ struct PendingDoxygenCommand { return "'discussion'" case .note: return "'note'" + case .abstract: + return "'abstract'" case .param(name: let name): return "'param' Argument: '\(name)'" case .returns: @@ -755,6 +758,8 @@ private enum ParseContainer: CustomStringConvertible { return [.doxygenDiscussion(parsedRange: range, children)] case .note: return [.doxygenNote(parsedRange: range, children)] + case .abstract: + return [.doxygenAbstract(parsedRange: range, children)] case .param(let name): return [.doxygenParam(name: String(name), parsedRange: range, children)] case .returns: @@ -887,6 +892,8 @@ struct ParseContainerStack { kind = .discussion case "note": kind = .note + case "brief", "abstract": + kind = .abstract case "param": guard let paramName = remainder.lex(until: { ch in if ch.isWhitespace { diff --git a/Sources/Markdown/Visitor/MarkupVisitor.swift b/Sources/Markdown/Visitor/MarkupVisitor.swift index 417cf974..9c3e38a7 100644 --- a/Sources/Markdown/Visitor/MarkupVisitor.swift +++ b/Sources/Markdown/Visitor/MarkupVisitor.swift @@ -291,6 +291,14 @@ public protocol MarkupVisitor { */ mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) -> Result + /** + Visit a `DoxygenAbstract` element and return the result. + + - parameter doxygenAbstract: A `DoxygenAbstract` element. + - returns: The result of the visit. + */ + mutating func visitDoxygenAbstract(_ doxygenAbstract: DoxygenAbstract) -> Result + /** Visit a `DoxygenParam` element and return the result. @@ -411,6 +419,9 @@ extension MarkupVisitor { public mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) -> Result { return defaultVisit(doxygenNote) } + public mutating func visitDoxygenAbstract(_ doxygenAbstract: DoxygenAbstract) -> Result { + return defaultVisit(doxygenAbstract) + } public mutating func visitDoxygenParameter(_ doxygenParam: DoxygenParameter) -> Result { return defaultVisit(doxygenParam) } diff --git a/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift b/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift index bee884b3..35593ae4 100644 --- a/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift +++ b/Sources/Markdown/Walker/Walkers/MarkupFormatter.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2024 Apple Inc. and the Swift project authors + Copyright (c) 2021-2025 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -1178,6 +1178,11 @@ public struct MarkupFormatter: MarkupWalker { descendInto(doxygenDiscussion) } + public mutating func visitDoxygenAbstract(_ doxygenAbstract: DoxygenAbstract) { + printDoxygenStart("abstract", for: doxygenAbstract) + descendInto(doxygenAbstract) + } + public mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) { printDoxygenStart("note", for: doxygenNote) descendInto(doxygenNote) @@ -1194,4 +1199,5 @@ public struct MarkupFormatter: MarkupWalker { printDoxygenStart("returns", for: doxygenReturns) descendInto(doxygenReturns) } + } diff --git a/Tests/MarkdownTests/Parsing/DoxygenCommandParserTests.swift b/Tests/MarkdownTests/Parsing/DoxygenCommandParserTests.swift index 71088720..24eb1bf2 100644 --- a/Tests/MarkdownTests/Parsing/DoxygenCommandParserTests.swift +++ b/Tests/MarkdownTests/Parsing/DoxygenCommandParserTests.swift @@ -32,6 +32,26 @@ class DoxygenCommandParserTests: XCTestCase { assertValidParse(source: #"\discussion The thing."#) } + func testParseAbstract() { + func assertValidParse(source: String) { + let document = Document(parsing: source, options: parseOptions) + XCTAssert(document.child(at: 0) is DoxygenAbstract) + + let expectedDump = """ + Document + └─ DoxygenAbstract + └─ Paragraph + └─ Text "The thing." + """ + XCTAssertEqual(document.debugDescription(), expectedDump) + } + + assertValidParse(source: "@abstract The thing.") + assertValidParse(source: #"\abstract The thing."#) + assertValidParse(source: "@brief The thing.") + assertValidParse(source: #"\brief The thing."#) + } + func testParseNote() { func assertValidParse(source: String) { let document = Document(parsing: source, options: parseOptions) @@ -451,7 +471,9 @@ class DoxygenCommandParserTests: XCTestCase { let expectedDump = #""" Document ├─ BlockDirective name: "method" - ├─ BlockDirective name: "abstract" + ├─ DoxygenAbstract + │ └─ Paragraph + │ └─ Text "Some brief description of this method" ├─ DoxygenParameter parameter: number │ └─ Paragraph │ └─ Text "Some description of the “number” parameter" diff --git a/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift b/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift index 37750e62..dc26ee35 100644 --- a/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift +++ b/Tests/MarkdownTests/Visitors/MarkupFormatterTests.swift @@ -301,6 +301,26 @@ class MarkupFormatterSingleElementTests: XCTestCase { XCTAssertEqual(expectedAt, printedAt) } + func testPrintDoxygenAbstract() { + let expected = #"\abstract Another thing."# + let printed = DoxygenAbstract(children: Paragraph(Text("Another thing."))).format() + print (printed) + XCTAssertEqual(expected, printed) + } + + func testPrintDoxygenAbstractMultiline() { + let expected = #""" + \abstract Another thing. + This is an extended abstract. + """# + let printed = DoxygenAbstract(children: Paragraph( + Text("Another thing."), + SoftBreak(), + Text("This is an extended abstract.") + )).format() + XCTAssertEqual(expected, printed) + } + func testPrintDoxygenDiscussion() { let expected = #"\discussion Another thing."# let printed = DoxygenDiscussion(children: Paragraph(Text("Another thing."))).format()