Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

Overview

Swift Markdown

Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

The parser is powered by GitHub-flavored Markdown's cmark-gfm implementation, so it follows the spec closely. As the needs of the community change, the effective dialect implemented by this library may change.

The markup tree provided by this package is comprised of immutable/persistent, thread-safe, copy-on-write value types that only copy substructure that has changed. Other examples of the main strategy behind this library can be seen in Swift's lib/Syntax and its Swift bindings, SwiftSyntax.

Getting Started Using Markup

In your Package.swift Swift Package Manager manifest, add the following dependency to your dependencies argument:

.package(url: "ssh://[email protected]/apple/swift-markdown.git", .branch("main")),

Add the dependency to any targets you've declared in your manifest:

.target(name: "MyTarget", dependencies: ["Markdown"]),

Parsing

To parse a document, use Document(parsing:), supplying a String or URL:

import Markdown

let source = "This is a markup *document*."
let document = Document(parsing: source)
print(document.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is a markup "
//    ├─ Emphasis
//    │  └─ Text "document"
//    └─ Text "."

Parsing text is just one way to build a tree of Markup elements. You can also build them yourself declaratively.

Building Markup Trees

You can build trees using initializers for the various element types provided.

import Markdown

let document = Document(
    Paragraph(
        Text("This is a "),
        Emphasis(
            Text("paragraph."))))

This would be equivalent to parsing "This is a *paragraph.*" but allows you to programmatically insert content from other data sources into individual elements.

Modifying Markup Trees with Persistence

Swift Markdown uses a persistent tree for its backing storage, providing effectively immutable, copy-on-write value types that only copy the substructure necessary to create a unique root without affecting the previous version of the tree.

Modifying Elements Directly

If you just need to make a quick change, you can modify an element anywhere in a tree, and Swift Markdown will create copies of substructure that cannot be shared.

import Markdown

let source = "This is *emphasized.*"
let document = Document(parsing: source)
print(document.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is "
//    └─ Emphasis
//       └─ Text "emphasized."

var text = document.child(through:
    0, // Paragraph
    1, // Emphasis
    0) as! Text // Text

text.string = "really emphasized!"
print(text.root.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is "
//    └─ Emphasis
//       └─ Text "really emphasized!"

// The original document is unchanged:

print(document.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is "
//    └─ Emphasis
//       └─ Text "emphasized."

If you find yourself needing to systematically change many parts of a tree, or even provide a complete transformation into something else, maybe the familiar Visitor Pattern is what you want.

Visitors, Walkers, and Rewriters

There is a core MarkupVisitor protocol that provides the basis for transforming, walking, or rewriting a markup tree.

public protocol MarkupVisitor {
    associatedtype Result
}

Using its Result type, you can transform a markup tree into anything: another markup tree, or perhaps a tree of XML or HTML elements. There are two included refinements of MarkupVisitor for common uses.

The first refinement, MarkupWalker, has an associated Result type of Void, so it's meant for summarizing or detecting aspects of a markup tree. If you wanted to append to a string as elements are visited, this might be a good tool for that.

import Markdown

/// Counts `Link`s in a `Document`.
struct LinkCounter: MarkupWalker {
    var count = 0
    mutating func visitLink(_ link: Link) {
        if link.destination == "https://swift.org" {
            count += 1
        }
        descendInto(link)
    }
}

let source = "There are [two](https://swift.org) links to 
   
     here.
    "
   
let document = Document(parsing: source)
print(document.debugDescription())
var linkCounter = LinkCounter()
linkCounter.visit(document)
print(linkCounter.count)
// 2

The second refinement, MarkupRewriter, has an associated Result type of Markup?, so it's meant to change or even remove elements from a markup tree. You can return nil to delete an element, or return another element to substitute in its place.

import Markdown

/// Delete all **strong** elements in a markup tree.
struct StrongDeleter: MarkupRewriter {
    mutating func visitStrong(_ strong: Strong) -> Markup? {
        return nil
    }
}

let source = "Now you see me, **now you don't**"
let document = Document(parsing: source)
var strongDeleter = StrongDeleter()
let newDocument = strongDeleter.visit(document)

print(newDocument!.debugDescription())
// Document
// └─ Paragraph
//    └─ Text "Now you see me, "

Block Directives

Swift Markdown includes a syntax extension for attributed block elements. See Block Directive documentation for more information.

Getting Involved

Submitting a Bug Report

Swift Markdown tracks all bug reports with Swift JIRA. You can use the "SwiftMarkdown" component for issues and feature requests specific to Swift Markdown. When you submit a bug report we ask that you follow the Swift Bug Reporting guidelines and provide as many details as possible.

Submitting a Feature Request

For feature requests, please feel free to create an issue on Swift JIRA with the New Feature type or start a discussion on the Swift Forums.

Don't hesitate to submit a feature request if you see a way Swift Markdown can be improved to better meet your needs.

Contributing to Swift Markdown

Please see the contributing guide for more information.

Comments
  • Bump Swift version

    Bump Swift version

    Summary

    Bump the Swift version Package.swift use to 5.4. And add a new 5.5 Package.swift file

    Checklist

    Make sure you check off the following items. If they cannot be completed, provide a reason.

    • [x] Ran the ./bin/test script and it succeeded
    opened by Kyle-Ye 19
  • Add support for custom attributes

    Add support for custom attributes

    Currently the library will crash if you input Markdown such as the following:

    Here is a ^[custom attribute](rainbow: 'extreme')
    

    This is because while the swift-cmark side of things is knowledgeable about custom attributes (also see WWDC21 "What's New in Foundation @ 10:12" for its usage), this library's Swift counterpart to the C code isn't aware of custom attributes, and fatal errors with an unknown type if such an example is encountered. (Ask me how I know!)

    This PR adds a CustomAttributes Swift type into the library as well as some functions to get it to work, thereby preventing the crash and letting users implement custom attributes in their Walkers/Visitors/Rewriters.

    Some design decisions I wasn't 100% sure of:

    • The name CustomAttributes. The plural does seem necessary as the actual custom attributes (per the WWDC video) can be JSON5 with any number of attributes, rather than just 1 in the example above. Custom as a prefix is also debatable, but Attributes felt too generic, and CustomBlock already exists.
    • For the user-facing Walker API, I wasn't sure if the CustomAttributes object should have a value that is a String (the raw string/JSON representing the attributes), or a [String: AnyHashable] dictionary where the values are actually broken down. I erred on the side of a string, as it would allow the user to ingest it more flexibly, through a custom Codable implementation, JSONSerialization, etc. rather trivially, instead of the API trying to guess what they want their output as.
    • In the same vein, the output for MarkupFormatter could technically have each key-value pair on a separate line to better adhere to preferredLineLimit, however formatting the output as such also gets into the weeds of JSON formatting, proper indentation, and things likely beyond the scope of the formatter so I elected not to.

    (Thank you to @QuietMisdreavus for help.)

    opened by christianselig 17
  • Fix macOS deprecated API

    Fix macOS deprecated API

    Bug/issue #, if applicable:

    Summary

    Fix deprecated FileHandle.readDataToEndOfFile, Process.launch and Process.launchPath API on macOS since they can be removed at a future release.

    Checklist

    Make sure you check off the following items. If they cannot be completed, provide a reason.

    • [x] Ran the ./bin/test script and it succeeded
    opened by Kyle-Ye 11
  • Fix single-line directive parsing issue

    Fix single-line directive parsing issue

    Bug/issue #, if applicable:

    • [ ] Close #64
    • [x] Close #65

    Summary

    Before this PR, the block directive parser will just ignore the content after "{" in the same line.

    After the PR, the behavior becomes the following:

    @xx { yy           // "yy" will be ignored
    @xx { yy }         // "yy" will be parsed
    @xx { yy } zz }    // "yy } zz" will be parsed. It can be "yy } zz" or "yy". My implementation choose it to the former.
    

    Checklist

    Make sure you check off the following items. If they cannot be completed, provide a reason.

    • [x] Added tests
    • [x] Ran the ./bin/test script and it succeeded
    • [x] Updated documentation if necessary
    opened by Kyle-Ye 9
  • Update Aside to parse custom titles

    Update Aside to parse custom titles

    Bug/issue #, if applicable: rdar://65856948 #59

    Summary

    Adds support for parsing custom aside titles.

    Note: This is a source-breaking change for the Aside.Kind API and removes case-insensitivity support for aside tags.

    Dependencies

    https://github.com/apple/swift-docc/pull/303

    Testing

    See https://github.com/apple/swift-docc/pull/303 for details.

    Checklist

    Make sure you check off the following items. If they cannot be completed, provide a reason.

    • [X] Added tests
    • [x] Ran the ./bin/test script and it succeeded
    • [X] Updated documentation if necessary
    api-breaking-change 
    opened by micpringle 7
  • Revert

    Revert "revert" to test toolchain change

    • Revert "Revert "Fix deprecated launch and launchPath API on Process""
    • Revert "Revert "Fix deprecated readDataToEndOfFile API""
    • Revert "Revert "Bump Swift version (#4)" (#11)"

    Bug/issue #, if applicable:

    Summary

    Revert #11 and #12 to test the change in the toolchain build. @franklinsch

    Checklist

    Make sure you check off the following items. If they cannot be completed, provide a reason.

    • [x] Ran the ./bin/test script and it succeeded
    opened by Kyle-Ye 7
  • Fix MarkupTest's missing array code convention

    Fix MarkupTest's missing array code convention

    Summary

    Found the missing code convention of the array in the MarkupTest code. So, add it to this.

    Checklist

    • [x] Added tests
    • [x] Ran the ./bin/test script and it succeeded
    opened by GREENOVER 7
  • Improve performance of `Markup.child(at:)` method while still accurately tracking metadata

    Improve performance of `Markup.child(at:)` method while still accurately tracking metadata

    Summary

    While investigating general performance improvements in Swift-DocC for:

    • https://github.com/apple/swift-docc/pull/166

    I found that Swift-DocC was spending a lot of time creating iterators while rewriting markup elements in Swift-Markdown code using MarkupRewriters. This PR is a refactoring of the Markup.child(at:) method to improve performance.

    I put up a PR with a similar change in #36 where @bitjammer pointed out that I was incorrectly tracking metadata for child elements. This is a second iteration on that PR that resolves that issue and adds a test to catch the issue I missed before. It appears to have the same performance win as the original PR when testing with the Swift-DocC performance suite.

    Testing

    I added tests that confirm the metadata returned for child elements via the new child(at:) implementation is the same as the metadata for child elements returned via the existing sequence implementation. Swift-Markdown and Swift-DocC's existing tests continue to pass with this change.

    Checklist

    Make sure you check off the following items. If they cannot be completed, provide a reason.

    • [x] Added tests
    • [x] Ran the ./bin/test script and it succeeded
    • [x] Updated documentation if necessary
    opened by ethan-kusters 6
  • store ordered list start index from cmark

    store ordered list start index from cmark

    Bug/issue #, if applicable: rdar://73847907

    Summary

    While cmark-gfm stores the start index for an ordered list, swift-markdown currently discards this information. This PR is part of a set to use this start index when handling ordered lists.

    I also added license headers to the markdown in the doc bundle so that bin/check-source can pass.

    Dependencies

    None

    Testing

    With the following markdown test file, ensure that the debug tree properly contains the start index of the list.

    2. this is a test
    3. this is a test
    4. this is a test
    

    Steps:

    1. swift run markdown-tool dump-tree test.md
    2. Compare the output to the below sample and ensure that the start index is preserved:
    Document
    └─ OrderedList start: 2
       ├─ ListItem
       │  └─ Paragraph
       │     └─ Text "this is a test"
       ├─ ListItem
       │  └─ Paragraph
       │     └─ Text "this is a test"
       └─ ListItem
          └─ Paragraph
             └─ Text "this is a test"
    

    Checklist

    Make sure you check off the following items. If they cannot be completed, provide a reason.

    • [x] Added tests
    • [x] Ran the ./bin/test script and it succeeded
    • [x] Updated documentation if necessary
    opened by QuietMisdreavus 6
  • MarkdownText powered by swift-markdown

    MarkdownText powered by swift-markdown

    Just wanted to say thanks for open sourcing this. 👏

    This enabled me to build a really cool open source package MarkdownText that provides similar support to Apples iOS 15+ Text markdown support.

    But it works back to iOS 13+ and macOS 11+ and even includes a lot of styling APIs.

    Bit of a shameless plug but wanted to mention it here in case it's useful to anyone else.

    • Headings
    • Paragraphs
    • Quotes
    • Inline formatting
      • Strong/Bold
      • Emphasis/Italic
      • Strikethrough
      • Code
      • Links (non interactive only)
    • Lists
      • Ordered
      • Unordered
      • Checklist (GitHub style)
    • Thematic Breaks
    • Code Blocks
    • Images

    A full backport of AsyncImage is included to make image support simple and performant.

    Since it's pure SwiftUI you can even easily animate content which is a nice plus.

    opened by shaps80 5
  • DocC drops content have single-line directive

    DocC drops content have single-line directive

    Description

    When compiling this documentation comment:

    /// Hello
    ///
    /// Some content
    ///
    /// @Comment { This is a comment }
    ///
    /// Hello World
    

    The text after the comment directive incorrectly gets dropped. This doesn't reproduce when writing the directive over multiple lines.

    Checklist

    • [X] If possible, I've reproduced the issue using the main branch of this package.
    • [X] This issue hasn't been addressed in an existing GitHub issue.

    Expected Behavior

    DocC should not drop content after single-line directives.

    Actual behavior

    No response

    Steps To Reproduce

    No response

    Swift-DocC Version Information

    Swift 5.7

    Swift Compiler Version Information

    No response

    bug 
    opened by franklinsch 5
  • Support for single backtick (`) in code voice

    Support for single backtick (`) in code voice

    This issue appears is input like the following:

    > *identifier* → **`` ` ``** *identifier-head* *identifier-characters*_?_ **`` ` ``**
    

    The CommonMark definition of a code voice permits one or more backticks, which supports exactly this sort of scenario where a backtick needs to appear in code voice. Additionally, escaping via backslash isn't allowed in code voice.

    The issue here is a collision with DocC's extended markup using double-backticks surround links to API symbols. Currently, building "The Swift Programming Language" with top-of-tree docc, the output is correct but we get warnings like the following:

    Topic reference '`' couldn't be resolved. No local documentation matches this reference.

    Trying 3 or 4 backticks to start/end the code voice produces the same result.

    opened by amartini51 0
  • Run of tildes in HTML comments confuses/reverses the at-directive parser

    Run of tildes in HTML comments confuses/reverses the at-directive parser

    The at-directive parser looks for runs of ~ or ` to delimit the start and end of code blocks. It knows that an at-directive can't start by @foo inside a code block. However, if one of those lines appears in an HTML comment, the at-directive parser doesn't ignore the line. This results in the "not in a code block" logic being inverted. For example, we ran into this issue in "The Swift Programming Language" (reduced example):

    <!--
      Adding Child Tasks to a Task Group
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      
      - Creating a group with ``withTaskGroup`` and ``withThrowingTaskGroup``
      
      - awaiting ``withGroup`` means waiting for all child tasks to complete
    -->
    
    ```
    struct NonsendableTemperatureReading {
        var measurement: Int
    }
    
    @available(*, unavailable)
    extension NonsendableTemperatureReading: Sendable { }
    ```
    

    In this case, @available is treated as the start of an at-directive and the code listing is mis-parsed and displays as follows:

    199356359-535a8a12-6acd-4033-b709-82c45c38c6f1

    rdar://101828693

    opened by amartini51 1
  • Hard breaks aren't reflected in output

    Hard breaks aren't reflected in output

    Description

    The CommonMark spec allows writers to specify a line break (not a paragraph break) by writing two or more spaces at the end of the line, or by writing a backslash (\) at the end of the line. Currently, the rendered content from DocC doesn't break the line at that location.

    Example input:

    This text should\
    appear on two lines.
    
    This is the second paragraph.
    

    Checklist

    • [X] If possible, I've reproduced the issue using the main branch of this package.
    • [X] This issue hasn't been addressed in an existing GitHub issue.

    rdar://101684224

    bug good first issue 
    opened by amartini51 3
  • HTML comment that contains @ causes stray " in output">

    HTML comment that contains @ causes stray "–>" in output

    Description

    Input like the following causes the issue:

    Hi there.
    
    <!--
      @test
    -->
    
    image

    In contrast, this renders correctly:

    Hi there.
    
    <!--
      test
    -->
    
    image

    It seems like DocC is trying to parse an @ directive block in the middle of the comment.

    Checklist

    • [X] If possible, I've reproduced the issue using the main branch of this package.
    • [X] This issue hasn't been addressed in an existing GitHub issue.

    rdar://101144032

    bug 
    opened by amartini51 3
  • Allow adding `id` attributes to arbitrary elements

    Allow adding `id` attributes to arbitrary elements

    Radar: rdar://97350693

    The standard Markdown behavior is to add a link anchor (id attribute) to headings, based on the text of the heading. https://github.com/apple/swift-docc/issues/345 is about customizing these heading anchors. However, in some situations you may want to add a link anchor somewhere that's not a heading - e.g. a figure with caption, a term-definition list item, etc.

    Some prior art exists in MultiMarkdown, which allows you to add arbitrary attributes to a link or image, which can include an id attribute. This is restricted to links and images, but the syntax could be used as a jumping-off point, if it's going to be implemented in base Markdown.

    I'm unsure about the ultimate syntax this should take - if we follow syntax like MultiMarkdown or PHP Markdown Extra, we'll likely need to patch swift-cmark for it. However, if we add a directive for it, it's possible that we could leave the implementation wholly here in Swift-Markdown.

    opened by QuietMisdreavus 0
Owner
Apple
Apple
Swift Package Manager plugin which runs ActionBuilder to create a Github Actions workflow for a swift package.

ActionBuilderPlugin A Swift Package Manager command which builds a Github Actions workflow for the current package. By default the workflow file will

Elegant Chaos 4 Jul 20, 2022
Parsing indeterminate types with Decodable and Either enum using Swift

Decodable + Either Parsing indeterminate types with Decodable and Either enum us

Alonso Alvarez 1 Jan 9, 2022
Framework for easily parsing your JSON data directly to Swift object.

Server sends the all JSON data in black and white format i.e. its all strings & we make hard efforts to typecast them into their respective datatypes

Mukesh 11 Oct 17, 2022
Tools and helpers to make building apps faster and safer.

The UBFoundation framework provides a set of useful tools and helpers to make building apps faster and safer.

Ubique 7 Dec 19, 2022
IOS-Bootcamp-Examples - Learn to Swift while building apps - With IOS Development Bootcamp

IOS-Bootcamp-Examples Learn to Swift while building apps - With IOS Development

Bilge Çakar 9 Dec 21, 2022
A result builder that allows to define shape building closures

ShapeBuilder A result builder implementation that allows to define shape building closures and variables. Problem In SwiftUI, you can end up in a situ

Daniel Peter 47 Dec 2, 2022
A danger-swift plug-in to manage/post danger checking results with markdown style

DangerSwiftShoki A danger-swift plug-in to manage/post danger checking results with markdown style Install DangerSwiftShoki SwiftPM (Recommended) Add

YUMEMI Inc. 4 Dec 17, 2021
Generate Markdown documentation from source code

SourceDocs SourceDocs is a command line tool that generates markdown documentation files from inline source code comments. Similar to Sphinx or Jazzy,

Eneko Alonso 349 Dec 10, 2022
A command-line tool and Swift Package for generating class diagrams powered by PlantUML

SwiftPlantUML Generate UML class diagrams from swift code with this Command Line Interface (CLI) and Swift Package. Use one or more Swift files as inp

null 374 Jan 3, 2023
A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.

ZamzamKit ZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and othe

Zamzam Inc. 261 Dec 15, 2022
Swift package adding fraction and percentage types.

swift-rationals Rationals is a package containing Fraction and Percentage types for the Swift programming language. Contents The package currently pro

Alexandre H. Saad 0 Jul 28, 2022
Units is a Swift package to manipulate, compare, and convert between physical quantities.

Units ?? Units is a Swift package to manipulate, compare, and convert between physical quantities. This package models measurements, which are a numer

Jay Herron 3 Jun 22, 2022
WWDCKit - Creating and Using a Swift Package

WWDCKit - Creating and Using a Swift Package 1. Create the Package in Xcode Navigate to File >> New >> Package. Give the Package a name e.g "WWDCKit".

Alex Paul 6 Dec 11, 2022
Protected is a Swift Package that allows you to specify the read and write rights for any type, depending on context by using Phantom types

Protected is a Swift Package that allows you to specify the read and write rights for any type, depending on context by using Phantom types

Mathias Quintero 9 Sep 25, 2022
Approximate is a Swift package that provides implementations of floating point comparisons for the Swift ecosystem

Approximate Approximate floating point equality comparisons for the Swift Programming Language. Introduction Approximate is a Swift package that provi

Christopher Blanchard 1 Jun 1, 2022
A simple swift package that provides a Swift Concurrency equivalent to `@Published`.

AsyncValue This is a simple package that provides a convenience property wrapper around AsyncStream that behaves almost identically to @Published. Ins

Brent Mifsud 33 Oct 3, 2022
Swift implementation of the package url spec

PackageURL Swift implementation of the package url specification. Requirements Swift 5.3+ Usage import PackageURL let purl: PackageURL = "pkg:swift/a

Mattt 21 Jun 14, 2022
Swift Package for Decoding RSS Feeds.

SyndiKit Swift Package built on top of XMLCoder for Decoding RSS Feeds. Check out the DocC-Built Site! Table of Contents Introduction Features Install

BrightDigit 34 Dec 27, 2022
A simple Swift package for counting the Syllables in a sentence.

A simple Swift package for counting the Syllables in a sentence.

null 2 Jan 3, 2022