Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/Markdown/Base/Markup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func makeMarkup(_ data: _MarkupData) -> Markup {
return Table.Cell(data)
case .symbolLink:
return SymbolLink(data)
case .inlineAttributes:
return InlineAttributes(data)
}
}

Expand Down
5 changes: 5 additions & 0 deletions Sources/Markdown/Base/RawMarkup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum RawMarkupData: Equatable {
case strong
case text(String)
case symbolLink(destination: String?)
case inlineAttributes(attributes: String)

// Extensions
case strikethrough
Expand Down Expand Up @@ -290,6 +291,10 @@ final class RawMarkup: ManagedBuffer<RawMarkupHeader, RawMarkup> {
return .create(data: .symbolLink(destination: destination), parsedRange: parsedRange, children: [])
}

static func inlineAttributes(attributes: String, parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {
return .create(data: .inlineAttributes(attributes: attributes), parsedRange: parsedRange, children: children)
}

// MARK: Extensions

static func strikethrough(parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2021 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
*/

/// A set of one or more inline attributes.
public struct InlineAttributes: InlineMarkup, InlineContainer {
public var _data: _MarkupData

init(_ raw: RawMarkup) throws {
guard case .inlineAttributes = raw.data else {
throw RawMarkup.Error.concreteConversionError(from: raw, to: InlineAttributes.self)
}
let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0))
self.init(_MarkupData(absoluteRaw))
}

init(_ data: _MarkupData) {
self._data = data
}
}

// MARK: - Public API

public extension InlineAttributes {
/// Create a set of custom inline attributes applied to zero or more child inline elements.
init<Children: Sequence>(attributes: String, _ children: Children) where Children.Element == RecurringInlineMarkup {
try! self.init(.inlineAttributes(attributes: attributes, parsedRange: nil, children.map { $0.raw.markup }))
}

/// Create a set of custom attributes applied to zero or more child inline elements.
init(attributes: String, _ children: RecurringInlineMarkup...) {
self.init(attributes: attributes, children)
}

/// The specified attributes in JSON5 format.
var attributes: String {
get {
guard case let .inlineAttributes(attributes) = _data.raw.markup.data else {
fatalError("\(self) markup wrapped unexpected \(_data.raw)")
}
return attributes
}
set {
_data = _data.replacingSelf(.inlineAttributes(attributes: newValue, parsedRange: nil, _data.raw.markup.copyChildren()))
}
}

// MARK: Visitation

func accept<V: MarkupVisitor>(_ visitor: inout V) -> V.Result {
return visitor.visitInlineAttributes(self)
}
}
14 changes: 14 additions & 0 deletions Sources/Markdown/Parser/CommonMarkConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ fileprivate enum CommonMarkNodeType: String {
case strong
case link
case image
case inlineAttributes = "attribute"
case none = "NONE"
case unknown = "<unknown>"

Expand Down Expand Up @@ -229,6 +230,8 @@ struct MarkupParser {
return convertTableRow(state)
case .tableCell:
return convertTableCell(state)
case .inlineAttributes:
return convertInlineAttributes(state)
default:
fatalError("Unknown cmark node type '\(state.nodeType.rawValue)' encountered during conversion")
}
Expand Down Expand Up @@ -578,6 +581,17 @@ struct MarkupParser {
return MarkupConversion(state: childConversion.state.next(), result: .tableCell(parsedRange: parsedRange, colspan: colspan, rowspan: rowspan, childConversion.result))
}

private static func convertInlineAttributes(_ state: MarkupConverterState) -> MarkupConversion<RawMarkup> {
precondition(state.event == CMARK_EVENT_ENTER)
precondition(state.nodeType == .inlineAttributes)
let parsedRange = state.range(state.node)
let childConversion = convertChildren(state)
let attributes = String(cString: cmark_node_get_attributes(state.node))
precondition(childConversion.state.node == state.node)
precondition(childConversion.state.event == CMARK_EVENT_EXIT)
return MarkupConversion(state: childConversion.state.next(), result: .inlineAttributes(attributes: attributes, parsedRange: parsedRange, childConversion.result))
}

static func parseString(_ string: String, source: URL?, options: ParseOptions) -> Document {
cmark_gfm_core_extensions_ensure_registered()

Expand Down
3 changes: 3 additions & 0 deletions Sources/Markdown/Rewriter/MarkupRewriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ extension MarkupRewriter {
public mutating func visitLink(_ link: Link) -> Result {
return defaultVisit(link)
}
public mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> Result {
return defaultVisit(attributes)
}
public mutating func visitSoftBreak(_ softBreak: SoftBreak) -> Result {
return defaultVisit(softBreak)
}
Expand Down
11 changes: 11 additions & 0 deletions Sources/Markdown/Visitor/MarkupVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,14 @@ public protocol MarkupVisitor {
- returns: The result of the visit.
*/
mutating func visitSymbolLink(_ symbolLink: SymbolLink) -> Result

/**
Visit an `InlineAttributes` element and return the result.

- parameter attribute: An `InlineAttributes` element.
- returns: The result of the visit.
*/
mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> Result
}

extension MarkupVisitor {
Expand Down Expand Up @@ -362,4 +370,7 @@ extension MarkupVisitor {
public mutating func visitSymbolLink(_ symbolLink: SymbolLink) -> Result {
return defaultVisit(symbolLink)
}
public mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> Result {
return defaultVisit(attributes)
}
}
24 changes: 24 additions & 0 deletions Sources/Markdown/Walker/Walkers/MarkupFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1120,4 +1120,28 @@ public struct MarkupFormatter: MarkupWalker {
print(symbolLink.destination ?? "", for: symbolLink)
print("``", for: symbolLink)
}

public mutating func visitInlineAttributes(_ attributes: InlineAttributes) {
let savedState = state
func printInlineAttributes() {
print("[", for: attributes)
descendInto(attributes)
print("](", for: attributes)
print(attributes.attributes, for: attributes)
print(")", for: attributes)
}

printInlineAttributes()

// Inline attributes *can* have their key-value pairs split across multiple
// lines as they are formatted as JSON5, however formatting the output as such
// gets into the realm of JSON formatting which might be out of scope of
// this formatter. Therefore if exceeded, prefer to print it on the next
// line to give as much opportunity to keep the attributes on one line.
if attributes.indexInParent > 0 && (isOverPreferredLineLimit || state.lineNumber > savedState.lineNumber) {
restoreState(to: savedState)
queueNewline()
printInlineAttributes()
}
}
}
4 changes: 4 additions & 0 deletions Sources/Markdown/Walker/Walkers/MarkupTreeDumper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,8 @@ struct MarkupTreeDumper: MarkupWalker {
dump(tableCell)
}
}

mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> () {
dump(attributes, customDescription: "attributes: `\(attributes.attributes)`")
}
}
49 changes: 49 additions & 0 deletions Tests/MarkdownTests/Inline Nodes/InlineAttributesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2021 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 XCTest
@testable import Markdown

class InlineAttributesTests: XCTestCase {
func testInlineAttributesAttributes() {
let attributes = "rainbow: 'extreme'"
let inlineAttributes = InlineAttributes(attributes: attributes)
XCTAssertEqual(attributes, inlineAttributes.attributes)
XCTAssertEqual(0, inlineAttributes.childCount)

let newAttributes = "rainbow: 'medium'"
var newInlineAttributes = inlineAttributes
newInlineAttributes.attributes = newAttributes
XCTAssertEqual(newAttributes, newInlineAttributes.attributes)
XCTAssertFalse(inlineAttributes.isIdentical(to: newInlineAttributes))
}

func testInlineAttributesFromSequence() {
let children = [Text("Hello, world!")]
let inlineAttributes = InlineAttributes(attributes: "rainbow: 'extreme'", children)
let expectedDump = """
InlineAttributes attributes: `rainbow: 'extreme'`
└─ Text "Hello, world!"
"""
XCTAssertEqual(expectedDump, inlineAttributes.debugDescription())
}

func testParseInlineAttributes() {
let source = "^[Hello, world!](rainbow: 'extreme')"
let document = Document(parsing: source)
let expectedDump = """
Document @1:1-1:37
└─ Paragraph @1:1-1:37
└─ InlineAttributes @1:1-1:37 attributes: `rainbow: 'extreme'`
└─ Text @1:3-1:16 "Hello, world!"
"""
XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations))
}
}