Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Footnotes #129

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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: 1 addition & 1 deletion [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ let package = Package(
if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
// Building standalone, so fetch all dependencies remotely.
package.dependencies += [
.package(url: "https://github.com/apple/swift-cmark.git", .branch("gfm")),
.package(url: "https://github.com/apple/swift-cmark.git", .branch("QuietMisdreavus/footnote-fixes")),
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "1.0.1")),
]

Expand Down
4 changes: 4 additions & 0 deletions Sources/Markdown/Base/Markup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func makeMarkup(_ data: _MarkupData) -> Markup {
return DoxygenParameter(data)
case .doxygenReturns:
return DoxygenReturns(data)
case .footnoteReference:
return FootnoteReference(data)
case .footnoteDefinition:
return FootnoteDefinition(data)
}
}

Expand Down
12 changes: 12 additions & 0 deletions Sources/Markdown/Base/RawMarkup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ enum RawMarkupData: Equatable {

case doxygenParam(name: String)
case doxygenReturns
case footnoteReference(footnoteID: String)
case footnoteDefinition(footnoteID: String)
}

extension RawMarkupData {
Expand Down Expand Up @@ -247,6 +249,11 @@ final class RawMarkup: ManagedBuffer<RawMarkupHeader, RawMarkup> {
static func blockDirective(name: String, nameLocation: SourceLocation?, argumentText: DirectiveArgumentText, parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {
return .create(data: .blockDirective(name: name, nameLocation: nameLocation, arguments: argumentText), parsedRange: parsedRange, children: children)
}

static func footnoteDefinition(footnoteID: String, parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {

return .create(data: .footnoteDefinition(footnoteID: footnoteID), parsedRange: parsedRange, children: children)
}

// MARK: Inline Creation

Expand Down Expand Up @@ -297,6 +304,11 @@ final class RawMarkup: ManagedBuffer<RawMarkupHeader, RawMarkup> {
static func inlineAttributes(attributes: String, parsedRange: SourceRange?, _ children: [RawMarkup]) -> RawMarkup {
return .create(data: .inlineAttributes(attributes: attributes), parsedRange: parsedRange, children: children)
}

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


// MARK: Extensions

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
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
*/

public struct FootnoteDefinition: BlockContainer {
public var _data: _MarkupData
init(_ raw: RawMarkup) throws {
guard case .footnoteDefinition = raw.data else {
throw RawMarkup.Error.concreteConversionError(from: raw, to: FootnoteDefinition.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 FootnoteDefinition {
// MARK: BasicBlockContainer

init<Children: Sequence>(footnoteID: String, _ children: Children) where Children.Element == BlockMarkup {
try! self.init(.footnoteDefinition(footnoteID: footnoteID, parsedRange: nil, children.map { $0.raw.markup }))
}

init(footnoteID: String, _ children: BlockMarkup...) {
self.init(footnoteID: footnoteID, children)
}

var footnoteID: String {
get {
guard case let .footnoteDefinition(footnoteID: footnoteID) = _data.raw.markup.data else {
fatalError("\(self) markup wrapped unexpected \(_data.raw)")
}
return footnoteID
}
set {
_data = _data.replacingSelf(.footnoteDefinition(footnoteID: newValue, parsedRange: nil, _data.raw.markup.copyChildren()))
}
}

// MARK: Visitation

func accept<V: MarkupVisitor>(_ visitor: inout V) -> V.Result {
return visitor.visitFootnoteDefinition(self)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
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 reference to a footnote
public struct FootnoteReference: InlineMarkup, InlineContainer {
public var _data: _MarkupData

init(_ raw: RawMarkup) throws {
guard case .footnoteReference = raw.data else {
throw RawMarkup.Error.concreteConversionError(from: raw, to: FootnoteReference.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 FootnoteReference {
init<Children: Sequence>(footnoteID: String, _ children: Children) where Children.Element == RecurringInlineMarkup {
try! self.init(.footnoteReference(footnoteID: footnoteID, parsedRange: nil, children.map { $0.raw.markup }))
}

init(footnoteID: String, _ children: RecurringInlineMarkup...) {
self.init(footnoteID: footnoteID, children)
}

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

// MARK: Visitation

func accept<V: MarkupVisitor>(_ visitor: inout V) -> V.Result {
return visitor.visitFootnoteReference(self)
}
}
32 changes: 32 additions & 0 deletions Sources/Markdown/Parser/CommonMarkConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ fileprivate enum CommonMarkNodeType: String {
case image
case inlineAttributes = "attribute"
case none = "NONE"
case footnoteReference = "footnote_reference"
case footnoteDefinition = "footnote_definition"
case unknown = "<unknown>"

// Extensions
Expand All @@ -68,6 +70,7 @@ fileprivate enum CommonMarkNodeType: String {
case tableCell = "table_cell"

case taskListItem = "tasklist"

}

/// Represents the result of a cmark conversion: the current `MarkupConverterState` and the resulting converted node.
Expand Down Expand Up @@ -230,6 +233,10 @@ struct MarkupParser {
return convertTableRow(state)
case .tableCell:
return convertTableCell(state)
case .footnoteReference:
return convertFootnoteReference(state)
case .footnoteDefinition:
return convertFootnoteDefinition(state)
case .inlineAttributes:
return convertInlineAttributes(state)
default:
Expand Down Expand Up @@ -591,20 +598,45 @@ struct MarkupParser {
precondition(childConversion.state.event == CMARK_EVENT_EXIT)
return MarkupConversion(state: childConversion.state.next(), result: .inlineAttributes(attributes: attributes, parsedRange: parsedRange, childConversion.result))
}

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

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

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

var cmarkOptions = CMARK_OPT_TABLE_SPANS
if !options.contains(.disableSmartOpts) {
cmarkOptions |= CMARK_OPT_SMART
cmarkOptions |= CMARK_OPT_FOOTNOTES
}

let parser = cmark_parser_new(cmarkOptions)

cmark_parser_attach_syntax_extension(parser, cmark_find_syntax_extension("table"))
cmark_parser_attach_syntax_extension(parser, cmark_find_syntax_extension("strikethrough"))
cmark_parser_attach_syntax_extension(parser, cmark_find_syntax_extension("tasklist"))


cmark_parser_feed(parser, string, string.utf8.count)
let rawDocument = cmark_parser_finish(parser)
let initialState = MarkupConverterState(source: source, iterator: cmark_iter_new(rawDocument), event: CMARK_EVENT_NONE, node: nil, options: options, headerSeen: false, pendingTableBody: nil).next()
Expand Down
7 changes: 7 additions & 0 deletions Sources/Markdown/Rewriter/MarkupRewriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,11 @@ extension MarkupRewriter {
public mutating func visitText(_ text: Text) -> Result {
return defaultVisit(text)
}
public mutating func visitFootnoteReference(_ footnoteReference: FootnoteReference) -> Result {
return defaultVisit(footnoteReference)
}

public mutating func visitFootnoteDefinition(_ footnoteDefinition: FootnoteDefinition) -> Result {
return defaultVisit(footnoteDefinition)
}
}
11 changes: 11 additions & 0 deletions Sources/Markdown/Visitor/MarkupVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ public protocol MarkupVisitor {
- returns: The result of the visit.
*/
mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> Result

mutating func visitFootnoteReference(_ footnoteReference: FootnoteReference) -> Result

mutating func visitFootnoteDefinition(_ footnoteDefinition: FootnoteDefinition) -> Result

/**
Visit a `DoxygenParam` element and return the result.
Expand Down Expand Up @@ -395,4 +399,11 @@ extension MarkupVisitor {
public mutating func visitDoxygenReturns(_ doxygenReturns: DoxygenReturns) -> Result {
return defaultVisit(doxygenReturns)
}

public mutating func visitFootnoteReference(_ footnoteReference: FootnoteReference) -> Result {
return defaultVisit(footnoteReference)
}
public mutating func visitFootnoteDefinition(_ footnoteDefinition: FootnoteDefinition) -> Result {
return defaultVisit(footnoteDefinition)
}
}
9 changes: 9 additions & 0 deletions Sources/Markdown/Walker/Walkers/MarkupTreeDumper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,4 +290,13 @@ struct MarkupTreeDumper: MarkupWalker {
mutating func visitDoxygenParameter(_ doxygenParam: DoxygenParameter) -> () {
dump(doxygenParam, customDescription: "parameter: \(doxygenParam.name)")
}

mutating func visitFootnoteReference(_ footnoteReference: FootnoteReference) -> () {
dump(footnoteReference, customDescription: "footnoteID: `\(footnoteReference.footnoteID)`")
}

mutating func visitFootnoteDefinition(_ footnoteDefinition: FootnoteDefinition) -> () {
dump(footnoteDefinition, customDescription: "footnoteID: `\(footnoteDefinition.footnoteID)`")
}

}
2 changes: 2 additions & 0 deletions Sources/markdown-tool/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct MarkdownCommand: ParsableCommand {
])

static func parseFile(at path: String, options: ParseOptions) throws -> (source: String, parsed: Document) {
print(path)
print(Process().currentDirectoryPath)
let data = try Data(contentsOf: URL(fileURLWithPath: path))
guard let inputString = String(data: data, encoding: .utf8) else {
throw Error.couldntDecodeInputAsUTF8
Expand Down
36 changes: 36 additions & 0 deletions Tests/MarkdownTests/Parsing/FootnoteTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
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
*/

@testable import Markdown
import XCTest

class FootnoteTests: XCTestCase {
func testFootnotes() {
let text = """
text with a footnote [^1].

[^1]: footnote definition.
"""

let expectedDump = """
Document @1:1-3:27
├─ Paragraph @1:1-1:27
│ ├─ Text @1:1-1:22 "text with a footnote "
│ ├─ FootnoteReference @1:22-1:26 footnoteID: `1`
│ └─ Text @1:26-1:27 "."
└─ FootnoteDefinition @3:7-3:27 footnoteID: `1`
└─ Paragraph @3:7-3:27
└─ Text @3:7-3:27 "footnote definition."
"""

let document = Document(parsing: text, source: nil, options: [.parseBlockDirectives, .parseSymbolLinks])
XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations))
}
}