├── .github └── pull_request_template.md ├── .gitignore ├── .spi.yml ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── Package.swift ├── README.md ├── Snippets ├── Formatting │ ├── CondenseAutolinks.swift │ ├── CustomLinePrefix.swift │ ├── DefaultFormatting.swift │ ├── EmphasisMarkers.swift │ ├── MaximumWidth.swift │ ├── OrderedListNumerals.swift │ ├── PreferredHeadingStyle.swift │ ├── ThematicBreakCharacter.swift │ ├── UnorderedListMarker.swift │ └── UseCodeFence.swift ├── Parsing │ ├── ParseDocumentFile.swift │ ├── ParseDocumentString.swift │ └── test.md ├── Querying │ └── ChildThrough.swift ├── Rewriters │ ├── RemoveElementKind.swift │ └── ReplaceText.swift ├── Visitors │ └── XMLConverter.swift └── Walkers │ └── LinkCollector.swift ├── Sources ├── CAtomic │ ├── CAtomic.c │ ├── CMakeLists.txt │ └── include │ │ ├── CAtomic.h │ │ └── module.modulemap ├── CMakeLists.txt └── Markdown │ ├── Base │ ├── ChildIndexPath.swift │ ├── DirectiveArgument.swift │ ├── Document.swift │ ├── LiteralMarkup.swift │ ├── Markup.swift │ ├── MarkupChildren.swift │ ├── MarkupData.swift │ ├── PlainTextConvertibleMarkup.swift │ └── RawMarkup.swift │ ├── Block Nodes │ ├── Block Container Blocks │ │ ├── BlockDirective.swift │ │ ├── BlockQuote.swift │ │ ├── CustomBlock.swift │ │ ├── Doxygen Commands │ │ │ ├── DoxygenAbstract.swift │ │ │ ├── DoxygenDiscussion.swift │ │ │ ├── DoxygenNote.swift │ │ │ ├── DoxygenParameter.swift │ │ │ └── DoxygenReturns.swift │ │ ├── ListItem.swift │ │ ├── OrderedList.swift │ │ └── UnorderedList.swift │ ├── Inline Container Blocks │ │ └── Paragraph.swift │ ├── Leaf Blocks │ │ ├── CodeBlock.swift │ │ ├── HTMLBlock.swift │ │ ├── Heading.swift │ │ └── ThematicBreak.swift │ └── Tables │ │ ├── Table.swift │ │ ├── TableBody.swift │ │ ├── TableCell.swift │ │ ├── TableCellContainer.swift │ │ ├── TableHead.swift │ │ └── TableRow.swift │ ├── CMakeLists.txt │ ├── Infrastructure │ ├── Replacement.swift │ └── SourceLocation.swift │ ├── Inline Nodes │ ├── Inline Containers │ │ ├── Emphasis.swift │ │ ├── Image.swift │ │ ├── InlineAttributes.swift │ │ ├── Link.swift │ │ ├── Strikethrough.swift │ │ └── Strong.swift │ └── Inline Leaves │ │ ├── CustomInline.swift │ │ ├── InlineCode.swift │ │ ├── InlineHTML.swift │ │ ├── LineBreak.swift │ │ ├── SoftBreak.swift │ │ ├── SymbolLink.swift │ │ └── Text.swift │ ├── Interpretive Nodes │ └── Aside.swift │ ├── Markdown.docc │ ├── Markdown.md │ ├── Markdown │ │ ├── BlockDirectives.md │ │ ├── BlockMarkup.md │ │ ├── DoxygenCommands.md │ │ ├── FormatterAndOptions.md │ │ ├── Infrastructure.md │ │ ├── InlineMarkup.md │ │ └── VisitMarkup.md │ ├── Parsing-Building-and-Modifying Markup-Trees.md │ ├── Snippets.md │ └── Visitors-Walkers-and-Rewriters.md │ ├── Parser │ ├── BlockDirectiveParser.swift │ ├── CommonMarkConverter.swift │ ├── LazySplitLines.swift │ ├── ParseOptions.swift │ ├── RangeAdjuster.swift │ └── RangerTracker.swift │ ├── Rewriter │ └── MarkupRewriter.swift │ ├── Structural Restrictions │ ├── BasicBlockContainer.swift │ ├── BasicInlineContainer.swift │ ├── BlockContainer.swift │ ├── BlockMarkup.swift │ ├── InlineContainer.swift │ ├── InlineMarkup.swift │ └── ListItemContainer.swift │ ├── Utility │ ├── AtomicCounter.swift │ ├── CharacterExtensions.swift │ ├── CollectionExtensions.swift │ └── StringExtensions.swift │ ├── Visitor │ └── MarkupVisitor.swift │ └── Walker │ ├── MarkupWalker.swift │ └── Walkers │ ├── HTMLFormatter.swift │ ├── MarkupFormatter.swift │ └── MarkupTreeDumper.swift ├── Tests └── MarkdownTests │ ├── Base │ ├── AtomicCounterTests.swift │ ├── HierarchyTests.swift │ ├── MarkupIdentifierTests.swift │ ├── MarkupTests.swift │ ├── ParsedRangePreservedAfterEditing.swift │ ├── PlainTextConvertibleMarkupTests.swift │ ├── RawMarkupTests.swift │ ├── RawMarkupToMarkupTests.swift │ └── StableIdentifierTests.swift │ ├── Block Nodes │ ├── CodeBlockTests.swift │ ├── DocumentTests.swift │ ├── HTMLBlockTests.swift │ ├── HeadingTests.swift │ ├── ParagraphTests.swift │ └── TableTests.swift │ ├── Infrastructure │ └── SourceLocationTests.swift │ ├── Inline Nodes │ ├── ImageTests.swift │ ├── InlineAttributesTests.swift │ ├── InlineCodeTests.swift │ ├── InlineHTMLTests.swift │ ├── LineBreakTests.swift │ ├── LinkTests.swift │ ├── SoftBreakTests.swift │ ├── SymbolLinkTests.swift │ └── TextTests.swift │ ├── Interpretive Nodes │ └── AsideTests.swift │ ├── Parsing │ ├── BacktickTests.swift │ ├── BlockDirectiveParserTests.swift │ ├── CommonMarkConverterTests.swift │ ├── DoxygenCommandParserTests.swift │ └── SourceURLTests.swift │ ├── Performance │ ├── EditPerformanceTests.swift │ └── MarkupChildrenPerformanceTests.swift │ ├── Structural Restrictions │ ├── BasicBlockContainerTests.swift │ ├── BasicInlineContainerTests.swift │ └── ListItemContainerTests.swift │ ├── Utility │ └── AssertElementDidntChange.swift │ └── Visitors │ ├── Everything.md │ ├── HTMLFormatterTests.swift │ ├── MarkupFormatterTests.swift │ ├── MarkupRewriterTests.swift │ ├── MarkupTreeDumperTests.swift │ ├── MarkupVisitorTests.swift │ └── MarkupWalkerTests.swift ├── Tools ├── Package.swift └── markdown-tool │ ├── Commands │ ├── DumpTreeCommand.swift │ ├── FormatCommand.swift │ └── PrintHTMLCommand.swift │ └── MarkdownCommand.swift ├── bin ├── check-source ├── test └── update-gh-pages-documentation-site └── cmake └── modules ├── CMakeLists.txt ├── SwiftMarkdownConfig.cmake.in └── SwiftSupport.cmake /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Bug/issue #, if applicable: 2 | 3 | ## Summary 4 | 5 | _Provide a description of what your PR addresses, explaining the expected user experience. 6 | Also, provide an overview of your implementation._ 7 | 8 | ## Dependencies 9 | 10 | _Describe any dependencies this PR might have, such as an associated branch in another repository._ 11 | 12 | ## Testing 13 | 14 | _Describe how a reviewer can test the functionality of your PR. Provide test content to test with if 15 | applicable._ 16 | 17 | Steps: 18 | 1. _Provide setup instructions._ 19 | 2. _Explain in detail how the functionality can be tested._ 20 | 21 | ## Checklist 22 | 23 | Make sure you check off the following items. If they cannot be completed, provide a reason. 24 | 25 | - [ ] Added tests 26 | - [ ] Ran the `./bin/test` script and it succeeded 27 | - [ ] Updated documentation if necessary 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This source file is part of the Swift.org open source project 2 | # 3 | # Copyright (c) 2021 Apple Inc. and the Swift project authors 4 | # Licensed under Apache License v2.0 with Runtime Library Exception 5 | # 6 | # See https://swift.org/LICENSE.txt for license information 7 | # See https://swift.org/CONTRIBUTORS.txt for Swift project authors 8 | 9 | .DS_Store 10 | .build 11 | /Packages 12 | /*.xcodeproj 13 | .swiftpm 14 | Package.resolved 15 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Markdown] 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | cmake_minimum_required(VERSION 3.19.0) 11 | 12 | if(POLICY CMP0077) 13 | cmake_policy(SET CMP0077 NEW) 14 | endif() 15 | 16 | if(POLICY CMP0091) 17 | cmake_policy(SET CMP0091 NEW) 18 | endif() 19 | 20 | project(SwiftMarkdown 21 | LANGUAGES C Swift) 22 | 23 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) 24 | 25 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 26 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 27 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 28 | 29 | set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) 30 | set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) 31 | 32 | include(FetchContent) 33 | include(SwiftSupport) 34 | 35 | set(_SM_VENDOR_DEPENDENCIES) 36 | 37 | set(BUILD_EXAMPLES NO) 38 | set(BUILD_TESTING NO) 39 | 40 | find_package(ArgumentParser CONFIG) 41 | if(NOT ArgumentParser_FOUND) 42 | FetchContent_Declare(ArgumentParser 43 | GIT_REPOSITORY https://github.com/apple/swift-argument-parser 44 | GIT_TAG 1.2.3) 45 | list(APPEND _SM_VENDOR_DEPENDENCIES ArgumentParser) 46 | endif() 47 | 48 | find_package(cmark-gfm CONFIG) 49 | if(NOT cmark-gfm_FOUND) 50 | FetchContent_Declare(cmark-gfm 51 | GIT_REPOSITORY https://github.com/apple/swift-cmark 52 | GIT_TAG gfm) 53 | list(APPEND _SM_VENDOR_DEPENDENCIES cmark-gfm) 54 | endif() 55 | 56 | if(_SM_VENDOR_DEPENDENCIES) 57 | FetchContent_MakeAvailable(${_SM_VENDOR_DEPENDENCIES}) 58 | endif() 59 | 60 | add_subdirectory(Sources) 61 | add_subdirectory(cmake/modules) 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | By submitting a pull request, you represent that you have the right to license your 2 | contribution to Apple and the community, and agree by submitting the patch that 3 | your contributions are licensed under the [Swift license](https://swift.org/LICENSE.txt). 4 | 5 | --- 6 | 7 | Before submitting the pull request, please make sure you have tested your changes 8 | and that they follow the Swift project [guidelines for contributing 9 | code](https://swift.org/contributing/#contributing-code). 10 | 11 | 12 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | The Swift Markdown Project 3 | ========================== 4 | 5 | Please visit the Swift Markdown web site for more information: 6 | 7 | * https://github.com/apple/swift-markdown 8 | 9 | Copyright (c) 2021 Apple Inc. and the Swift project authors 10 | 11 | The Swift Project licenses this file to you under the Apache License, 12 | version 2.0 (the "License"); you may not use this file except in compliance 13 | with the License. You may obtain a copy of the License at: 14 | 15 | https://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 19 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 20 | License for the specific language governing permissions and limitations 21 | under the License. 22 | 23 | ------------------------------------------------------------------------------- 24 | 25 | This product contains Swift Argument Parser. 26 | 27 | * LICENSE (Apache License 2.0): 28 | * https://www.apache.org/licenses/LICENSE-2.0 29 | * HOMEPAGE: 30 | * https://github.com/apple/swift-argument-parser 31 | 32 | --- 33 | 34 | This product contains a derivation of the cmark-gfm project, available at 35 | https://github.com/apple/swift-cmark. 36 | 37 | * LICENSE (BSD-2): 38 | * https://opensource.org/licenses/BSD-2-Clause 39 | * HOMEPAGE: 40 | * https://github.com/github/cmark-gfm 41 | 42 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | /* 3 | This source file is part of the Swift.org open source project 4 | 5 | Copyright (c) 2021-2023 Apple Inc. and the Swift project authors 6 | Licensed under Apache License v2.0 with Runtime Library Exception 7 | 8 | See https://swift.org/LICENSE.txt for license information 9 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | */ 11 | 12 | import PackageDescription 13 | import class Foundation.ProcessInfo 14 | 15 | let cmarkPackageName = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil ? "swift-cmark" : "cmark" 16 | 17 | let package = Package( 18 | name: "swift-markdown", 19 | products: [ 20 | .library( 21 | name: "Markdown", 22 | targets: ["Markdown"]), 23 | ], 24 | targets: [ 25 | .target( 26 | name: "Markdown", 27 | dependencies: [ 28 | "CAtomic", 29 | .product(name: "cmark-gfm", package: cmarkPackageName), 30 | .product(name: "cmark-gfm-extensions", package: cmarkPackageName), 31 | ], 32 | exclude: [ 33 | "CMakeLists.txt" 34 | ], 35 | swiftSettings: [ 36 | .unsafeFlags(["-Xcc", "-DCMARK_GFM_STATIC_DEFINE"], 37 | .when(platforms: [.windows])), 38 | ]), 39 | .testTarget( 40 | name: "MarkdownTests", 41 | dependencies: ["Markdown"], 42 | resources: [.process("Visitors/Everything.md")]), 43 | .target(name: "CAtomic"), 44 | ] 45 | ) 46 | 47 | // If the `SWIFTCI_USE_LOCAL_DEPS` environment variable is set, 48 | // we're building in the Swift.org CI system alongside other projects in the Swift toolchain and 49 | // we can depend on local versions of our dependencies instead of fetching them remotely. 50 | if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { 51 | // Building standalone, so fetch all dependencies remotely. 52 | package.dependencies += [ 53 | .package(url: "https://github.com/swiftlang/swift-cmark.git", branch: "gfm"), 54 | ] 55 | 56 | // SwiftPM command plugins are only supported by Swift version 5.6 and later. 57 | #if swift(>=5.6) 58 | package.dependencies += [ 59 | .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0"), 60 | ] 61 | #endif 62 | } else { 63 | // Building in the Swift.org CI system, so rely on local versions of dependencies. 64 | package.dependencies += [ 65 | .package(path: "../cmark"), 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Markdown 2 | 3 | Swift `Markdown` is a Swift package for parsing, building, editing, and analyzing Markdown documents. 4 | 5 | The parser is powered by GitHub-flavored Markdown's [cmark-gfm](https://github.com/github/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. 6 | 7 | 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 [SwiftSyntax](https://github.com/swiftlang/swift-syntax). 8 | 9 | ## Getting Started Using Markup 10 | 11 | In your `Package.swift` Swift Package Manager manifest, add the following dependency to your `dependencies` argument: 12 | 13 | ```swift 14 | .package(url: "https://github.com/swiftlang/swift-markdown.git", branch: "main"), 15 | ``` 16 | 17 | Add the dependency to any targets you've declared in your manifest: 18 | 19 | ```swift 20 | .target( 21 | name: "MyTarget", 22 | dependencies: [ 23 | .product(name: "Markdown", package: "swift-markdown"), 24 | ] 25 | ), 26 | ``` 27 | 28 | To parse a document, use `Document(parsing:)`, supplying a `String` or `URL`: 29 | 30 | ```swift 31 | import Markdown 32 | 33 | let source = "This is a markup *document*." 34 | let document = Document(parsing: source) 35 | print(document.debugDescription()) 36 | // Document 37 | // └─ Paragraph 38 | // ├─ Text "This is a markup " 39 | // ├─ Emphasis 40 | // │ └─ Text "document" 41 | // └─ Text "." 42 | ``` 43 | 44 | Please see Swift `Markdown`'s [documentation site](https://swiftlang.github.io/swift-markdown/documentation/markdown/) 45 | for more detailed information about the library. 46 | 47 | ## Contributing to Swift Markdown 48 | 49 | Please see the [contributing guide](https://swift.org/contributing/#contributing-code) for more information. 50 | 51 | ### Submitting a Bug Report 52 | 53 | Swift Markdown tracks all bug reports with [GitHub Issues](https://github.com/swiftlang/swift-markdown/issues). 54 | You can use the "Swift-Markdown" component for issues and feature requests specific to Swift Markdown. 55 | When you submit a bug report we ask that you follow the 56 | Swift [Bug Reporting](https://swift.org/contributing/#reporting-bugs) guidelines 57 | and provide as many details as possible. 58 | 59 | ### Submitting a Feature Request 60 | 61 | For feature requests, please feel free to file a [GitHub issue](https://github.com/swiftlang/swift-markdown/issues/new) 62 | or start a discussion on the [Swift Forums](https://forums.swift.org/c/development/swift-docc). 63 | 64 | Don't hesitate to submit a feature request if you see a way 65 | Swift Markdown can be improved to better meet your needs. 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /Snippets/Formatting/CondenseAutolinks.swift: -------------------------------------------------------------------------------- 1 | // Format links that use URLs as their link text into autolinks. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This [https://swift.org](https://swift.org) link will become 7 | """ 8 | 9 | let document = Document(parsing: source) 10 | let formattingOptions = MarkupFormatter.Options(condenseAutolinks: true) 11 | let formattedSource = document.format(options: formattingOptions) 12 | 13 | print(""" 14 | ## Original source: 15 | \(source) 16 | 17 | ## Formatted source: 18 | \(formattedSource) 19 | """) 20 | // snippet.hide 21 | /* 22 | This source file is part of the Swift.org open source project 23 | 24 | Copyright (c) 2022 Apple Inc. and the Swift project authors 25 | Licensed under Apache License v2.0 with Runtime Library Exception 26 | 27 | See https://swift.org/LICENSE.txt for license information 28 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 29 | */ 30 | -------------------------------------------------------------------------------- /Snippets/Formatting/CustomLinePrefix.swift: -------------------------------------------------------------------------------- 1 | // Format a Markdown document to have a custom line prefix, such as a comment prefix for use in source code. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This document's lines 7 | will be prefixed with `//`. 8 | """ 9 | 10 | let document = Document(parsing: source) 11 | let formattingOptions = MarkupFormatter.Options(customLinePrefix: "// ") 12 | let formattedSource = document.format(options: formattingOptions) 13 | 14 | print(""" 15 | ## Original source: 16 | \(source) 17 | 18 | ## Formatted source: 19 | \(formattedSource) 20 | """) 21 | // snippet.hide 22 | /* 23 | This source file is part of the Swift.org open source project 24 | 25 | Copyright (c) 2022 Apple Inc. and the Swift project authors 26 | Licensed under Apache License v2.0 with Runtime Library Exception 27 | 28 | See https://swift.org/LICENSE.txt for license information 29 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 30 | */ 31 | -------------------------------------------------------------------------------- /Snippets/Formatting/DefaultFormatting.swift: -------------------------------------------------------------------------------- 1 | // Format Markdown with the default settings. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | |a|b|c| 7 | |-|:-|-:| 8 | |*Some text*|x|| 9 | """ 10 | 11 | // There is not an option for formatting tables per se but is useful to show the behavior for tables. 12 | // Table columns are automatically expanded to fit the column's largest 13 | // cell, making the table easier to read in the Markdown source. 14 | 15 | let document = Document(parsing: source) 16 | let formattedSource = document.format() 17 | 18 | print(""" 19 | ## Original source: 20 | \(source) 21 | 22 | ## Formatted source: 23 | \(formattedSource) 24 | """) 25 | // snippet.hide 26 | /* 27 | This source file is part of the Swift.org open source project 28 | 29 | Copyright (c) 2022 Apple Inc. and the Swift project authors 30 | Licensed under Apache License v2.0 with Runtime Library Exception 31 | 32 | See https://swift.org/LICENSE.txt for license information 33 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 34 | */ 35 | -------------------------------------------------------------------------------- /Snippets/Formatting/EmphasisMarkers.swift: -------------------------------------------------------------------------------- 1 | // Format a consistent style for emphasis markers. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This document uses a mix of *star* and _underbar_ emphasized elements. 7 | """ 8 | 9 | let document = Document(parsing: source) 10 | // Use only * for emphasis markers. 11 | let emphasisMarker = MarkupFormatter.Options.EmphasisMarker.star 12 | let formattedSource = document.format(options: .init(emphasisMarker: emphasisMarker)) 13 | 14 | print(""" 15 | ## Original source: 16 | \(source) 17 | 18 | ## Formatted source: 19 | \(formattedSource) 20 | """) 21 | // snippet.hide 22 | /* 23 | This source file is part of the Swift.org open source project 24 | 25 | Copyright (c) 2022 Apple Inc. and the Swift project authors 26 | Licensed under Apache License v2.0 with Runtime Library Exception 27 | 28 | See https://swift.org/LICENSE.txt for license information 29 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 30 | */ 31 | -------------------------------------------------------------------------------- /Snippets/Formatting/MaximumWidth.swift: -------------------------------------------------------------------------------- 1 | // Format lines to stay under a certain length. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This is a really, really, really, really, really, really, really, really, really, really, really long line. 7 | """ 8 | 9 | let document = Document(parsing: source) 10 | // Break lines longer than 80 characters in width with a soft break. 11 | let lineLimit = MarkupFormatter.Options.PreferredLineLimit(maxLength: 80, breakWith: .softBreak) 12 | let formattingOptions = MarkupFormatter.Options(preferredLineLimit: lineLimit) 13 | let formattedSource = document.format(options: formattingOptions) 14 | 15 | print(""" 16 | ## Original source: 17 | \(source) 18 | 19 | ## Formatted source: 20 | \(formattedSource) 21 | """) 22 | // snippet.hide 23 | /* 24 | This source file is part of the Swift.org open source project 25 | 26 | Copyright (c) 2022 Apple Inc. and the Swift project authors 27 | Licensed under Apache License v2.0 with Runtime Library Exception 28 | 29 | See https://swift.org/LICENSE.txt for license information 30 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 31 | */ 32 | -------------------------------------------------------------------------------- /Snippets/Formatting/OrderedListNumerals.swift: -------------------------------------------------------------------------------- 1 | // Format the counting behavior of ordered lists. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | 1. An 7 | 2. ordered 8 | 3. list 9 | """ 10 | 11 | let document = Document(parsing: source) 12 | // Use all 0. markers to allow easily reordering ordered list items. 13 | let formattingOptions = MarkupFormatter.Options(orderedListNumerals: .allSame(1)) 14 | let formattedSource = document.format(options: formattingOptions) 15 | 16 | print(""" 17 | ## Original source: 18 | \(source) 19 | 20 | ## Formatted source: 21 | \(formattedSource) 22 | """) 23 | // snippet.hide 24 | /* 25 | This source file is part of the Swift.org open source project 26 | 27 | Copyright (c) 2022 Apple Inc. and the Swift project authors 28 | Licensed under Apache License v2.0 with Runtime Library Exception 29 | 30 | See https://swift.org/LICENSE.txt for license information 31 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 32 | */ 33 | -------------------------------------------------------------------------------- /Snippets/Formatting/PreferredHeadingStyle.swift: -------------------------------------------------------------------------------- 1 | // Format a Markdown document to use ATX style headings throughout. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | # Title 7 | 8 | ## Second-level Heading 9 | 10 | Another Second-level Heading 11 | ---------------------------- 12 | 13 | The above heading will be converted to ATX style, using hashes. 14 | """ 15 | 16 | let document = Document(parsing: source) 17 | let headingStyle = MarkupFormatter.Options.PreferredHeadingStyle.atx 18 | let formattingOptions = MarkupFormatter.Options(preferredHeadingStyle: headingStyle) 19 | let formattedSource = document.format(options: formattingOptions) 20 | 21 | print(""" 22 | ## Original source: 23 | \(source) 24 | 25 | ## Formatted source: 26 | \(formattedSource) 27 | """) 28 | // snippet.hide 29 | /* 30 | This source file is part of the Swift.org open source project 31 | 32 | Copyright (c) 2022 Apple Inc. and the Swift project authors 33 | Licensed under Apache License v2.0 with Runtime Library Exception 34 | 35 | See https://swift.org/LICENSE.txt for license information 36 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 37 | */ 38 | -------------------------------------------------------------------------------- /Snippets/Formatting/ThematicBreakCharacter.swift: -------------------------------------------------------------------------------- 1 | // Format a consistent style for thematic breaks. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | First paragraph. 7 | 8 | ----- 9 | 10 | Second paragraph. 11 | 12 | ***** 13 | """ 14 | 15 | let document = Document(parsing: source) 16 | let thematicBreakCharacter = MarkupFormatter.Options.ThematicBreakCharacter.dash 17 | // Make all thematic breaks 10 dash `-` characters. 18 | let formattingOptions = MarkupFormatter.Options(thematicBreakCharacter: thematicBreakCharacter, thematicBreakLength: 10) 19 | let formattedSource = document.format(options: formattingOptions) 20 | 21 | print(""" 22 | ## Original source: 23 | \(source) 24 | 25 | ## Formatted source: 26 | \(formattedSource) 27 | """) 28 | // snippet.hide 29 | /* 30 | This source file is part of the Swift.org open source project 31 | 32 | Copyright (c) 2022 Apple Inc. and the Swift project authors 33 | Licensed under Apache License v2.0 with Runtime Library Exception 34 | 35 | See https://swift.org/LICENSE.txt for license information 36 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 37 | */ 38 | -------------------------------------------------------------------------------- /Snippets/Formatting/UnorderedListMarker.swift: -------------------------------------------------------------------------------- 1 | // Format the unordered list marker. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | - An 7 | - unordered 8 | - list 9 | """ 10 | 11 | let document = Document(parsing: source) 12 | // Use an star or asterisk `*` as the unordered list marker. 13 | let formattedSource = document.format(options: .init(unorderedListMarker: .star)) 14 | 15 | print(""" 16 | ## Original source: 17 | \(source) 18 | 19 | ## Formatted source: 20 | \(formattedSource) 21 | """) 22 | // snippet.hide 23 | /* 24 | This source file is part of the Swift.org open source project 25 | 26 | Copyright (c) 2022 Apple Inc. and the Swift project authors 27 | Licensed under Apache License v2.0 with Runtime Library Exception 28 | 29 | See https://swift.org/LICENSE.txt for license information 30 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 31 | */ 32 | -------------------------------------------------------------------------------- /Snippets/Formatting/UseCodeFence.swift: -------------------------------------------------------------------------------- 1 | // Format all code blocks to use a consistent style for code blocks, 2 | // optionally setting the default info string to declare that they 3 | // have a particular syntax. 4 | 5 | import Markdown 6 | 7 | let source = """ 8 | This document contains a mix of indented and fenced code blocks. 9 | 10 | A code block. 11 | 12 | ``` 13 | func foo() {} 14 | ``` 15 | """ 16 | 17 | let document = Document(parsing: source) 18 | // Always fenced code blocks. 19 | let fencedCodeBlock = MarkupFormatter.Options.UseCodeFence.always 20 | // Use `swift` as the info string on all fenced code blocks. 21 | let defaultCodeBlockLanguage = "swift" 22 | let formattedSource = document.format(options: .init(useCodeFence: fencedCodeBlock, defaultCodeBlockLanguage: defaultCodeBlockLanguage)) 23 | 24 | print(""" 25 | ## Original source: 26 | \(source) 27 | 28 | ## Formatted source: 29 | \(formattedSource) 30 | """) 31 | // snippet.hide 32 | /* 33 | This source file is part of the Swift.org open source project 34 | 35 | Copyright (c) 2022 Apple Inc. and the Swift project authors 36 | Licensed under Apache License v2.0 with Runtime Library Exception 37 | 38 | See https://swift.org/LICENSE.txt for license information 39 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 40 | */ 41 | -------------------------------------------------------------------------------- /Snippets/Parsing/ParseDocumentFile.swift: -------------------------------------------------------------------------------- 1 | // Parse the contents of a file by its ``URL`` without having to read its contents yourself. 2 | 3 | import Foundation 4 | import Markdown 5 | 6 | // snippet.hide 7 | let inputFileURL = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("test.md") 8 | // snippet.show 9 | let document = try Document(parsing: inputFileURL) 10 | 11 | print(document.debugDescription()) 12 | // snippet.hide 13 | /* 14 | This source file is part of the Swift.org open source project 15 | 16 | Copyright (c) 2022 Apple Inc. and the Swift project authors 17 | Licensed under Apache License v2.0 with Runtime Library Exception 18 | 19 | See https://swift.org/LICENSE.txt for license information 20 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 21 | */ 22 | -------------------------------------------------------------------------------- /Snippets/Parsing/ParseDocumentString.swift: -------------------------------------------------------------------------------- 1 | // Parse a ``String`` as Markdown. 2 | 3 | import Markdown 4 | 5 | let source = "Some *Markdown* source" 6 | let document = Document(parsing: source) 7 | 8 | print(document.debugDescription()) 9 | // snippet.hide 10 | /* 11 | This source file is part of the Swift.org open source project 12 | 13 | Copyright (c) 2022 Apple Inc. and the Swift project authors 14 | Licensed under Apache License v2.0 with Runtime Library Exception 15 | 16 | See https://swift.org/LICENSE.txt for license information 17 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 18 | */ 19 | -------------------------------------------------------------------------------- /Snippets/Parsing/test.md: -------------------------------------------------------------------------------- 1 | # Sample document 2 | 3 | This is a *sample document*. 4 | 5 | 6 | -------------------------------------------------------------------------------- /Snippets/Querying/ChildThrough.swift: -------------------------------------------------------------------------------- 1 | // Find a matching element deep within a ``Markup`` tree. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | Reach into a document to find the *emphasized text*. 7 | """ 8 | let document = Document(parsing: source) 9 | let emphasizedText = document.child(through: [ 10 | (0, Paragraph.self), 11 | (1, Emphasis.self), 12 | (0, Text.self) 13 | ]) as! Text 14 | 15 | print(""" 16 | ## Document structure: 17 | \(document.debugDescription()) 18 | 19 | ## Found element: 20 | \(emphasizedText.detachedFromParent.debugDescription()) 21 | """) 22 | // snippet.hide 23 | /* 24 | This source file is part of the Swift.org open source project 25 | 26 | Copyright (c) 2022 Apple Inc. and the Swift project authors 27 | Licensed under Apache License v2.0 with Runtime Library Exception 28 | 29 | See https://swift.org/LICENSE.txt for license information 30 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 31 | */ 32 | -------------------------------------------------------------------------------- /Snippets/Rewriters/RemoveElementKind.swift: -------------------------------------------------------------------------------- 1 | // Remove all instances of a kind of element using a `MarkupRewriter`. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | The strong emphasis element is **going to be** deleted. 7 | """ 8 | 9 | struct StrongDeleter: MarkupRewriter { 10 | // Delete all ``Strong`` elements. 11 | func visitStrong(_ strong: Strong) -> Markup? { 12 | return nil 13 | } 14 | } 15 | 16 | let document = Document(parsing: source) 17 | var deleter = StrongDeleter() 18 | let newDocument = deleter.visit(document) as! Document 19 | 20 | print(""" 21 | ## Original Markdown structure: 22 | \(document.debugDescription()) 23 | 24 | ## New Markdown structure: 25 | \(newDocument.debugDescription()) 26 | """) 27 | // snippet.hide 28 | /* 29 | This source file is part of the Swift.org open source project 30 | 31 | Copyright (c) 2022 Apple Inc. and the Swift project authors 32 | Licensed under Apache License v2.0 with Runtime Library Exception 33 | 34 | See https://swift.org/LICENSE.txt for license information 35 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 36 | */ 37 | -------------------------------------------------------------------------------- /Snippets/Rewriters/ReplaceText.swift: -------------------------------------------------------------------------------- 1 | // Replace some text with a ``MarkupRewriter``. 2 | // 3 | // > Experiment: You can use a similar approach for other kinds of replacements. 4 | // > Try updating link destinations in your document by implementing 5 | // > a `visitLink` method and returning a new ``Link`` element. 6 | 7 | import Markdown 8 | 9 | struct TextReplacer: MarkupRewriter { 10 | var target: String 11 | var replacement: String 12 | 13 | init(replacing target: String, with replacement: String?) { 14 | precondition(!target.isEmpty) 15 | self.target = target 16 | self.replacement = replacement ?? "" 17 | } 18 | 19 | func visitText(_ text: Text) -> Markup? { 20 | return Text(text.string.replacingOccurrences(of: target, with: replacement)) 21 | } 22 | } 23 | 24 | let source = """ 25 | The word "foo" will be replaced with "bar". 26 | """ 27 | let document = Document(parsing: source) 28 | var replacer = TextReplacer(replacing: "foo", with: "bar") 29 | let newDocument = replacer.visit(document) as! Document 30 | 31 | print(""" 32 | ## Original Markdown structure: 33 | \(document.debugDescription()) 34 | 35 | ## New Markdown structure: 36 | \(newDocument.debugDescription()) 37 | """) 38 | // snippet.hide 39 | /* 40 | This source file is part of the Swift.org open source project 41 | 42 | Copyright (c) 2022 Apple Inc. and the Swift project authors 43 | Licensed under Apache License v2.0 with Runtime Library Exception 44 | 45 | See https://swift.org/LICENSE.txt for license information 46 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 47 | */ 48 | -------------------------------------------------------------------------------- /Snippets/Visitors/XMLConverter.swift: -------------------------------------------------------------------------------- 1 | // Implement a ``MarkupVisitor`` to transform a ``Markup`` tree 2 | // to another structured data format such as XML. 3 | // 4 | // > Note: This is not a complete example converting 5 | // > the unique and important properties of each kind of ``Markup`` element 6 | // > as XML attributes. 7 | // 8 | // > Experiment: Implement all visitor methods for each type 9 | // > for a more complete XML or HTML converter. 10 | 11 | import Markdown 12 | 13 | struct XMLConverter: MarkupVisitor { 14 | mutating func defaultVisit(_ markup: Markup) -> XML { 15 | return XML(tag: String(describing: type(of: markup)), 16 | children: markup.children.map { defaultVisit($0) }, 17 | text: (markup as? Text).map { $0.string }) 18 | } 19 | } 20 | 21 | let source = """ 22 | A ***basic*** document. 23 | """ 24 | let document = Document(parsing: source) 25 | var xmlConverter = XMLConverter() 26 | let xml = xmlConverter.visit(document) 27 | 28 | print(""" 29 | ## Original document structure: 30 | \(document.debugDescription()) 31 | 32 | ## Resulting XML: 33 | \(xml.format()) 34 | """) 35 | 36 | // A very basic XML tree type. 37 | struct XML { 38 | var tag: String 39 | var children: [XML] 40 | var text: String? 41 | 42 | func format(indent: Int = 0) -> String { 43 | let indentation = String(repeating: " ", count: indent) 44 | if tag == "Text" { 45 | return "\(indentation)<\(tag)>\(text ?? "")" 46 | } else { 47 | var result = "\(indentation)<\(tag)>" 48 | for child in children { 49 | result += "\n\(child.format(indent: indent + 2))" 50 | } 51 | result += "\n\(indentation)<\(tag)>" 52 | return result 53 | } 54 | } 55 | } 56 | 57 | // snippet.hide 58 | /* 59 | This source file is part of the Swift.org open source project 60 | 61 | Copyright (c) 2022 Apple Inc. and the Swift project authors 62 | Licensed under Apache License v2.0 with Runtime Library Exception 63 | 64 | See https://swift.org/LICENSE.txt for license information 65 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 66 | */ 67 | -------------------------------------------------------------------------------- /Snippets/Walkers/LinkCollector.swift: -------------------------------------------------------------------------------- 1 | // Collect all links in a Markdown document. 2 | 3 | import Markdown 4 | 5 | struct LinkCollector: MarkupWalker { 6 | var links = [String]() 7 | mutating func visitLink(_ link: Link) { 8 | link.destination.map { links.append($0) } 9 | } 10 | } 11 | 12 | let source = """ 13 | A link to a [non-existent website](https://iqnvodkfjd.com). 14 | 15 | A link to a missing resource at . 16 | 17 | A valid link to . 18 | """ 19 | let document = Document(parsing: source) 20 | // snippet.hide 21 | print("## Checking links in parsed document:") 22 | print(document.debugDescription()) 23 | // snippet.show 24 | var linkCollector = LinkCollector() 25 | linkCollector.visit(document) 26 | print("## Found links:") 27 | print(linkCollector.links.joined(separator: "\n")) 28 | // snippet.hide 29 | /* 30 | This source file is part of the Swift.org open source project 31 | 32 | Copyright (c) 2022 Apple Inc. and the Swift project authors 33 | Licensed under Apache License v2.0 with Runtime Library Exception 34 | 35 | See https://swift.org/LICENSE.txt for license information 36 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 37 | */ 38 | -------------------------------------------------------------------------------- /Sources/CAtomic/CAtomic.c: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | #include 12 | 13 | static _Atomic uint64_t _cmarkup_unique_id = 0; 14 | 15 | uint64_t _cmarkup_current_unique_id(void) { 16 | return _cmarkup_unique_id; 17 | } 18 | 19 | uint64_t _cmarkup_increment_and_get_unique_id(void) { 20 | return ++_cmarkup_unique_id; 21 | } 22 | -------------------------------------------------------------------------------- /Sources/CAtomic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | add_library(CAtomic STATIC 11 | CAtomic.c) 12 | target_include_directories(CAtomic PUBLIC 13 | include) 14 | 15 | set_property(GLOBAL APPEND PROPERTY SWIFT_MARKDOWN_EXPORTS CAtomic) 16 | -------------------------------------------------------------------------------- /Sources/CAtomic/include/CAtomic.h: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | #include 12 | 13 | /// The current unique atomic identifier. 14 | uint64_t _cmarkup_current_unique_id(void); 15 | 16 | /// Increment the current unique identifier atomically and return it. 17 | uint64_t _cmarkup_increment_and_get_unique_id(void); 18 | -------------------------------------------------------------------------------- /Sources/CAtomic/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module CAtomic { 2 | header "CAtomic.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Sources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | add_subdirectory(CAtomic) 11 | add_subdirectory(Markdown) 12 | -------------------------------------------------------------------------------- /Sources/Markdown/Base/ChildIndexPath.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An array of indexes for traversing deeply into a markup tree. 12 | public typealias ChildIndexPath = [Int] 13 | 14 | /// A description of a traversal through a markup tree by index and optional expected type. 15 | public struct TypedChildIndexPath: RandomAccessCollection, ExpressibleByArrayLiteral, Sendable { 16 | /// A pair consisting of an expected index and optional expected type for a child element. 17 | /// 18 | /// This type is a shorthand convenience when creating a ``TypedChildIndexPath`` from an array literal. 19 | public typealias ArrayLiteralElement = (Int, Markup.Type?) 20 | 21 | /// An element of a complex child index path. 22 | public struct Element: Sendable { 23 | /// The index to use when descending into the children. 24 | var index: Int 25 | 26 | /** 27 | The expected type of the child at ``index``. 28 | 29 | Use this to restrict the type of node to enter at this point in the traversal. If the child doesn't match this type, the traversal will fail. To allow any type of child markup type, set this to `nil`. 30 | */ 31 | var expectedType: Markup.Type? 32 | } 33 | 34 | /// The elements of the path. 35 | private var elements: [Element] 36 | 37 | /// Create an empty path. 38 | public init() { 39 | elements = [] 40 | } 41 | 42 | /// Create a path from a sequence of index-type pairs. 43 | public init(_ elements: S) where S.Element == Element { 44 | self.elements = Array(elements) 45 | } 46 | 47 | /// Create a path from a sequence of index-type pairs. 48 | public init(arrayLiteral elements: ArrayLiteralElement...) { 49 | self.elements = elements.map { Element(index: $0.0, expectedType: $0.1) } 50 | } 51 | 52 | public var startIndex: Int { 53 | return elements.startIndex 54 | } 55 | 56 | public var endIndex: Int { 57 | return elements.endIndex 58 | } 59 | 60 | public subscript(index: Int) -> Element { 61 | return elements[index] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Markdown/Base/Document.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A markup element representing the top level of a whole document. 14 | /// 15 | /// - note: Although this could be considered a block element that can contain block elements, a `Document` itself can't be the child of any other markup, so it is not considered a block element. 16 | public struct Document: Markup, BasicBlockContainer { 17 | public var _data: _MarkupData 18 | 19 | init(_ raw: RawMarkup) throws { 20 | guard case .document = raw.data else { 21 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Document.self) 22 | } 23 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 24 | self.init(_MarkupData(absoluteRaw)) 25 | 26 | } 27 | 28 | init(_ data: _MarkupData) { 29 | self._data = data 30 | } 31 | } 32 | 33 | // MARK: - Public API 34 | 35 | public extension Document { 36 | // MARK: Primitive 37 | 38 | /// Parse a string into a `Document`. 39 | /// 40 | /// - parameter string: the input Markdown text to parse. 41 | /// - parameter options: options for parsing Markdown text. 42 | /// - parameter source: an explicit source URL from which the input `string` came for marking source locations. 43 | /// This need not be a file URL. 44 | init(parsing string: String, source: URL? = nil, options: ParseOptions = []) { 45 | if options.contains(.parseBlockDirectives) { 46 | self = BlockDirectiveParser.parse(string, source: source, 47 | options: options) 48 | } else { 49 | self = MarkupParser.parseString(string, source: source, options: options) 50 | } 51 | } 52 | 53 | /// Parse a file's contents into a `Document`. 54 | /// 55 | /// - parameter file: a file URL from which to load Markdown text to parse. 56 | /// - parameter options: options for parsing Markdown text. 57 | init(parsing file: URL, options: ParseOptions = []) throws { 58 | let string = try String(contentsOf: file) 59 | if options.contains(.parseBlockDirectives) { 60 | self = BlockDirectiveParser.parse(string, source: file, 61 | options: options) 62 | } else { 63 | self = MarkupParser.parseString(string, source: file, options: options) 64 | } 65 | } 66 | 67 | /// Create a document from a sequence of block markup elements. 68 | init(_ children: some Sequence) { 69 | self.init(children, inheritSourceRange: false) 70 | } 71 | 72 | init(_ children: some Sequence, inheritSourceRange: Bool) { 73 | let rawChildren = children.map { $0.raw.markup } 74 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 75 | try! self.init(.document(parsedRange: parsedRange, rawChildren)) 76 | } 77 | 78 | // MARK: Visitation 79 | 80 | func accept(_ visitor: inout V) -> V.Result { 81 | return visitor.visitDocument(self) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/Markdown/Base/LiteralMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element that is represented with just some plain text. 12 | public protocol LiteralMarkup: Markup { 13 | /// Create an element from its literal text. 14 | init(_ literalText: String) 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Markdown/Base/PlainTextConvertibleMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element that can be converted to plain text without formatting. 12 | public protocol PlainTextConvertibleMarkup: Markup { 13 | /// The plain text content of an element. 14 | var plainText: String { get } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/BlockQuote.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block quote. 12 | public struct BlockQuote: BlockMarkup, BasicBlockContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .blockQuote = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: BlockQuote.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension BlockQuote { 30 | // MARK: BasicBlockContainer 31 | 32 | init(_ children: some Sequence) { 33 | self.init(children, inheritSourceRange: false) 34 | } 35 | 36 | init(_ children: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = children.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.blockQuote(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitBlockQuote(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/CustomBlock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A custom block markup element. 12 | /// 13 | /// - note: This element does not yet allow for custom information to be appended and is included for backward compatibility with CommonMark. It wraps any block element. 14 | public struct CustomBlock: BlockMarkup, BasicBlockContainer { 15 | public var _data: _MarkupData 16 | init(_ raw: RawMarkup) throws { 17 | guard case .customBlock = raw.data else { 18 | throw RawMarkup.Error.concreteConversionError(from: raw, to: CustomBlock.self) 19 | } 20 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 21 | self.init(_MarkupData(absoluteRaw)) 22 | } 23 | 24 | init(_ data: _MarkupData) { 25 | self._data = data 26 | } 27 | } 28 | 29 | // MARK: - Public API 30 | 31 | public extension CustomBlock { 32 | init(_ children: some Sequence) { 33 | self.init(children, inheritSourceRange: false) 34 | } 35 | 36 | init(_ children: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = children.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.customBlock(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitCustomBlock(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2025 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\abstract` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\abstract` commands of the form 16 | /// `\abstract description`, where `description` continues until the next blank 17 | /// line or parsed command. 18 | /// 19 | /// ```markdown 20 | /// \abstract This object can give other objects in your program magical powers. 21 | /// ``` 22 | public struct DoxygenAbstract: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenAbstract = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenAbstract.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenAbstract(self) 39 | } 40 | } 41 | 42 | public extension DoxygenAbstract { 43 | /// Create a new Doxygen abstract definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenAbstract(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen abstract definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\discussion` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\discussion` commands of the form 16 | /// `\discussion description`, where `description` continues until the next blank 17 | /// line or parsed command. 18 | /// 19 | /// ```markdown 20 | /// \discussion This object can give other objects in your program magical powers. 21 | /// ``` 22 | public struct DoxygenDiscussion: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenDiscussion = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenDiscussion.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenDiscussion(self) 39 | } 40 | } 41 | 42 | public extension DoxygenDiscussion { 43 | /// Create a new Doxygen discussion definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenDiscussion(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen discussion definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\note` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\note` commands of the form 16 | /// `\note description`, where `description` continues until the next blank 17 | /// line or parsed command. 18 | /// 19 | /// ```markdown 20 | /// \note This method is only meant to be called an odd number of times. 21 | /// ``` 22 | public struct DoxygenNote: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenNote = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenNote.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenNote(self) 39 | } 40 | } 41 | 42 | public extension DoxygenNote { 43 | /// Create a new Doxygen note definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenNote(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen note definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\param` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\param` commands of the form 16 | /// `\param name description`, where `description` extends until the next blank line or the next 17 | /// parsed command. For example, the following input will return two `DoxygenParam` instances: 18 | /// 19 | /// ```markdown 20 | /// \param coordinate The coordinate used to center the transformation. 21 | /// \param matrix The transformation matrix that describes the transformation. 22 | /// For more information about transformation matrices, refer to the Transformation 23 | /// documentation. 24 | /// ``` 25 | public struct DoxygenParameter: BlockContainer { 26 | public var _data: _MarkupData 27 | 28 | init(_ raw: RawMarkup) throws { 29 | guard case .doxygenParam = raw.data else { 30 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenParameter.self) 31 | } 32 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 33 | self.init(_MarkupData(absoluteRaw)) 34 | } 35 | 36 | init(_ data: _MarkupData) { 37 | self._data = data 38 | } 39 | 40 | public func accept(_ visitor: inout V) -> V.Result { 41 | return visitor.visitDoxygenParameter(self) 42 | } 43 | } 44 | 45 | public extension DoxygenParameter { 46 | /// Create a new Doxygen parameter definition. 47 | /// 48 | /// - Parameter name: The name of the parameter being described. 49 | /// - Parameter children: Block child elements. 50 | init(name: String, children: Children) where Children.Element == BlockMarkup { 51 | try! self.init(.doxygenParam(name: name, parsedRange: nil, children.map({ $0.raw.markup }))) 52 | } 53 | 54 | /// Create a new Doxygen parameter definition. 55 | /// 56 | /// - Parameter name: The name of the parameter being described. 57 | /// - Parameter children: Block child elements. 58 | init(name: String, children: BlockMarkup...) { 59 | self.init(name: name, children: children) 60 | } 61 | 62 | /// The name of the parameter being described. 63 | var name: String { 64 | get { 65 | guard case let .doxygenParam(name) = _data.raw.markup.data else { 66 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 67 | } 68 | return name 69 | } 70 | set { 71 | _data = _data.replacingSelf(.doxygenParam( 72 | name: newValue, 73 | parsedRange: nil, 74 | _data.raw.markup.copyChildren() 75 | )) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\returns`, `\return`, or `\result` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\returns` commands of the form 16 | /// `\returns description`, where `description` continues until the next blank line or parsed 17 | /// command. The commands `\return` and `\result` are also accepted, with the same format. 18 | /// 19 | /// ```markdown 20 | /// \returns A freshly-created object. 21 | /// ``` 22 | public struct DoxygenReturns: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenReturns = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenReturns.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenReturns(self) 39 | } 40 | } 41 | 42 | public extension DoxygenReturns { 43 | /// Create a new Doxygen returns definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenReturns(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen returns definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/ListItem.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A checkbox that can represent an on/off state. 12 | public enum Checkbox: Sendable { 13 | /// The checkbox is checked, representing an "on", "true", or "incomplete" state. 14 | case checked 15 | /// The checkbox is unchecked, representing an "off", "false", or "incomplete" state. 16 | case unchecked 17 | } 18 | 19 | /// A list item in an ordered or unordered list. 20 | public struct ListItem: BlockContainer { 21 | public var _data: _MarkupData 22 | init(_ raw: RawMarkup) throws { 23 | guard case .listItem = raw.data else { 24 | throw RawMarkup.Error.concreteConversionError(from: raw, to: ListItem.self) 25 | } 26 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 27 | self.init(_MarkupData(absoluteRaw)) 28 | } 29 | 30 | init(_ data: _MarkupData) { 31 | self._data = data 32 | } 33 | } 34 | 35 | // MARK: - Public API 36 | 37 | public extension ListItem { 38 | /// Create a list item. 39 | /// - Parameter checkbox: An optional ``Checkbox`` for the list item. 40 | /// - Parameter children: The child block elements of the list item. 41 | init(checkbox: Checkbox? = .none, _ children: BlockMarkup...) { 42 | try! self.init(.listItem(checkbox: checkbox, parsedRange: nil, children.map { $0.raw.markup })) 43 | } 44 | 45 | /// Create a list item. 46 | /// - Parameter checkbox: An optional ``Checkbox`` for the list item. 47 | /// - Parameter children: The child block elements of the list item. 48 | init(checkbox: Checkbox? = .none, _ children: Children) where Children.Element == BlockMarkup { 49 | try! self.init(.listItem(checkbox: checkbox, parsedRange: nil, children.map { $0.raw.markup })) 50 | } 51 | 52 | /// An optional ``Checkbox`` for the list item, which can indicate completion of a task, or some other off/on information. 53 | var checkbox: Checkbox? { 54 | get { 55 | guard case let .listItem(checkbox) = _data.raw.markup.data else { 56 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 57 | } 58 | return checkbox 59 | } 60 | set { 61 | _data = _data.replacingSelf(.listItem(checkbox: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 62 | } 63 | } 64 | 65 | // MARK: Visitation 66 | 67 | func accept(_ visitor: inout V) -> V.Result { 68 | return visitor.visitListItem(self) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/OrderedList.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An ordered list. 12 | public struct OrderedList: ListItemContainer, BlockMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .orderedList = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: OrderedList.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | init(_ data: _MarkupData) { 22 | self._data = data 23 | } 24 | } 25 | 26 | // MARK: - Public API 27 | 28 | public extension OrderedList { 29 | // MARK: ListItemContainer 30 | 31 | init(_ items: Items) where Items.Element == ListItem { 32 | try! self.init(.orderedList(parsedRange: nil, items.map { $0.raw.markup })) 33 | } 34 | 35 | /// The starting index for the list. 36 | /// 37 | /// The default starting index in CommonMark is 1. In this case, clients may use the default 38 | /// ordered-list start index of their desired rendering format. For example, when rendering to 39 | /// HTML, clients may omit the `start` attribute of the rendered list when this returns 1. 40 | var startIndex: UInt { 41 | get { 42 | guard case let .orderedList(start) = _data.raw.markup.data else { 43 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 44 | } 45 | return start 46 | } 47 | set { 48 | guard startIndex != newValue else { 49 | return 50 | } 51 | _data = _data.replacingSelf(.orderedList(parsedRange: nil, _data.raw.markup.copyChildren(), startIndex: newValue)) 52 | } 53 | } 54 | 55 | // MARK: Visitation 56 | 57 | func accept(_ visitor: inout V) -> V.Result { 58 | return visitor.visitOrderedList(self) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/UnorderedList.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An unordered list. 12 | public struct UnorderedList: ListItemContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .unorderedList = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: UnorderedList.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension UnorderedList { 30 | // MARK: ListItemContainer 31 | 32 | init(_ items: Items) where Items.Element == ListItem { 33 | try! self.init(.unorderedList(parsedRange: nil, items.map { $0.raw.markup })) 34 | } 35 | 36 | // MARK: Visitation 37 | 38 | func accept(_ visitor: inout V) -> V.Result { 39 | return visitor.visitUnorderedList(self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Inline Container Blocks/Paragraph.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A paragraph. 12 | public struct Paragraph: BlockMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ data: _MarkupData) { 15 | self._data = data 16 | } 17 | 18 | init(_ raw: RawMarkup) throws { 19 | guard case .paragraph = raw.data else { 20 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Paragraph.self) 21 | } 22 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 23 | self.init(_MarkupData(absoluteRaw)) 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension Paragraph { 30 | // MARK: InlineContainer 31 | 32 | init(_ newChildren: some Sequence) { 33 | self.init(newChildren, inheritSourceRange: false) 34 | } 35 | 36 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = newChildren.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.paragraph(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitParagraph(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/CodeBlock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A code block. 12 | public struct CodeBlock: BlockMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .codeBlock = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: CodeBlock.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension CodeBlock { 30 | /// Create a code block with raw `code` and optional `language`. 31 | init(language: String? = nil, _ code: String) { 32 | try! self.init(RawMarkup.codeBlock(parsedRange: nil, code: code, language: language)) 33 | } 34 | 35 | /// The name of the syntax or programming language of the code block, which may be `nil` when unspecified. 36 | var language: String? { 37 | get { 38 | guard case let .codeBlock(_, language) = _data.raw.markup.data else { 39 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 40 | } 41 | return language 42 | } 43 | set { 44 | _data = _data.replacingSelf(.codeBlock(parsedRange: nil, code: code, language: newValue)) 45 | } 46 | } 47 | 48 | /// The raw text representing the code of this block. 49 | var code: String { 50 | get { 51 | guard case let .codeBlock(code, _) = _data.raw.markup.data else { 52 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 53 | } 54 | return code 55 | } 56 | set { 57 | _data = _data.replacingSelf(.codeBlock(parsedRange: nil, code: newValue, language: language)) 58 | } 59 | } 60 | 61 | // MARK: Visitation 62 | 63 | func accept(_ visitor: inout V) -> V.Result { 64 | return visitor.visitCodeBlock(self) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/HTMLBlock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block element containing raw HTML. 12 | public struct HTMLBlock: BlockMarkup, LiteralMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .htmlBlock = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: HTMLBlock.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension HTMLBlock { 30 | init(_ literalText: String) { 31 | try! self.init(.htmlBlock(parsedRange: nil, html: literalText)) 32 | } 33 | 34 | /// The raw HTML text comprising the block. 35 | var rawHTML: String { 36 | get { 37 | guard case let .htmlBlock(text) = _data.raw.markup.data else { 38 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 39 | } 40 | return text 41 | } 42 | set { 43 | _data = _data.replacingSelf(.htmlBlock(parsedRange: nil, html: newValue)) 44 | } 45 | } 46 | 47 | // MARK: Visitation 48 | 49 | func accept(_ visitor: inout V) -> V.Result { 50 | return visitor.visitHTMLBlock(self) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/Heading.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A heading. 12 | public struct Heading: BlockMarkup, InlineContainer { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .heading = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Heading.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension Heading { 31 | // MARK: Primitive 32 | 33 | /// Create a heading with a level and a sequence of children. 34 | init(level: Int, _ children: Children) where Children.Element == InlineMarkup { 35 | try! self.init(.heading(level: level, parsedRange: nil, children.map { $0.raw.markup })) 36 | } 37 | 38 | /// The level of the heading, starting at `1`. 39 | var level: Int { 40 | get { 41 | guard case let .heading(level) = _data.raw.markup.data else { 42 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 43 | } 44 | return level 45 | } 46 | set { 47 | precondition(newValue > 0, "Heading level must be 1 or greater") 48 | guard level != newValue else { 49 | return 50 | } 51 | _data = _data.replacingSelf(.heading(level: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 52 | } 53 | } 54 | 55 | // MARK: Secondary 56 | 57 | /// Create a heading with a level and a sequence of children. 58 | init(level: Int, _ children: InlineMarkup...) { 59 | self.init(level: level, children) 60 | } 61 | 62 | // MARK: Visitation 63 | 64 | func accept(_ visitor: inout V) -> V.Result { 65 | return visitor.visitHeading(self) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/ThematicBreak.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A thematic break. 12 | public struct ThematicBreak: BlockMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .thematicBreak = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: ThematicBreak.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | init(_ data: _MarkupData) { 22 | self._data = data 23 | } 24 | } 25 | 26 | // MARK: - Public API 27 | 28 | public extension ThematicBreak { 29 | /// Create a thematic break. 30 | init() { 31 | try! self.init(.thematicBreak(parsedRange: nil)) 32 | } 33 | 34 | // MARK: Visitation 35 | 36 | func accept(_ visitor: inout V) -> V.Result { 37 | return visitor.visitThematicBreak(self) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableBody.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension Table { 12 | /// The body of a table consisting of zero or more ``Table/Row`` elements. 13 | public struct Body : Markup { 14 | public var _data: _MarkupData 15 | 16 | init(_ data: _MarkupData) { 17 | self._data = data 18 | } 19 | 20 | init(_ raw: RawMarkup) throws { 21 | guard case .tableBody = raw.data else { 22 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Table.Body.self) 23 | } 24 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 25 | self.init(_MarkupData(absoluteRaw)) 26 | } 27 | 28 | /// The maximum number of columns seen in all rows. 29 | var maxColumnCount: Int { 30 | return children.reduce(0) { (result, row) -> Int in 31 | return max(result, row.childCount) 32 | } 33 | } 34 | } 35 | } 36 | 37 | // MARK: - Public API 38 | 39 | public extension Table.Body { 40 | /// Create a table body from a sequence of ``Table/Row`` elements. 41 | init(_ rows: Rows) where Rows.Element == Table.Row { 42 | try! self.init(RawMarkup.tableBody(parsedRange: nil, rows: rows.map { $0.raw.markup })) 43 | } 44 | 45 | /// Create a table body from a sequence of ``Table/Row`` elements. 46 | init(_ rows: Table.Row...) { 47 | self.init(rows) 48 | } 49 | 50 | /// The rows of the body. 51 | /// 52 | /// - Precondition: All children of a `ListItemContainer` 53 | /// must be a `ListItem`. 54 | var rows: LazyMapSequence { 55 | return children.lazy.map { $0 as! Table.Row } 56 | } 57 | 58 | /// Replace all list items with a sequence of items. 59 | mutating func setRows(_ newRows: Rows) where Rows.Element == Table.Row { 60 | replaceRowsInRange(0..(_ range: Range, with incomingRows: Rows) where Rows.Element == Table.Row { 65 | var rawChildren = raw.markup.copyChildren() 66 | rawChildren.replaceSubrange(range, with: incomingRows.map { $0.raw.markup }) 67 | let newRaw = raw.markup.withChildren(rawChildren) 68 | _data = _data.replacingSelf(newRaw) 69 | } 70 | 71 | /// Append a row to the list. 72 | mutating func appendRow(_ row: Table.Row) { 73 | replaceRowsInRange(childCount..(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 79 | return visitor.visitTableBody(self) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension Table { 12 | /// A cell in a table. 13 | public struct Cell: Markup, BasicInlineContainer { 14 | public var _data: _MarkupData 15 | init(_ raw: RawMarkup) throws { 16 | guard case .tableCell = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Table.Cell.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | } 28 | 29 | // MARK: - Public API 30 | 31 | public extension Table.Cell { 32 | 33 | /// The number of columns this cell spans over. 34 | /// 35 | /// A normal, non-spanning table cell has a `colspan` of 1. A value greater than one indicates 36 | /// that this cell has expanded to cover up that number of columns. A value of zero means that 37 | /// this cell is being covered up by a previous cell in the same row. 38 | var colspan: UInt { 39 | get { 40 | guard case let .tableCell(colspan, _) = _data.raw.markup.data else { 41 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 42 | } 43 | return colspan 44 | } 45 | set { 46 | _data = _data.replacingSelf(.tableCell(parsedRange: nil, colspan: newValue, rowspan: rowspan, _data.raw.markup.copyChildren())) 47 | } 48 | } 49 | 50 | /// The number of rows this cell spans over. 51 | /// 52 | /// A normal, non-spanning table cell has a `rowspan` of 1. A value greater than one indicates 53 | /// that this cell has expanded to cover up that number of rows. A value of zero means that 54 | /// this cell is being covered up by another cell in a row above it. 55 | var rowspan: UInt { 56 | get { 57 | guard case let .tableCell(_, rowspan) = _data.raw.markup.data else { 58 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 59 | } 60 | return rowspan 61 | } 62 | set { 63 | _data = _data.replacingSelf(.tableCell(parsedRange: nil, colspan: colspan, rowspan: newValue, _data.raw.markup.copyChildren())) 64 | } 65 | } 66 | 67 | // MARK: BasicInlineContainer 68 | 69 | init(_ children: some Sequence) { 70 | self.init(colspan: 1, rowspan: 1, children) 71 | } 72 | 73 | init(_ children: some Sequence, inheritSourceRange: Bool) { 74 | self.init(colspan: 1, rowspan: 1, children, inheritSourceRange: inheritSourceRange) 75 | } 76 | 77 | init(colspan: UInt, rowspan: UInt, _ children: some Sequence) { 78 | self.init(colspan: colspan, rowspan: rowspan, children, inheritSourceRange: false) 79 | } 80 | 81 | init(colspan: UInt, rowspan: UInt, _ children: some Sequence, inheritSourceRange: Bool) { 82 | let rawChildren = children.map { $0.raw.markup } 83 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 84 | try! self.init(.tableCell(parsedRange: parsedRange, colspan: colspan, rowspan: rowspan, rawChildren)) 85 | } 86 | 87 | // MARK: Visitation 88 | 89 | func accept(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 90 | return visitor.visitTableCell(self) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableCellContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A container of ``Table/Cell`` elements. 12 | public protocol TableCellContainer: Markup, ExpressibleByArrayLiteral { 13 | /// Create a row from cells. 14 | /// 15 | /// - parameter cells: A sequence of ``Table/Cell`` elements from which to make this row. 16 | init(_ cells: Cells) where Cells.Element == Table.Cell 17 | } 18 | 19 | // MARK: - Public API 20 | 21 | public extension TableCellContainer { 22 | /// Create a row from one cell. 23 | /// 24 | /// - parameter cell: The one cell comprising the row. 25 | init(_ cell: Table.Cell) { 26 | self.init(CollectionOfOne(cell)) 27 | } 28 | 29 | /// Create a row from cells. 30 | /// 31 | /// - parameter cells: A sequence of ``Table/Cell`` elements from which to make this row. 32 | init(_ cells: Table.Cell...) { 33 | self.init(cells) 34 | } 35 | 36 | init(arrayLiteral elements: Table.Cell...) { 37 | self.init(elements) 38 | } 39 | 40 | /// The cells of the row. 41 | /// 42 | /// - Precondition: All children of a ``TableCellContainer`` must be a `Table.Cell`. 43 | var cells: LazyMapSequence { 44 | return children.lazy.map { $0 as! Table.Cell } 45 | } 46 | 47 | /// Replace all cells with a sequence of cells. 48 | /// 49 | /// - parameter newCells: A sequence of ``Table/Cell`` elements that will replace all of the cells in this row. 50 | mutating func setCells(_ newCells: Cells) where Cells.Element == Table.Cell { 51 | replaceCellsInRange(0..(_ range: Range, with incomingCells: Cells) where Cells.Element == Table.Cell { 56 | var rawChildren = raw.markup.copyChildren() 57 | rawChildren.replaceSubrange(range, with: incomingCells.map { $0.raw.markup }) 58 | let newRaw = raw.markup.withChildren(rawChildren) 59 | _data = _data.replacingSelf(newRaw) 60 | } 61 | 62 | /// Append a cell to the row. 63 | /// 64 | /// - parameter cell: The cell to append to the row. 65 | mutating func appendCell(_ cell: Table.Cell) { 66 | replaceCellsInRange(childCount..(_ cells: Cells) where Cells : Sequence, Cells.Element == Table.Cell { 36 | try! self.init(.tableHead(parsedRange: nil, columns: cells.map { $0.raw.markup })) 37 | } 38 | 39 | // MARK: Visitation 40 | 41 | func accept(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 42 | return visitor.visitTableHead(self) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableRow.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | public protocol _TableRowProtocol : TableCellContainer {} 12 | 13 | extension Table { 14 | /// A row of cells in a table. 15 | public struct Row: Markup, _TableRowProtocol { 16 | public var _data: _MarkupData 17 | 18 | init(_ data: _MarkupData) { 19 | self._data = data 20 | } 21 | 22 | init(_ raw: RawMarkup) throws { 23 | guard case .tableRow = raw.data else { 24 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Table.Row.self) 25 | } 26 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 27 | self.init(_MarkupData(absoluteRaw)) 28 | } 29 | } 30 | } 31 | 32 | // MARK: - Public API 33 | 34 | public extension Table.Row { 35 | 36 | // MARK: TableCellContainer 37 | 38 | init(_ cells: Cells) where Cells : Sequence, Cells.Element == Table.Cell { 39 | try! self.init(.tableRow(parsedRange: nil, cells.map { $0.raw.markup })) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 45 | return visitor.visitTableRow(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Infrastructure/Replacement.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A textual replacement. 12 | public struct Replacement: CustomStringConvertible, CustomDebugStringConvertible, Sendable { 13 | /// The range of source text to replace. 14 | public var range: SourceRange 15 | 16 | /// The text to substitute in the ``range``. 17 | public var replacementText: String 18 | 19 | /// Create a textual replacement. 20 | /// 21 | /// - parameter range: The range of the source text to replace. 22 | /// - parameter replacementText: The text to substitute in the range. 23 | public init(range: SourceRange, replacementText: String) { 24 | self.range = range 25 | self.replacementText = replacementText 26 | } 27 | 28 | public var description: String { 29 | return "\(range.diagnosticDescription()): fixit: \(replacementText)" 30 | } 31 | 32 | public var debugDescription: String { 33 | return description 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Markdown/Infrastructure/SourceLocation.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A location in a source file. 14 | public struct SourceLocation: Hashable, CustomStringConvertible, Comparable, Sendable { 15 | public static func < (lhs: SourceLocation, rhs: SourceLocation) -> Bool { 16 | if lhs.line < rhs.line { 17 | return true 18 | } else if lhs.line == rhs.line { 19 | return lhs.column < rhs.column 20 | } else { 21 | return false 22 | } 23 | } 24 | 25 | /// The line number of the location. 26 | public var line: Int 27 | 28 | /// The number of bytes in UTF-8 encoding from the start of the line to the character at this source location. 29 | public var column: Int 30 | 31 | /// The source file for which this location applies, if it came from an accessible location. 32 | public var source: URL? 33 | 34 | /// Create a source location with line, column, and optional source to which the location applies. 35 | /// 36 | /// - parameter line: The line number of the location, starting with 1. 37 | /// - parameter column: The column of the location, starting with 1. 38 | /// - parameter source: The URL in which the location resides, or `nil` if there is not a specific 39 | /// file or resource that needs to be identified. 40 | public init(line: Int, column: Int, source: URL?) { 41 | self.line = line 42 | self.column = column 43 | self.source = source 44 | } 45 | 46 | public var description: String { 47 | let path = source.map { 48 | $0.path.isEmpty 49 | ? "" 50 | : "\($0.path):" 51 | } ?? "" 52 | return "\(path)\(line):\(column)" 53 | } 54 | } 55 | 56 | extension Range { 57 | /// Widen this range to contain another. 58 | mutating func widen(toFit other: Self) { 59 | self = Swift.min(self.lowerBound, other.lowerBound).. 65 | 66 | extension SourceRange { 67 | @available(*, deprecated, message: "Use lowerBound.. String { 94 | let path = includePath ? lowerBound.source?.path ?? "" : "" 95 | var result = "" 96 | if !path.isEmpty { 97 | result += "\(path):" 98 | } 99 | result += "\(lowerBound)" 100 | if lowerBound != upperBound { 101 | result += "-\(upperBound)" 102 | } 103 | return result 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Emphasis.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A markup element that tags inline elements with emphasis. 12 | public struct Emphasis: RecurringInlineMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .emphasis = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Emphasis.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension Emphasis { 30 | // MARK: BasicInlineContainer 31 | 32 | init(_ newChildren: some Sequence) { 33 | self.init(newChildren, inheritSourceRange: false) 34 | } 35 | 36 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = newChildren.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.emphasis(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: PlainTextConvertibleMarkup 43 | 44 | var plainText: String { 45 | let childrenPlainText = children.compactMap { 46 | return ($0 as? InlineMarkup)?.plainText 47 | }.joined() 48 | return "\(childrenPlainText)" 49 | } 50 | 51 | // MARK: Visitation 52 | 53 | func accept(_ visitor: inout V) -> V.Result { 54 | return visitor.visitEmphasis(self) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Image.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline image reference. 12 | public struct Image: InlineMarkup, InlineContainer { 13 | public var _data: _MarkupData 14 | 15 | init(_ data: _MarkupData) { 16 | self._data = data 17 | } 18 | 19 | init(_ raw: RawMarkup) throws { 20 | guard case .image = raw.data else { 21 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Image.self) 22 | } 23 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 24 | self.init(_MarkupData(absoluteRaw)) 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension Image { 31 | /// Create an image from a source and zero or more child inline elements. 32 | init(source: String? = nil, title: String? = nil, _ children: Children) where Children.Element == RecurringInlineMarkup { 33 | let titleToUse: String? 34 | if let t = title, t.isEmpty { 35 | titleToUse = nil 36 | } else { 37 | titleToUse = title 38 | } 39 | 40 | let sourceToUse: String? 41 | if let s = source, s.isEmpty { 42 | sourceToUse = nil 43 | } else { 44 | sourceToUse = source 45 | } 46 | 47 | try! self.init(.image(source: sourceToUse, title: titleToUse, parsedRange: nil, children.map { $0.raw.markup })) 48 | } 49 | 50 | /// Create an image from a source and zero or more child inline elements. 51 | init(source: String? = nil, title: String? = nil, _ children: RecurringInlineMarkup...) { 52 | self.init(source: source, title: title, children) 53 | } 54 | 55 | /// The image's source. 56 | var source: String? { 57 | get { 58 | guard case let .image(source, _) = _data.raw.markup.data else { 59 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 60 | } 61 | return source 62 | } 63 | set { 64 | guard newValue != source else { 65 | return 66 | } 67 | if let s = newValue, s.isEmpty { 68 | _data = _data.replacingSelf(.image(source: nil, title: title, parsedRange: nil, _data.raw.markup.copyChildren())) 69 | } else { 70 | _data = _data.replacingSelf(.image(source: newValue, title: title, parsedRange: nil, _data.raw.markup.copyChildren())) 71 | } 72 | } 73 | } 74 | 75 | /// The image's title. 76 | var title: String? { 77 | get { 78 | guard case let .image(_, title) = _data.raw.markup.data else { 79 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 80 | } 81 | return title 82 | } 83 | set { 84 | guard newValue != title else { 85 | return 86 | } 87 | 88 | if let t = newValue, t.isEmpty { 89 | _data = _data.replacingSelf(.image(source: source, title: nil, parsedRange: nil, _data.raw.markup.copyChildren())) 90 | } else { 91 | _data = _data.replacingSelf(.image(source: source, title: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 92 | } 93 | } 94 | } 95 | 96 | // MARK: Visitation 97 | 98 | func accept(_ visitor: inout V) -> V.Result { 99 | return visitor.visitImage(self) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/InlineAttributes.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A set of one or more inline attributes. 12 | public struct InlineAttributes: InlineMarkup, InlineContainer { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .inlineAttributes = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: InlineAttributes.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension InlineAttributes { 31 | /// Create a set of custom inline attributes applied to zero or more child inline elements. 32 | init(attributes: String, _ children: Children) where Children.Element == RecurringInlineMarkup { 33 | try! self.init(.inlineAttributes(attributes: attributes, parsedRange: nil, children.map { $0.raw.markup })) 34 | } 35 | 36 | /// Create a set of custom attributes applied to zero or more child inline elements. 37 | init(attributes: String, _ children: RecurringInlineMarkup...) { 38 | self.init(attributes: attributes, children) 39 | } 40 | 41 | /// The specified attributes in JSON5 format. 42 | var attributes: String { 43 | get { 44 | guard case let .inlineAttributes(attributes) = _data.raw.markup.data else { 45 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 46 | } 47 | return attributes 48 | } 49 | set { 50 | _data = _data.replacingSelf(.inlineAttributes(attributes: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 51 | } 52 | } 53 | 54 | // MARK: Visitation 55 | 56 | func accept(_ visitor: inout V) -> V.Result { 57 | return visitor.visitInlineAttributes(self) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Strikethrough.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// Inline elements that should be rendered with a strike through them. 12 | public struct Strikethrough: RecurringInlineMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .strikethrough = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Strikethrough.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | init(_ data: _MarkupData) { 22 | self._data = data 23 | } 24 | } 25 | 26 | // MARK: - Public API 27 | 28 | public extension Strikethrough { 29 | // MARK: BasicInlineContainer 30 | 31 | init(_ newChildren: some Sequence) { 32 | self.init(newChildren, inheritSourceRange: false) 33 | } 34 | 35 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 36 | let rawChildren = newChildren.map { $0.raw.markup } 37 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 38 | try! self.init(.strikethrough(parsedRange: parsedRange, rawChildren)) 39 | } 40 | 41 | // MARK: PlainTextConvertibleMarkup 42 | 43 | var plainText: String { 44 | let childrenPlainText = children.compactMap { 45 | return ($0 as? InlineMarkup)?.plainText 46 | }.joined() 47 | return "~\(childrenPlainText)~" 48 | } 49 | 50 | // MARK: Visitation 51 | 52 | func accept(_ visitor: inout V) -> V.Result { 53 | return visitor.visitStrikethrough(self) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Strong.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element that tags inline elements with strong emphasis. 12 | public struct Strong: RecurringInlineMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .strong = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Strong.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | init(_ data: _MarkupData) { 22 | self._data = data 23 | } 24 | } 25 | 26 | // MARK: - Public API 27 | 28 | public extension Strong { 29 | // MARK: BasicInlineContainer 30 | 31 | init(_ newChildren: some Sequence) { 32 | self.init(newChildren, inheritSourceRange: false) 33 | } 34 | 35 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 36 | let rawChildren = newChildren.map { $0.raw.markup } 37 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 38 | try! self.init(.strong(parsedRange: parsedRange, rawChildren)) 39 | } 40 | 41 | // MARK: PlainTextConvertibleMarkup 42 | 43 | var plainText: String { 44 | let childrenPlainText = children.compactMap { 45 | return ($0 as? InlineMarkup)?.plainText 46 | }.joined() 47 | return "\(childrenPlainText)" 48 | } 49 | 50 | // MARK: Visitation 51 | 52 | func accept(_ visitor: inout V) -> V.Result { 53 | return visitor.visitStrong(self) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/CustomInline.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A custom inline markup element. 12 | /// 13 | /// - note: This element does not yet allow for custom information to be appended and is included for backward compatibility with CommonMark. It wraps raw text. 14 | public struct CustomInline: RecurringInlineMarkup { 15 | public var _data: _MarkupData 16 | init(_ raw: RawMarkup) throws { 17 | guard case .customInline = raw.data else { 18 | throw RawMarkup.Error.concreteConversionError(from: raw, to: CustomInline.self) 19 | } 20 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 21 | self.init(_MarkupData(absoluteRaw)) 22 | } 23 | 24 | init(_ data: _MarkupData) { 25 | self._data = data 26 | } 27 | } 28 | 29 | // MARK: - Public API 30 | 31 | public extension CustomInline { 32 | /// Create a custom inline element from raw text. 33 | init(_ text: String) { 34 | try! self.init(.customInline(parsedRange: nil, text: text)) 35 | } 36 | 37 | /// The raw inline text of the element. 38 | var text: String { 39 | guard case let .customInline(text) = _data.raw.markup.data else { 40 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 41 | } 42 | return text 43 | } 44 | 45 | // MARK: PlainTextConvertibleMarkup 46 | 47 | var plainText: String { 48 | return text 49 | } 50 | 51 | func accept(_ visitor: inout V) -> V.Result { 52 | return visitor.visitCustomInline(self) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/InlineCode.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline code markup element, representing some code-like or "code voice" text. 12 | public struct InlineCode: RecurringInlineMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .inlineCode = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: InlineCode.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension InlineCode { 31 | /// Create an inline code element from a string. 32 | init(_ code: String) { 33 | try! self.init(.inlineCode(parsedRange: nil, code: code)) 34 | } 35 | 36 | /// The literal text content. 37 | var code: String { 38 | get { 39 | guard case let .inlineCode(code) = _data.raw.markup.data else { 40 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 41 | } 42 | return code 43 | } 44 | set { 45 | self._data = _data.replacingSelf(.inlineCode(parsedRange: nil, code: newValue)) 46 | } 47 | } 48 | 49 | // MARK: PlainTextConvertibleMarkup 50 | 51 | var plainText: String { 52 | return "`\(code)`" 53 | } 54 | 55 | // MARK: Visitation 56 | 57 | func accept(_ visitor: inout V) -> V.Result { 58 | return visitor.visitInlineCode(self) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/InlineHTML.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline markup element containing raw HTML. 12 | public struct InlineHTML: RecurringInlineMarkup, LiteralMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .inlineHTML = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: InlineHTML.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension InlineHTML { 30 | init(_ literalText: String) { 31 | try! self.init(.inlineHTML(parsedRange: nil, html: literalText)) 32 | } 33 | 34 | /// The raw HTML text. 35 | var rawHTML: String { 36 | get { 37 | guard case let .inlineHTML(text) = _data.raw.markup.data else { 38 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 39 | } 40 | return text 41 | } 42 | set { 43 | _data = _data.replacingSelf(.inlineHTML(parsedRange: nil, html: newValue)) 44 | } 45 | } 46 | 47 | // MARK: PlainTextConvertibleMarkup 48 | 49 | var plainText: String { 50 | return rawHTML 51 | } 52 | 53 | // MARK: Visitation 54 | 55 | func accept(_ visitor: inout V) -> V.Result { 56 | return visitor.visitInlineHTML(self) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/LineBreak.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A line break. 12 | public struct LineBreak: RecurringInlineMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ data: _MarkupData) { 16 | self._data = data 17 | } 18 | 19 | init(_ raw: RawMarkup) throws { 20 | guard case .lineBreak = raw.data else { 21 | throw RawMarkup.Error.concreteConversionError(from: raw, to: LineBreak.self) 22 | } 23 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 24 | self.init(_MarkupData(absoluteRaw)) 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension LineBreak { 31 | /// Create a hard line break. 32 | init() { 33 | try! self.init(.lineBreak(parsedRange: nil)) 34 | } 35 | 36 | // MARK: PlainTextConvertibleMarkup 37 | 38 | var plainText: String { 39 | return "\n" 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitLineBreak(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/SoftBreak.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A soft break. 12 | public struct SoftBreak: RecurringInlineMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .softBreak = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: SoftBreak.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension SoftBreak { 31 | /// Create a soft line break. 32 | init() { 33 | try! self.init(.softBreak(parsedRange: nil)) 34 | } 35 | 36 | // MARK: PlainTextConvertibleMarkup 37 | 38 | var plainText: String { 39 | return " " 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitSoftBreak(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/SymbolLink.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A link to a symbol. 12 | /// 13 | /// Symbol links are written the same as inline code spans but with 14 | /// two backticks `\`` instead of one. The contents inside the backticks become 15 | /// the link's destination. 16 | /// 17 | /// Symbol links should be typically rendered with "code voice", usually 18 | /// monospace. 19 | public struct SymbolLink: InlineMarkup { 20 | public var _data: _MarkupData 21 | 22 | init(_ raw: RawMarkup) throws { 23 | guard case .symbolLink = raw.data else { 24 | throw RawMarkup.Error.concreteConversionError(from: raw, to: SymbolLink.self) 25 | } 26 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 27 | self.init(_MarkupData(absoluteRaw)) 28 | } 29 | 30 | init(_ data: _MarkupData) { 31 | self._data = data 32 | } 33 | } 34 | 35 | // MARK: - Public API 36 | 37 | public extension SymbolLink { 38 | /// Create a symbol link with a destination. 39 | init(destination: String? = nil) { 40 | try! self.init(.symbolLink(parsedRange: nil, destination: destination ?? "")) 41 | } 42 | 43 | /// The link's destination. 44 | var destination: String? { 45 | get { 46 | guard case let .symbolLink(destination) = _data.raw.markup.data else { 47 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 48 | } 49 | return destination 50 | } 51 | set { 52 | if let newDestination = newValue, newDestination.isEmpty { 53 | _data = _data.replacingSelf(.symbolLink(parsedRange: nil, destination: nil)) 54 | } else { 55 | _data = _data.replacingSelf(.symbolLink(parsedRange: nil, destination: newValue)) 56 | } 57 | } 58 | } 59 | 60 | // MARK: Visitation 61 | 62 | func accept(_ visitor: inout V) -> V.Result { 63 | return visitor.visitSymbolLink(self) 64 | } 65 | 66 | // MARK: PlainTextConvertibleMarkup 67 | 68 | var plainText: String { 69 | return "``\(destination ?? "")``" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/Text.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// Plain text. 12 | public struct Text: RecurringInlineMarkup, LiteralMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .text = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Text.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension Text { 31 | init(_ literalText: String) { 32 | try! self.init(.text(parsedRange: nil, string: literalText)) 33 | } 34 | 35 | /// The raw text of the element. 36 | var string: String { 37 | get { 38 | guard case let .text(string) = _data.raw.markup.data else { 39 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 40 | } 41 | return string 42 | } 43 | set { 44 | _data = _data.replacingSelf(.text(parsedRange: nil, string: newValue)) 45 | } 46 | } 47 | 48 | // MARK: PlainTextConvertibleMarkup 49 | 50 | var plainText: String { 51 | return string 52 | } 53 | 54 | // MARK: Visitation 55 | 56 | func accept(_ visitor: inout V) -> V.Result { 57 | return visitor.visitText(self) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown.md: -------------------------------------------------------------------------------- 1 | # ``Markdown`` 2 | 3 | Swift `Markdown` is a Swift package for parsing, building, editing, and analyzing Markdown documents. 4 | 5 | ## Overview 6 | 7 | The parser is powered by GitHub-flavored Markdown's [cmark-gfm](https://github.com/github/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. 8 | 9 | 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 [SwiftSyntax](https://github.com/swiftlang/swift-syntax). 10 | 11 | ## Topics 12 | 13 | ### Snippets 14 | 15 | A quick overview of examples showing tasks you can achieve with Swift Markdown. 16 | 17 | - 18 | 19 | ### Getting Started 20 | 21 | - 22 | - 23 | 24 | ### Essentials 25 | 26 | - ``Markup`` 27 | - ``MarkupChildren`` 28 | - ``ChildIndexPath`` 29 | - ``TypedChildIndexPath`` 30 | - ``DirectiveArgument`` 31 | - ``DirectiveArgumentText`` 32 | - ``Document`` 33 | - ``LiteralMarkup`` 34 | - ``PlainTextConvertibleMarkup`` 35 | 36 | ### Markup Types 37 | 38 | - 39 | - 40 | - ``Aside`` 41 | 42 | ### Infrastructure 43 | 44 | - 45 | 46 | ### Visit Markup 47 | 48 | - 49 | - 50 | 51 | 52 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/BlockMarkup.md: -------------------------------------------------------------------------------- 1 | # Markup Block Nodes 2 | 3 | ## Topics 4 | 5 | ### Block Container Blocks 6 | - ``BlockDirective`` 7 | - ``BlockQuote`` 8 | - ``CustomBlock`` 9 | - ``ListItem`` 10 | - ``OrderedList`` 11 | - ``UnorderedList`` 12 | 13 | ### Inline Container Blocks 14 | - ``Paragraph`` 15 | 16 | ### Leaf Blocks 17 | - ``Heading`` 18 | - ``HTMLBlock`` 19 | - ``ThematicBreak`` 20 | - ``CodeBlock`` 21 | 22 | ### Tables 23 | 24 | - ``Table`` 25 | - ``TableCellContainer`` 26 | 27 | ## See Also 28 | - 29 | - 30 | 31 | 32 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/DoxygenCommands.md: -------------------------------------------------------------------------------- 1 | # Doxygen Commands 2 | 3 | Include a limited set of Doxygen commands in parsed Markdown. 4 | 5 | Swift Markdown includes an option to parse a limited set of Doxygen commands, to facilitate 6 | transitioning from a different Markdown parser. To include these commands in the output, include 7 | the options ``ParseOptions/parseBlockDirectives`` and ``ParseOptions/parseMinimalDoxygen`` when 8 | parsing a ``Document``. In the resulting document, parsed Doxygen commands appear as regular 9 | ``Markup`` types in the hierarchy. 10 | 11 | ## Parsing Strategy 12 | 13 | Doxygen commands are written by using either a backslash (`\`) or an at-sign (`@`) character, 14 | followed by the name of the command. Any parameters are then parsed as whitespace-separated words, 15 | then a "description" argument is taken from the remainder of the line, as well as all lines 16 | immediately after the command, until the parser sees a blank line, another Doxygen command, or a 17 | block directive. The description is then parsed for regular Markdown formatting, which is then 18 | stored as the children of the command type. For example, with Doxygen parsing turned on, the 19 | following document will parse three separate commands and one block directive: 20 | 21 | ```markdown 22 | \param thing The thing. 23 | This is the thing that is modified. 24 | \param otherThing The other thing. 25 | 26 | \returns A thing that has been modified. 27 | @Comment { 28 | This is not part of the `\returns` command. 29 | } 30 | ``` 31 | 32 | Trailing lines in a command's description are allowed to be indented relative to the command. For 33 | example, the description below is parsed as a paragraph, not a code block: 34 | 35 | ```markdown 36 | \param thing 37 | The thing. 38 | This is the thing that is modified. 39 | ``` 40 | 41 | Doxygen commands are not parsed within code blocks or block directive content. 42 | 43 | ## Topics 44 | 45 | ### Commands 46 | 47 | - ``DoxygenAbstract`` 48 | - ``DoxygenDiscussion`` 49 | - ``DoxygenNote`` 50 | - ``DoxygenParameter`` 51 | - ``DoxygenReturns`` 52 | 53 | 54 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/FormatterAndOptions.md: -------------------------------------------------------------------------------- 1 | # Formatter and Options 2 | 3 | ## Topics 4 | 5 | ### Formatter 6 | 7 | - ``MarkupFormatter`` 8 | - ``HTMLFormatter`` 9 | 10 | ### Options 11 | 12 | - ``MarkupDumpOptions`` 13 | - ``HTMLFormatterOptions`` 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/Infrastructure.md: -------------------------------------------------------------------------------- 1 | # Infrastructure 2 | 3 | ## Topics 4 | 5 | ### Replacement 6 | 7 | - ``Replacement`` 8 | 9 | ### Source 10 | 11 | - ``SourceLocation`` 12 | - ``SourceRange`` 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/InlineMarkup.md: -------------------------------------------------------------------------------- 1 | # Markup Inline Nodes 2 | 3 | ## Topics 4 | 5 | ### Inline Container 6 | - ``Emphasis`` 7 | - ``Image`` 8 | - ``Link`` 9 | - ``Strikethrough`` 10 | - ``Strong`` 11 | 12 | ### Inline Leaves 13 | - ``CustomInline`` 14 | - ``InlineCode`` 15 | - ``InlineHTML`` 16 | - ``LineBreak`` 17 | - ``SoftBreak`` 18 | - ``SymbolLink`` 19 | - ``Text`` 20 | 21 | 22 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/VisitMarkup.md: -------------------------------------------------------------------------------- 1 | # Visiting Markup 2 | 3 | ## Topics 4 | 5 | ### Vistor 6 | 7 | - ``MarkupVisitor`` 8 | 9 | ### Walker 10 | 11 | ``MarkupWalker`` is a default implementation for ``MarkupVisitor``. 12 | 13 | - ``MarkupWalker`` 14 | 15 | ### Rewriter 16 | 17 | - ``MarkupRewriter`` 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Parsing-Building-and-Modifying Markup-Trees.md: -------------------------------------------------------------------------------- 1 | # Parsing, Building, and Modifying Markup Trees 2 | 3 | Get started with Swift-Markdown's markup trees. 4 | 5 | ## Parsing 6 | 7 | To create a new ``Document`` by parsing markdown content, use Document's ``Document/init(parsing:options:)`` initializer, supplying a `String` or `URL`: 8 | 9 | ```swift 10 | import Markdown 11 | 12 | let source = "This is a markup *document*." 13 | let document = Document(parsing: source) 14 | print(document.debugDescription()) 15 | // Document 16 | // └─ Paragraph 17 | // ├─ Text "This is a markup " 18 | // ├─ Emphasis 19 | // │ └─ Text "document" 20 | // └─ Text "." 21 | ``` 22 | 23 | Parsing text is just one way to build a tree of ``Markup`` elements. You can also build them yourself declaratively. 24 | 25 | ## Building Markup Trees 26 | 27 | You can build trees using initializers for the various element types provided. 28 | 29 | ```swift 30 | import Markdown 31 | 32 | let document = Document( 33 | Paragraph( 34 | Text("This is a "), 35 | Emphasis( 36 | Text("paragraph.")))) 37 | ``` 38 | 39 | This would be equivalent to parsing `"This is a *paragraph.*"` but allows you to programmatically insert content from other data sources into individual elements. 40 | 41 | ## Modifying Markup Trees with Persistence 42 | 43 | Swift Markdown uses a [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure) 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. 44 | 45 | ### Modifying Elements Directly 46 | 47 | 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. 48 | 49 | ```swift 50 | import Markdown 51 | 52 | let source = "This is *emphasized.*" 53 | let document = Document(parsing: source) 54 | print(document.debugDescription()) 55 | // Document 56 | // └─ Paragraph 57 | // ├─ Text "This is " 58 | // └─ Emphasis 59 | // └─ Text "emphasized." 60 | 61 | var text = document.child(through: 62 | 0, // Paragraph 63 | 1, // Emphasis 64 | 0) as! Text // Text 65 | 66 | text.string = "really emphasized!" 67 | print(text.root.debugDescription()) 68 | // Document 69 | // └─ Paragraph 70 | // ├─ Text "This is " 71 | // └─ Emphasis 72 | // └─ Text "really emphasized!" 73 | 74 | // The original document is unchanged: 75 | 76 | print(document.debugDescription()) 77 | // Document 78 | // └─ Paragraph 79 | // ├─ Text "This is " 80 | // └─ Emphasis 81 | // └─ Text "emphasized." 82 | ``` 83 | 84 | 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](https://en.wikipedia.org/wiki/Visitor_pattern) is what you want. 85 | 86 | 87 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Snippets.md: -------------------------------------------------------------------------------- 1 | # Snippets 2 | 3 | ## Parsing 4 | 5 | Parse strings in memory or files on disk into a structured ``Markup`` tree. 6 | 7 | @Snippet(path: "swift-markdown/Snippets/Parsing/ParseDocumentString") 8 | @Snippet(path: "swift-markdown/Snippets/Parsing/ParseDocumentFile") 9 | 10 | ## Querying 11 | 12 | @Snippet(path: "swift-markdown/Snippets/Querying/ChildThrough") 13 | 14 | ## Walkers, Rewriters, and Visitors 15 | 16 | Use ``MarkupWalker`` to collect information about ``Markup`` trees without modifying their contents. 17 | 18 | @Snippet(path: "swift-markdown/Snippets/Walkers/LinkCollector") 19 | 20 | Use ``MarkupRewriter`` to programmatically change the structure and contents of ``Markup`` trees. 21 | 22 | @Snippet(path: "swift-markdown/Snippets/Rewriters/RemoveElementKind") 23 | @Snippet(path: "swift-markdown/Snippets/Rewriters/ReplaceText") 24 | 25 | Use ``MarkupVisitor`` to convert a ``Markup`` tree to another nested structure. 26 | 27 | @Snippet(path: "swift-markdown/Snippets/Visitors/XMLConverter") 28 | 29 | ## Formatting 30 | 31 | Use the following formatting options alone or in combination to format 32 | a Markdown document to a consistent, preferred style. 33 | 34 | @Snippet(path: "swift-markdown/Snippets/Formatting/DefaultFormatting") 35 | @Snippet(path: "swift-markdown/Snippets/Formatting/MaximumWidth") 36 | @Snippet(path: "swift-markdown/Snippets/Formatting/CondenseAutolinks") 37 | @Snippet(path: "swift-markdown/Snippets/Formatting/CustomLinePrefix") 38 | @Snippet(path: "swift-markdown/Snippets/Formatting/EmphasisMarkers") 39 | @Snippet(path: "swift-markdown/Snippets/Formatting/OrderedListNumerals") 40 | @Snippet(path: "swift-markdown/Snippets/Formatting/UnorderedListMarker") 41 | @Snippet(path: "swift-markdown/Snippets/Formatting/PreferredHeadingStyle") 42 | @Snippet(path: "swift-markdown/Snippets/Formatting/ThematicBreakCharacter") 43 | @Snippet(path: "swift-markdown/Snippets/Formatting/UseCodeFence") 44 | 45 | 46 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Visitors-Walkers-and-Rewriters.md: -------------------------------------------------------------------------------- 1 | # Visitors, Walkers, and Rewriters 2 | 3 | Use `MarkupVisitor` to transform, walk, and rewrite markup trees. 4 | 5 | ## Markup Visitor 6 | 7 | The core ``MarkupVisitor`` protocol provides the basis for transforming, walking, or rewriting a markup tree. 8 | 9 | ```swift 10 | public protocol MarkupVisitor { 11 | associatedtype Result 12 | } 13 | ``` 14 | 15 | Using its ``MarkupVisitor/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. 16 | 17 | ## Markup Walker 18 | 19 | The first refinement of `MarkupVisitor`, ``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. 20 | 21 | ```swift 22 | import Markdown 23 | 24 | /// Counts `Link`s in a `Document`. 25 | struct LinkCounter: MarkupWalker { 26 | var count = 0 27 | mutating func visitLink(_ link: Link) { 28 | if link.destination == "https://swift.org" { 29 | count += 1 30 | } 31 | descendInto(link) 32 | } 33 | } 34 | 35 | let source = "There are [two](https://swift.org) links to here." 36 | let document = Document(parsing: source) 37 | print(document.debugDescription()) 38 | var linkCounter = LinkCounter() 39 | linkCounter.visit(document) 40 | print(linkCounter.count) 41 | // 2 42 | ``` 43 | 44 | ## Markup Rewriter 45 | 46 | The second refinement, ``MarkupRewriter``, has an associated `Result` type of an optional ``Markup`` element, 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. 47 | 48 | ```swift 49 | import Markdown 50 | 51 | /// Delete all **strong** elements in a markup tree. 52 | struct StrongDeleter: MarkupRewriter { 53 | mutating func visitStrong(_ strong: Strong) -> Markup? { 54 | return nil 55 | } 56 | } 57 | 58 | let source = "Now you see me, **now you don't**" 59 | let document = Document(parsing: source) 60 | var strongDeleter = StrongDeleter() 61 | let newDocument = strongDeleter.visit(document) 62 | 63 | print(newDocument!.debugDescription()) 64 | // Document 65 | // └─ Paragraph 66 | // └─ Text "Now you see me, " 67 | ``` 68 | 69 | 70 | -------------------------------------------------------------------------------- /Sources/Markdown/Parser/LazySplitLines.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A lazy sequence of split lines that keeps track of initial indentation and 14 | /// consecutive runs of empty lines. 15 | struct LazySplitLines: Sequence { 16 | struct Iterator: IteratorProtocol { 17 | /// The current running count of consecutive empty lines before the current iteration. 18 | private var precedingConsecutiveEmptyLineCount = 0 19 | 20 | /// The raw lines to be iterated. 21 | private let rawLines: [Substring] 22 | 23 | /// The current index of the iteration. 24 | private var index: Array.Index 25 | 26 | /// The source file or resource from which the line came, 27 | /// or `nil` if no such file or resource can be identified. 28 | private var source: URL? 29 | 30 | init(_ input: S, source: URL?) where S.SubSequence == Substring { 31 | self.rawLines = input.split(omittingEmptySubsequences: false, whereSeparator: \.isNewline) 32 | self.index = rawLines.startIndex 33 | self.source = source 34 | } 35 | 36 | mutating func next() -> TrimmedLine? { 37 | guard index != rawLines.endIndex else { 38 | return nil 39 | } 40 | 41 | let segment = TrimmedLine(rawLines[index], source: source, lineNumber: index + 1) 42 | 43 | index = rawLines.index(after: index) 44 | 45 | if segment.text.isEmpty { 46 | precedingConsecutiveEmptyLineCount += 1 47 | } else { 48 | precedingConsecutiveEmptyLineCount = 0 49 | } 50 | 51 | return segment 52 | } 53 | } 54 | 55 | /// The input to be lazily split on newlines. 56 | private let input: Substring 57 | 58 | /// The source file or resource from which the line came, 59 | /// or `nil` if no such file or resource can be identified. 60 | private let source: URL? 61 | 62 | init(_ input: Substring, source: URL?) { 63 | self.input = input 64 | self.source = source 65 | } 66 | 67 | func makeIterator() -> Iterator { 68 | return Iterator(input, source: source) 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /Sources/Markdown/Parser/ParseOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// Options for parsing Markdown. 12 | public struct ParseOptions: OptionSet, Sendable { 13 | public var rawValue: UInt 14 | 15 | public init(rawValue: UInt) { 16 | self.rawValue = rawValue 17 | } 18 | 19 | /// Enable block directive syntax. 20 | public static let parseBlockDirectives = ParseOptions(rawValue: 1 << 0) 21 | 22 | /// Enable interpretation of symbol links from inline code spans surrounded by two backticks. 23 | public static let parseSymbolLinks = ParseOptions(rawValue: 1 << 1) 24 | 25 | /// Disable converting straight quotes to curly, --- to em dashes, -- to en dashes during parsing. 26 | public static let disableSmartOpts = ParseOptions(rawValue: 1 << 2) 27 | 28 | /// Parse a limited set of Doxygen commands. Requires ``parseBlockDirectives``. 29 | public static let parseMinimalDoxygen = ParseOptions(rawValue: 1 << 3) 30 | 31 | /// Disable including a `data-sourcepos` attribute on all block elements during parsing. 32 | public static let disableSourcePosOpts = ParseOptions(rawValue: 1 << 4) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Sources/Markdown/Parser/RangeAdjuster.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A type for adjusting the columns of elements that are parsed in *line runs* 12 | /// of the block directive parser to their locations before their indentation was trimmed. 13 | struct RangeAdjuster: MarkupWalker { 14 | /// The line number of the first line in the line run that needs adjustment. 15 | var startLine: Int 16 | 17 | /// The tracker that will collect the adjusted ranges. 18 | var ranges: RangeTracker 19 | 20 | /// An array of whitespace spans that were removed for each line, indexed 21 | /// by line number. `nil` means that no whitespace was removed on that line. 22 | var trimmedIndentationPerLine: [Int] 23 | 24 | mutating func defaultVisit(_ markup: Markup) { 25 | /// This should only be used in the parser where ranges are guaranteed 26 | /// to be filled in from cmark. 27 | let adjustedRange = markup.range.map { range -> SourceRange in 28 | // Add back the offset to the column as if the indentation weren't stripped. 29 | let start = SourceLocation(line: startLine + range.lowerBound.line - 1, 30 | column: range.lowerBound.column + (trimmedIndentationPerLine[range.lowerBound.line - 1] ), 31 | source: range.lowerBound.source) 32 | let end = SourceLocation(line: startLine + range.upperBound.line - 1, 33 | column: range.upperBound.column + (trimmedIndentationPerLine[range.upperBound.line - 1]), 34 | source: range.upperBound.source) 35 | return start.. Markup? { 16 | let newChildren = markup.children.compactMap { 17 | return self.visit($0) 18 | } 19 | return markup.withUncheckedChildren(newChildren) 20 | } 21 | public mutating func visitBlockQuote(_ blockQuote: BlockQuote) -> Result { 22 | return defaultVisit(blockQuote) 23 | } 24 | public mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> Result { 25 | return defaultVisit(codeBlock) 26 | } 27 | public mutating func visitCustomBlock(_ customBlock: CustomBlock) -> Result { 28 | return defaultVisit(customBlock) 29 | } 30 | public mutating func visitDocument(_ document: Document) -> Result { 31 | return defaultVisit(document) 32 | } 33 | public mutating func visitHeading(_ heading: Heading) -> Result { 34 | return defaultVisit(heading) 35 | } 36 | public mutating func visitThematicBreak(_ thematicBreak: ThematicBreak) -> Result { 37 | return defaultVisit(thematicBreak) 38 | } 39 | public mutating func visitHTMLBlock(_ html: HTMLBlock) -> Result { 40 | return defaultVisit(html) 41 | } 42 | public mutating func visitListItem(_ listItem: ListItem) -> Result { 43 | return defaultVisit(listItem) 44 | } 45 | public mutating func visitOrderedList(_ orderedList: OrderedList) -> Result { 46 | return defaultVisit(orderedList) 47 | } 48 | public mutating func visitUnorderedList(_ unorderedList: UnorderedList) -> Result { 49 | return defaultVisit(unorderedList) 50 | } 51 | public mutating func visitParagraph(_ paragraph: Paragraph) -> Result { 52 | return defaultVisit(paragraph) 53 | } 54 | public mutating func visitBlockDirective(_ blockDirective: BlockDirective) -> Result { 55 | return defaultVisit(blockDirective) 56 | } 57 | public mutating func visitInlineCode(_ inlineCode: InlineCode) -> Result { 58 | return defaultVisit(inlineCode) 59 | } 60 | public mutating func visitCustomInline(_ customInline: CustomInline) -> Result { 61 | return defaultVisit(customInline) 62 | } 63 | public mutating func visitEmphasis(_ emphasis: Emphasis) -> Result { 64 | return defaultVisit(emphasis) 65 | } 66 | public mutating func visitImage(_ image: Image) -> Result { 67 | return defaultVisit(image) 68 | } 69 | public mutating func visitInlineHTML(_ inlineHTML: InlineHTML) -> Result { 70 | return defaultVisit(inlineHTML) 71 | } 72 | public mutating func visitLineBreak(_ lineBreak: LineBreak) -> Result { 73 | return defaultVisit(lineBreak) 74 | } 75 | public mutating func visitLink(_ link: Link) -> Result { 76 | return defaultVisit(link) 77 | } 78 | public mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> Result { 79 | return defaultVisit(attributes) 80 | } 81 | public mutating func visitSoftBreak(_ softBreak: SoftBreak) -> Result { 82 | return defaultVisit(softBreak) 83 | } 84 | public mutating func visitStrong(_ strong: Strong) -> Result { 85 | return defaultVisit(strong) 86 | } 87 | public mutating func visitText(_ text: Text) -> Result { 88 | return defaultVisit(text) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BasicBlockContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block element that can contain only other block elements and doesn't require any other information. 12 | public protocol BasicBlockContainer: BlockContainer { 13 | /// Create this element from a sequence of block markup elements. 14 | init(_ children: some Sequence) 15 | 16 | /// Create this element from a sequence of block markup elements, and optionally inherit the source range from those elements. 17 | init(_ children: some Sequence, inheritSourceRange: Bool) 18 | } 19 | 20 | // MARK: - Public API 21 | 22 | extension BasicBlockContainer { 23 | /// Create this element with a sequence of block markup elements. 24 | public init(_ children: BlockMarkup...) { 25 | self.init(children) 26 | } 27 | 28 | /// Create this element with a sequence of block markup elements, and optionally inherit the source range from those elements. 29 | public init(_ children: BlockMarkup..., inheritSourceRange: Bool) { 30 | self.init(children, inheritSourceRange: inheritSourceRange) 31 | } 32 | 33 | /// Default implementation of `init(_:inheritSourceRange:)` that discards the `inheritSourceRange` parameter. 34 | public init(_ children: some Sequence, inheritSourceRange: Bool) { 35 | self.init(children) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BasicInlineContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block or inline markup element that can contain only `InlineMarkup` elements and doesn't require any other information. 12 | public protocol BasicInlineContainer: InlineContainer { 13 | /// Create this element with a sequence of inline markup elements. 14 | init(_ children: some Sequence) 15 | 16 | /// Create this element with a sequence of inline markup elements, and optionally inherit the source range from those elements. 17 | init(_ children: some Sequence, inheritSourceRange: Bool) 18 | } 19 | 20 | extension BasicInlineContainer { 21 | /// Create this element with a sequence of inline markup elements. 22 | public init(_ children: InlineMarkup...) { 23 | self.init(children) 24 | } 25 | 26 | public init(_ children: InlineMarkup..., inheritSourceRange: Bool) { 27 | self.init(children, inheritSourceRange: inheritSourceRange) 28 | } 29 | 30 | /// Default implementation for `init(_:inheritSourceRange:)` that discards the `inheritSourceRange` parameter. 31 | public init(_ children: some Sequence, inheritSourceRange: Bool) { 32 | self.init(children) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BlockContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block element whose children must conform to `BlockMarkup` 12 | public protocol BlockContainer: BlockMarkup {} 13 | 14 | // MARK: - Public API 15 | 16 | public extension BlockContainer { 17 | /// The inline child elements of this element. 18 | /// 19 | /// - Precondition: All children of an `InlineContainer` 20 | /// must conform to `InlineMarkup`. 21 | var blockChildren: LazyMapSequence { 22 | return children.lazy.map { $0 as! BlockMarkup } 23 | } 24 | 25 | /// Replace all inline child elements with a new sequence of inline elements. 26 | mutating func setBlockChildren(_ newChildren: Items) where Items.Element == BlockMarkup { 27 | replaceChildrenInRange(0..(_ range: Range, with incomingItems: Items) where Items.Element == BlockMarkup { 32 | var rawChildren = raw.markup.copyChildren() 33 | rawChildren.replaceSubrange(range, with: incomingItems.map { $0.raw.markup }) 34 | let newRaw = raw.markup.withChildren(rawChildren) 35 | _data = _data.replacingSelf(newRaw) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BlockMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block markup element. 12 | public protocol BlockMarkup: Markup {} 13 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/InlineContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element whose children must conform to `InlineMarkup` 12 | public protocol InlineContainer: PlainTextConvertibleMarkup {} 13 | 14 | // MARK: - Public API 15 | 16 | public extension InlineContainer { 17 | /// The inline child elements of this element. 18 | /// 19 | /// - Precondition: All children of an `InlineContainer` 20 | /// must conform to `InlineMarkup`. 21 | var inlineChildren: LazyMapSequence { 22 | return children.lazy.map { $0 as! InlineMarkup } 23 | } 24 | 25 | /// Replace all inline child elements with a new sequence of inline elements. 26 | mutating func setInlineChildren(_ newChildren: Items) where Items.Element == InlineMarkup { 27 | replaceChildrenInRange(0..(_ range: Range, with incomingItems: Items) where Items.Element == InlineMarkup { 32 | var rawChildren = raw.markup.copyChildren() 33 | rawChildren.replaceSubrange(range, with: incomingItems.map { $0.raw.markup }) 34 | let newRaw = raw.markup.withChildren(rawChildren) 35 | _data = _data.replacingSelf(newRaw) 36 | } 37 | 38 | // MARK: PlainTextConvertibleMarkup 39 | 40 | var plainText: String { 41 | return children.compactMap { 42 | return ($0 as? InlineMarkup)?.plainText 43 | }.joined() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/InlineMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline markup element. 12 | public protocol InlineMarkup: PlainTextConvertibleMarkup {} 13 | 14 | /// An inline element that can recur throughout any structure. 15 | /// 16 | /// This is mostly used to prevent some kinds of elements from nesting; for 17 | /// example, you cannot put a ``Link`` inside another ``Link`` or an ``Image`` 18 | /// inside another ``Image``. 19 | public protocol RecurringInlineMarkup: InlineMarkup {} 20 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/ListItemContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A markup element that can contain only `ListItem`s as children and require no other information. 12 | public protocol ListItemContainer: BlockMarkup { 13 | /// Create a list from a sequence of items. 14 | init(_ items: Items) where Items.Element == ListItem 15 | } 16 | 17 | // MARK: - Public API 18 | 19 | public extension ListItemContainer { 20 | /// Create a list with one item. 21 | init(_ item: ListItem) { 22 | self.init(CollectionOfOne(item)) 23 | } 24 | /// Create a list with the given `ListItem`s. 25 | init(_ items: ListItem...) { 26 | self.init(items) 27 | } 28 | 29 | /// The items of the list. 30 | /// 31 | /// - Precondition: All children of a `ListItemContainer` 32 | /// must be a `ListItem`. 33 | var listItems: LazyMapSequence { 34 | return children.lazy.map { $0 as! ListItem } 35 | } 36 | 37 | /// Replace all list items with a sequence of items. 38 | mutating func setListItems(_ newItems: Items) where Items.Element == ListItem { 39 | replaceItemsInRange(0..(_ range: Range, with incomingItems: Items) where Items.Element == ListItem { 44 | var rawChildren = raw.markup.copyChildren() 45 | rawChildren.replaceSubrange(range, with: incomingItems.map { $0.raw.markup }) 46 | let newRaw = raw.markup.withChildren(rawChildren) 47 | _data = _data.replacingSelf(newRaw) 48 | } 49 | 50 | /// Append an item to the list. 51 | mutating func appendItem(_ item: ListItem) { 52 | replaceItemsInRange(childCount..=6.0) 12 | internal import CAtomic 13 | #else 14 | @_implementationOnly import CAtomic 15 | #endif 16 | 17 | /// A wrapper for a 64-bit unsigned atomic singleton counter. 18 | struct AtomicCounter { 19 | /// The current counter value. 20 | static var current: UInt64 { 21 | return _cmarkup_current_unique_id() 22 | } 23 | 24 | /// Atomically increment and return the latest counter value. 25 | static func next() -> UInt64 { 26 | return _cmarkup_increment_and_get_unique_id() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Markdown/Utility/CharacterExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension Character { 12 | /// The character as a ``Swift.String`` surrounded by single quotation marks `'`. 13 | var singleQuoted: String { 14 | return "'\(self)'" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Markdown/Utility/CollectionExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension RangeReplaceableCollection { 12 | /// Append filler elements until ``count`` is at least `minCount`. 13 | mutating func ensureCount(atLeast minCount: Int, filler: Element) { 14 | let neededElementCount = minCount - count 15 | if neededElementCount > 0 { 16 | self.append(contentsOf: Array(repeating: filler, count: neededElementCount)) 17 | } 18 | } 19 | 20 | /// Return a copy of `self` with filler elements appended until ``count`` is at least `minCount`. 21 | func ensuringCount(atLeast minCount: Int, filler: Element) -> Self { 22 | var maybeExtend = self 23 | maybeExtend.ensureCount(atLeast: minCount, filler: filler) 24 | return maybeExtend 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Markdown/Utility/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension StringProtocol { 12 | /// `self` surrounded by single quotation marks `'`. 13 | var singleQuoted: String { 14 | return "'\(self)'" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Markdown/Walker/MarkupWalker.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An interface for walking a `Markup` tree without altering it. 12 | public protocol MarkupWalker: MarkupVisitor where Result == Void {} 13 | 14 | extension MarkupWalker { 15 | /// Continue walking by descending in the given element. 16 | /// 17 | /// - Parameter markup: the element whose children the walker should visit. 18 | public mutating func descendInto(_ markup: Markup) { 19 | for child in markup.children { 20 | visit(child) 21 | } 22 | } 23 | public mutating func defaultVisit(_ markup: Markup) { 24 | descendInto(markup) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/AtomicCounterTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class AtomicCounterTests: XCTestCase { 15 | func testIncremental() { 16 | XCTAssertEqual(AtomicCounter.current, AtomicCounter.current) 17 | XCTAssertNotEqual(AtomicCounter.next(), AtomicCounter.next()) 18 | } 19 | 20 | func testSimultaneousFetch() { 21 | var counters = Set() 22 | let group = DispatchGroup() 23 | let fetchQueue = DispatchQueue(label: "AtomicCounterTests.testSimultaneousFetch.fetch", attributes: [.concurrent]) 24 | let collectQueue = DispatchQueue(label: "AtomicCounterTests.testSimultaneousFetch.collect") 25 | let numTasks = 4 26 | let idsPerQueue = 200000 27 | for _ in 0.. Int { 16 | let total = 17 | (pow(Double(N), Double(h + 1)) - 1) 18 | / 19 | Double(N - 1) 20 | return Int(total) 21 | } 22 | 23 | func buildCustomBlock(height: Int, width: Int) -> CustomBlock { 24 | guard height > 0 else { 25 | return CustomBlock() 26 | } 27 | return CustomBlock(Array(repeating: buildCustomBlock(height: height - 1, width: width), count: width)) 28 | } 29 | 30 | /// No two children should have the same child identifier. 31 | func testChildIDsAreUnique() { 32 | let height = 5 33 | let width = 5 34 | 35 | let customBlock = buildCustomBlock(height: height, width: width) 36 | 37 | struct IDCounter: MarkupWalker { 38 | var id = 0 39 | 40 | mutating func defaultVisit(_ markup: Markup) { 41 | XCTAssertEqual(id, markup._data.id.childId) 42 | id += 1 43 | descendInto(markup) 44 | } 45 | } 46 | 47 | var counter = IDCounter() 48 | counter.visit(customBlock) 49 | XCTAssertEqual(totalElementsInTree(height: height, width: width), counter.id) 50 | } 51 | 52 | /// The very first child id shall be 1 greater than that of its parent. 53 | func testFirstChildIdentifier() { 54 | func checkFirstChildOf(_ markup: Markup, expectedId: Int) { 55 | guard let firstChild = markup.child(at: 0) else { 56 | return 57 | } 58 | XCTAssertEqual(expectedId, firstChild.raw.metadata.id.childId) 59 | // As we descend depth-first, each first child identifier shall be one more than the last. 60 | checkFirstChildOf(firstChild, expectedId: expectedId + 1) 61 | } 62 | 63 | checkFirstChildOf(buildCustomBlock(height: 100, width: 1), expectedId: 1) 64 | } 65 | 66 | func testNextSiblingIdentifier() { 67 | let height = 2 68 | let width = 100 69 | let customBlock = buildCustomBlock(height: height, width: width) 70 | 71 | var id = 1 72 | for child in customBlock.children { 73 | // Every branch in the tree should use 1 + 100 identifiers. 74 | XCTAssertEqual(id, child.raw.metadata.id.childId) 75 | id += width + 1 76 | } 77 | } 78 | 79 | func testPreviousSiblingIdentifier() { 80 | let height = 2 81 | let width = 100 82 | let customBlock = buildCustomBlock(height: height, width: width) 83 | 84 | var id = totalElementsInTree(height: height, width: width) 85 | for child in customBlock.children.reversed() { 86 | // Every branch in the tree should use 1 + 100 identifiers. 87 | XCTAssertEqual(id, child.raw.metadata.id.childId) 88 | id -= width + 1 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/PlainTextConvertibleMarkupTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class PlainTextConvertibleMarkupTests: XCTestCase { 15 | func testParagraph() { 16 | let paragraph = Paragraph( 17 | Text("This is a "), 18 | Emphasis(Text("paragraph")), 19 | Text(".")) 20 | 21 | XCTAssertEqual("This is a paragraph.", paragraph.plainText) 22 | } 23 | 24 | func testEmphasis() { 25 | let emphasis = Emphasis(Text("Emphasis")) 26 | XCTAssertEqual("Emphasis", emphasis.plainText) 27 | } 28 | 29 | func testImage() { 30 | let image = Image(source: "test.png", title: "", Text("This "), Text("is "), Text("an "), Text("image.")) 31 | XCTAssertEqual("This is an image.", image.plainText) 32 | } 33 | 34 | func testLink() { 35 | let link = Link(destination: "test.png", 36 | Text("This "), 37 | Text("is "), 38 | Text("a "), 39 | Text("link.")) 40 | XCTAssertEqual("This is a link.", link.plainText) 41 | } 42 | 43 | func testStrong() { 44 | let strong = Strong(Text("Strong")) 45 | XCTAssertEqual("Strong", strong.plainText) 46 | } 47 | 48 | func testCustomInline() { 49 | let customInline = CustomInline("Custom inline") 50 | XCTAssertEqual("Custom inline", customInline.plainText) 51 | } 52 | 53 | func testInlineCode() { 54 | let inlineCode = InlineCode("foo") 55 | XCTAssertEqual("`foo`", inlineCode.plainText) 56 | } 57 | 58 | func testInlineHTML() { 59 | let inlineHTML = InlineHTML("
") 60 | XCTAssertEqual("
", inlineHTML.plainText) 61 | } 62 | 63 | func testLineBreak() { 64 | XCTAssertEqual("\n", LineBreak().plainText) 65 | } 66 | 67 | func testSoftBreak() { 68 | XCTAssertEqual(" ", SoftBreak().plainText) 69 | } 70 | 71 | func testText() { 72 | let text = Text("OK") 73 | XCTAssertEqual("OK", text.plainText) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/RawMarkupTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class RawMarkupTests: XCTestCase { 15 | func testHasSameStructureAs() { 16 | do { // Identity match 17 | let document = RawMarkup.document(parsedRange: nil, []) 18 | XCTAssert(document.hasSameStructure(as: document)) 19 | } 20 | 21 | do { // Empty match 22 | XCTAssert(RawMarkup.document(parsedRange: nil, []).hasSameStructure(as: .document(parsedRange: nil, []))) 23 | } 24 | 25 | do { // Same child count, but different structure 26 | let document1 = RawMarkup.document(parsedRange: nil, [.paragraph(parsedRange: nil, [])]) 27 | let document2 = RawMarkup.document(parsedRange: nil, [.thematicBreak(parsedRange: nil)]) 28 | XCTAssertFalse(document1.hasSameStructure(as: document2)) 29 | } 30 | 31 | do { // Different child count 32 | let document1 = RawMarkup.document(parsedRange: nil, [.paragraph(parsedRange: nil, [])]) 33 | let document2 = RawMarkup.document(parsedRange: nil, [.paragraph(parsedRange: nil, []), .thematicBreak(parsedRange: nil)]) 34 | XCTAssertFalse(document1.hasSameStructure(as: document2)) 35 | } 36 | 37 | do { // Same child count, different structure, nested 38 | let document1 = RawMarkup.document(parsedRange: nil, [ 39 | .paragraph(parsedRange: nil, [ 40 | .text(parsedRange: nil, string: "Hello") 41 | ]), 42 | .paragraph(parsedRange: nil, [ 43 | .text(parsedRange: nil, string: "World") 44 | ]), 45 | ]) 46 | let document2 = RawMarkup.document(parsedRange: nil, [ 47 | .paragraph(parsedRange: nil, [ 48 | .text(parsedRange: nil, string: "Hello"), 49 | ]), 50 | .paragraph(parsedRange: nil, [ 51 | .emphasis(parsedRange: nil, [ 52 | .text(parsedRange: nil, string: "World"), 53 | ]), 54 | ]), 55 | ]) 56 | XCTAssertFalse(document1.hasSameStructure(as: document2)) 57 | } 58 | } 59 | 60 | /// When an element changes a child, unchanged children should use the same `RawMarkup` as before. 61 | func testSharing() { 62 | let originalRoot = Document( 63 | Paragraph(Text("ChangeMe")), 64 | Paragraph(Text("Unchanged"))) 65 | 66 | let firstText = originalRoot.child(through: [ 67 | 0, // Paragraph 68 | 0, // Text 69 | ]) as! Text 70 | 71 | var newText = firstText 72 | newText.string = "Changed" 73 | let newRoot = newText.root 74 | 75 | XCTAssertFalse(originalRoot.child(at: 0)!.raw.markup === newRoot.child(at: 0)!.raw.markup) 76 | XCTAssertTrue(originalRoot.child(at: 1)!.raw.markup === newRoot.child(at: 1)!.raw.markup) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/StableIdentifierTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | /// Test that unique identifiers aren't recreated for the same elements. 15 | final class StableIdentifierTests: XCTestCase { 16 | /// Children are constructed on the fly; test that each time they are gotten, they have the same identifier. 17 | func testStableIdentifiers() { 18 | let paragraph = Paragraph(Emphasis(Text("OK."))) 19 | 20 | // A copy of a node should have the same identifier. 21 | let paragraphCopy = paragraph 22 | XCTAssertTrue(paragraph.isIdentical(to: paragraphCopy)) 23 | 24 | // A child gotten twice should have the same identifier both times. 25 | XCTAssertTrue(paragraph.child(at: 0)!.isIdentical(to: paragraph.child(at: 0)!)) 26 | 27 | // Similarly, for deeper nodes. 28 | XCTAssertTrue(paragraph.child(at: 0)!.child(at: 0)!.isIdentical(to: paragraph.child(at: 0)!.child(at: 0)!)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Block Nodes/CodeBlockTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class CodeBlockTests: XCTestCase { 15 | var testCodeBlock: CodeBlock { 16 | let language = "swift" 17 | let code = "func foo() {}" 18 | let codeBlock = CodeBlock(language: language, code) 19 | XCTAssertEqual(.some(language), codeBlock.language) 20 | XCTAssertEqual(code, codeBlock.code) 21 | return codeBlock 22 | } 23 | 24 | func testCodeBlockLanguage() { 25 | let codeBlock = testCodeBlock 26 | var newCodeBlock = codeBlock 27 | newCodeBlock.language = "c" 28 | 29 | XCTAssertEqual(.some("c"), newCodeBlock.language) 30 | XCTAssertFalse(codeBlock.isIdentical(to: newCodeBlock)) 31 | 32 | var codeBlockWithoutLanguage = newCodeBlock 33 | codeBlockWithoutLanguage.language = nil 34 | XCTAssertNil(codeBlockWithoutLanguage.language) 35 | XCTAssertFalse(codeBlock.isIdentical(to: codeBlockWithoutLanguage)) 36 | } 37 | 38 | func testCodeBlockCode() { 39 | let codeBlock = testCodeBlock 40 | let newCode = "func bar() {}" 41 | var newCodeBlock = codeBlock 42 | newCodeBlock.code = newCode 43 | 44 | XCTAssertEqual(newCode, newCodeBlock.code) 45 | XCTAssertEqual(codeBlock.language, newCodeBlock.language) 46 | XCTAssertFalse(codeBlock.isIdentical(to: newCodeBlock)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Block Nodes/DocumentTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | import Foundation 13 | import Markdown 14 | 15 | final class DocumentTests: XCTestCase { 16 | func testDocumentFromSequence() { 17 | let children = [ 18 | Paragraph(Text("First")), 19 | Paragraph(Text("Second")), 20 | ] 21 | let document = Document(children) 22 | let expectedDump = """ 23 | Document 24 | ├─ Paragraph 25 | │ └─ Text "First" 26 | └─ Paragraph 27 | └─ Text "Second" 28 | """ 29 | XCTAssertEqual(expectedDump, document.debugDescription()) 30 | } 31 | 32 | func testParseURL() { 33 | let readmeURL = URL(fileURLWithPath: #file) 34 | .deletingLastPathComponent() // Block Nodes 35 | .appendingPathComponent("..") // MarkupTests 36 | .appendingPathComponent("..") // Tests 37 | .appendingPathComponent("..") // Project 38 | .appendingPathComponent("README.md") 39 | XCTAssertNoThrow(try Document(parsing: readmeURL)) 40 | XCTAssertThrowsError(try Document(parsing: URL(fileURLWithPath: #file) 41 | .appendingPathComponent("doesntexist"))) 42 | } 43 | 44 | func testParseNewlineCharacters() { 45 | let inputParagraphString = """ 46 | # First\n 47 | ## Second\r 48 | ### Third\r\n 49 | """ 50 | let document = Document(parsing: inputParagraphString, options: [.parseBlockDirectives, .parseSymbolLinks]) 51 | let expectedDump = """ 52 | Document 53 | ├─ Heading level: 1 54 | │ └─ Text "First" 55 | ├─ Heading level: 2 56 | │ └─ Text "Second" 57 | └─ Heading level: 3 58 | └─ Text "Third" 59 | """ 60 | XCTAssertEqual(expectedDump, document.debugDescription()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Block Nodes/HTMLBlockTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class HTMLTestsTests: XCTestCase { 15 | func testHTMLBlockRawHTML() { 16 | let rawHTML = "Hi!" 17 | let html = HTMLBlock(rawHTML) 18 | XCTAssertEqual(rawHTML, html.rawHTML) 19 | 20 | let newRawHTML = "
" 21 | var newHTML = html 22 | newHTML.rawHTML = newRawHTML 23 | XCTAssertEqual(newRawHTML, newHTML.rawHTML) 24 | XCTAssertFalse(html.isIdentical(to: newHTML)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Block Nodes/HeadingTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class HeadingTests: XCTestCase { 15 | func testLevel() { 16 | let heading = Heading(level: 1, [Text("Some text")]) 17 | XCTAssertEqual(1, heading.level) 18 | 19 | var newHeading = heading 20 | newHeading.level = 2 21 | XCTAssertEqual(2, newHeading.level) 22 | XCTAssertFalse(heading.isIdentical(to: newHeading)) 23 | 24 | // If you don't actually change the level, you get the same node back. 25 | var newHeadingUnchanged = heading 26 | newHeadingUnchanged.level = heading.level 27 | XCTAssertTrue(heading.isIdentical(to: newHeadingUnchanged)) 28 | XCTAssertTrue(heading.isIdentical(to: newHeadingUnchanged)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Infrastructure/SourceLocationTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2022 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | class SourceLocationTests: XCTestCase { 15 | func testNonAsciiCharacterColumn() throws { 16 | func assertColumnNumberAssumesUTF8Encoding(text: String) throws { 17 | let document = Document(parsing: text) 18 | let range = try XCTUnwrap(document.range) 19 | XCTAssertEqual(range.upperBound.column - 1, text.utf8.count) 20 | } 21 | 22 | // Emoji 23 | try assertColumnNumberAssumesUTF8Encoding(text: "🇺🇳") 24 | // CJK Character 25 | try assertColumnNumberAssumesUTF8Encoding(text: "叶") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/ImageTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class ImageTests: XCTestCase { 15 | func testImageSource() { 16 | let source = "test.png" 17 | let image = Image(source: source, title: "") 18 | XCTAssertEqual(source, image.source) 19 | XCTAssertEqual(0, image.childCount) 20 | 21 | let newSource = "new.png" 22 | var newImage = image 23 | newImage.source = newSource 24 | XCTAssertEqual(newSource, newImage.source) 25 | XCTAssertFalse(image.isIdentical(to: newImage)) 26 | } 27 | 28 | func testImageTitle() { 29 | let title = "title" 30 | let image = Image(source: "_", title: title) 31 | XCTAssertEqual(title, image.title) 32 | XCTAssertEqual(0, image.childCount) 33 | 34 | do { 35 | let source = "![Alt](test.png \"\(title)\")" 36 | let document = Document(parsing: source) 37 | let image = document.child(through:[ 38 | (0, Paragraph.self), 39 | (0, Image.self), 40 | ]) as! Image 41 | XCTAssertEqual(title, image.title) 42 | } 43 | } 44 | 45 | func testLinkFromSequence() { 46 | let children = [Text("Hello, world!")] 47 | let image = Image(source: "test.png", title: "title", children) 48 | let expectedDump = """ 49 | Image source: "test.png" title: "title" 50 | └─ Text "Hello, world!" 51 | """ 52 | XCTAssertEqual(expectedDump, image.debugDescription()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/InlineAttributesTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class InlineAttributesTests: XCTestCase { 15 | func testInlineAttributesAttributes() { 16 | let attributes = "rainbow: 'extreme'" 17 | let inlineAttributes = InlineAttributes(attributes: attributes) 18 | XCTAssertEqual(attributes, inlineAttributes.attributes) 19 | XCTAssertEqual(0, inlineAttributes.childCount) 20 | 21 | let newAttributes = "rainbow: 'medium'" 22 | var newInlineAttributes = inlineAttributes 23 | newInlineAttributes.attributes = newAttributes 24 | XCTAssertEqual(newAttributes, newInlineAttributes.attributes) 25 | XCTAssertFalse(inlineAttributes.isIdentical(to: newInlineAttributes)) 26 | } 27 | 28 | func testInlineAttributesFromSequence() { 29 | let children = [Text("Hello, world!")] 30 | let inlineAttributes = InlineAttributes(attributes: "rainbow: 'extreme'", children) 31 | let expectedDump = """ 32 | InlineAttributes attributes: `rainbow: 'extreme'` 33 | └─ Text "Hello, world!" 34 | """ 35 | XCTAssertEqual(expectedDump, inlineAttributes.debugDescription()) 36 | } 37 | 38 | func testParseInlineAttributes() { 39 | let source = "^[Hello, world!](rainbow: 'extreme')" 40 | let document = Document(parsing: source) 41 | let expectedDump = """ 42 | Document @1:1-1:37 43 | └─ Paragraph @1:1-1:37 44 | └─ InlineAttributes @1:1-1:37 attributes: `rainbow: 'extreme'` 45 | └─ Text @1:3-1:16 "Hello, world!" 46 | """ 47 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/InlineCodeTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class InlineCodeTests: XCTestCase { 15 | func testInlineCodeString() { 16 | let text = "foo()" 17 | let text2 = "bar()" 18 | let inlineCode = InlineCode(text) 19 | 20 | XCTAssertEqual(text, inlineCode.code) 21 | 22 | var inlineCodeWithText2 = inlineCode 23 | inlineCodeWithText2.code = text2 24 | 25 | XCTAssertEqual(text2, inlineCodeWithText2.code) 26 | XCTAssertFalse(inlineCode.isIdentical(to: inlineCodeWithText2)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/InlineHTMLTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class InlineHTMLTests: XCTestCase { 15 | func testInlineHTMLRawHTML() { 16 | let rawHTML = "bold" 17 | let rawHTML2 = "

para

" 18 | 19 | let inlineHTML = InlineHTML(rawHTML) 20 | XCTAssertEqual(rawHTML, inlineHTML.rawHTML) 21 | 22 | var newInlineHTML = inlineHTML 23 | newInlineHTML.rawHTML = rawHTML2 24 | XCTAssertEqual(rawHTML2, newInlineHTML.rawHTML) 25 | XCTAssertFalse(inlineHTML.isIdentical(to: newInlineHTML)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/LineBreakTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class LineBreakTests: XCTestCase { 15 | /// Tests that creation doesn't crash. 16 | func testLineBreak() { 17 | _ = LineBreak() 18 | } 19 | 20 | /// Test that line breaks are parsed correctly. 21 | /// (Lots of folks have trailing whitespace trimming on). 22 | func testParseLineBreak() { 23 | let source = "Paragraph. \nStill the same paragraph." 24 | let document = Document(parsing: source) 25 | let paragraph = document.child(at: 0) as! Paragraph 26 | XCTAssertTrue(Array(paragraph.children)[1] is LineBreak) 27 | } 28 | 29 | /// Test that hard line breaks work with spaces (two or more). 30 | func testSpaceHardLineBreak() { 31 | let source = """ 32 | Paragraph.\(" ") 33 | Still the same paragraph. 34 | """ 35 | let document = Document(parsing: source) 36 | let paragraph = document.child(at: 0) as! Paragraph 37 | XCTAssertTrue(Array(paragraph.children)[1] is LineBreak) 38 | } 39 | 40 | /// Test that hard line breaks work with a slash. 41 | func testSlashHardLineBreak() { 42 | let source = #""" 43 | Paragraph.\ 44 | Still the same paragraph. 45 | """# 46 | let document = Document(parsing: source) 47 | let paragraph = document.child(at: 0) as! Paragraph 48 | XCTAssertTrue(Array(paragraph.children)[1] is LineBreak) 49 | } 50 | 51 | /// Sanity test that a multiline text without hard breaks doesn't return line breaks. 52 | func testLineBreakWithout() { 53 | let source = """ 54 | Paragraph. 55 | Same line text. 56 | """ 57 | let document = Document(parsing: source) 58 | 59 | let paragraph = document.child(at: 0) as! Paragraph 60 | XCTAssertFalse(Array(paragraph.children)[1] is LineBreak) 61 | XCTAssertEqual(Array(paragraph.children)[1].withoutSoftBreaks?.childCount, nil) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/LinkTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021-2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class LinkTests: XCTestCase { 15 | func testLinkDestination() { 16 | let destination = "destination" 17 | let link = Link(destination: destination) 18 | XCTAssertEqual(destination, link.destination) 19 | XCTAssertEqual(0, link.childCount) 20 | 21 | let newDestination = "newdestination" 22 | var newLink = link 23 | newLink.destination = newDestination 24 | XCTAssertEqual(newDestination, newLink.destination) 25 | XCTAssertFalse(link.isIdentical(to: newLink)) 26 | } 27 | 28 | func testLinkFromSequence() { 29 | let children = [Text("Hello, world!")] 30 | let link = Link(destination: "destination", children) 31 | let expectedDump = """ 32 | Link destination: "destination" 33 | └─ Text "Hello, world!" 34 | """ 35 | XCTAssertEqual(expectedDump, link.debugDescription()) 36 | } 37 | 38 | func testAutoLink() { 39 | let children = [Text("example.com")] 40 | var link = Link(destination: "example.com", children) 41 | let expectedDump = """ 42 | Link destination: "example.com" 43 | └─ Text "example.com" 44 | """ 45 | XCTAssertEqual(expectedDump, link.debugDescription()) 46 | XCTAssertTrue(link.isAutolink) 47 | 48 | link.destination = "test.example.com" 49 | XCTAssertFalse(link.isAutolink) 50 | } 51 | 52 | func testTitleLink() throws { 53 | let markdown = #""" 54 | [Example](example.com "The example title") 55 | [Example2](example2.com) 56 | [Example3]() 57 | """# 58 | 59 | let document = Document(parsing: markdown) 60 | XCTAssertEqual(document.childCount, 1) 61 | let paragraph = try XCTUnwrap(document.child(at: 0) as? Paragraph) 62 | XCTAssertEqual(paragraph.childCount, 5) 63 | 64 | XCTAssertTrue(paragraph.child(at: 1) is SoftBreak) 65 | XCTAssertTrue(paragraph.child(at: 3) is SoftBreak) 66 | let linkWithTitle = try XCTUnwrap(paragraph.child(at: 0) as? Link) 67 | let linkWithoutTitle = try XCTUnwrap(paragraph.child(at: 2) as? Link) 68 | let linkWithoutDestination = try XCTUnwrap(paragraph.child(at: 4) as? Link) 69 | 70 | XCTAssertEqual(try XCTUnwrap(linkWithTitle.child(at: 0) as? Text).string, "Example") 71 | XCTAssertEqual(linkWithTitle.destination, "example.com") 72 | XCTAssertEqual(linkWithTitle.title, "The example title") 73 | 74 | XCTAssertEqual(try XCTUnwrap(linkWithoutTitle.child(at: 0) as? Text).string, "Example2") 75 | XCTAssertEqual(linkWithoutTitle.destination, "example2.com") 76 | XCTAssertEqual(linkWithoutTitle.title, nil) 77 | 78 | XCTAssertEqual(try XCTUnwrap(linkWithoutDestination.child(at: 0) as? Text).string, "Example3") 79 | XCTAssertEqual(linkWithoutDestination.destination, nil) 80 | XCTAssertEqual(linkWithoutDestination.title, nil) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/SoftBreakTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class SoftBreakTests: XCTestCase { 15 | /// Tests that creation doesn't crash. 16 | func testSoftBreak() { 17 | _ = SoftBreak() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/SymbolLinkTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class SymbolLinkTests: XCTestCase { 15 | func testSymbolLinkDestination() { 16 | let destination = "destination" 17 | let symbolLink = SymbolLink(destination: destination) 18 | XCTAssertEqual(destination, symbolLink.destination) 19 | XCTAssertEqual(0, symbolLink.childCount) 20 | 21 | let newDestination = "newdestination" 22 | var newSymbolLink = symbolLink 23 | newSymbolLink.destination = newDestination 24 | XCTAssertEqual(newDestination, newSymbolLink.destination) 25 | XCTAssertFalse(symbolLink.isIdentical(to: newSymbolLink)) 26 | } 27 | 28 | func testDetectionFromInlineCode() { 29 | let source = "``foo()``" 30 | do { // option on 31 | let document = Document(parsing: source, options: .parseSymbolLinks) 32 | let expectedDump = """ 33 | Document @1:1-1:10 34 | └─ Paragraph @1:1-1:10 35 | └─ SymbolLink @1:1-1:10 destination: foo() 36 | """ 37 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 38 | } 39 | do { // option off 40 | let document = Document(parsing: source) 41 | let expectedDump = """ 42 | Document @1:1-1:10 43 | └─ Paragraph @1:1-1:10 44 | └─ InlineCode @1:1-1:10 `foo()` 45 | """ 46 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 47 | } 48 | } 49 | 50 | func testMultilineSymbolLink() { 51 | let source = """ 52 | Test of a ``multi 53 | line symbolink`` 54 | """ 55 | let document = Document(parsing: source, options: .parseSymbolLinks) 56 | let expectedDump = """ 57 | Document @1:1-2:17 58 | └─ Paragraph @1:1-2:17 59 | ├─ Text @1:1-1:11 "Test of a " 60 | └─ SymbolLink @1:11-2:17 destination: multi line symbolink 61 | """ 62 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/TextTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class TextTests: XCTestCase { 15 | func testWithText() { 16 | let string = "OK" 17 | let text = Text(string) 18 | XCTAssertEqual(string, text.string) 19 | 20 | let string2 = "Changed" 21 | var newText = text 22 | newText.string = string2 23 | XCTAssertEqual(string2, newText.string) 24 | XCTAssertFalse(text.isIdentical(to: newText)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Parsing/BacktickTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | class BacktickTests: XCTestCase { 15 | func testNormalBackticks() { 16 | let string = "Hello `test` String" 17 | let document = Document(parsing: string) 18 | let expectedDump = """ 19 | Document @1:1-1:20 20 | └─ Paragraph @1:1-1:20 21 | ├─ Text @1:1-1:7 "Hello " 22 | ├─ InlineCode @1:7-1:13 `test` 23 | └─ Text @1:13-1:20 " String" 24 | """ 25 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 26 | } 27 | 28 | func testOpenBacktick() { 29 | let single = "`" 30 | let document = Document(parsing: single) 31 | let expectedDump = """ 32 | Document @1:1-1:2 33 | └─ Paragraph @1:1-1:2 34 | └─ Text @1:1-1:2 "`" 35 | """ 36 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 37 | } 38 | 39 | func testOpenBackticks() { 40 | let double = "``" 41 | let document = Document(parsing: double) 42 | let expectedDump = """ 43 | Document @1:1-1:3 44 | └─ Paragraph @1:1-1:3 45 | └─ Text @1:1-1:3 "``" 46 | """ 47 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Parsing/CommonMarkConverterTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | class CommonMarkConverterTests: XCTestCase { 15 | /// Verify that a link that spans multiple lines does not crash cmark and also returns a valid range 16 | func testMulitlineLinks() { 17 | let text = """ 18 | This is a link to an article on a different domain [link 19 | to an article](https://www.host.com/article). 20 | """ 21 | 22 | let expectedDump = """ 23 | Document @1:1-2:46 24 | └─ Paragraph @1:1-2:46 25 | ├─ Text @1:1-1:52 "This is a link to an article on a different domain " 26 | ├─ Link @1:52-2:45 destination: "https://www.host.com/article" 27 | │ ├─ Text @1:53-1:57 "link" 28 | │ ├─ SoftBreak 29 | │ └─ Text @2:1-2:14 "to an article" 30 | └─ Text @2:45-2:46 "." 31 | """ 32 | 33 | let document = Document(parsing: text, source: nil, options: [.parseBlockDirectives, .parseSymbolLinks]) 34 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Performance/EditPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | import Markdown 13 | 14 | final class EditPerformanceTests: XCTestCase { 15 | #if os(Windows) 16 | #if DEBUG 17 | static let maxDepth = 625 18 | #else 19 | static let maxDepth = 1250 20 | #endif 21 | #else 22 | static let maxDepth = 5000 23 | #endif 24 | /// Test the performance of changing a leaf in an unrealistically deep markup tree. 25 | func testChangeTextInDeepTree() { 26 | func buildDeepListItem(depth: Int) -> ListItem { 27 | guard depth < EditPerformanceTests.maxDepth else { 28 | return ListItem(Paragraph(Text("A"), Text("B"), Text("C"))) 29 | } 30 | return ListItem(buildDeepList(depth: depth + 1)) 31 | } 32 | 33 | func buildDeepList(depth: Int = 0) -> UnorderedList { 34 | guard depth < EditPerformanceTests.maxDepth else { 35 | return UnorderedList(buildDeepListItem(depth: depth)) 36 | } 37 | return UnorderedList(buildDeepListItem(depth: depth + 1)) 38 | } 39 | 40 | let list = buildDeepList() 41 | var deepChild: Markup = list 42 | while let child = deepChild.child(at: 0) { 43 | deepChild = child 44 | } 45 | 46 | var deepText = (deepChild as! Text) 47 | measure { 48 | deepText.string = "Z" 49 | } 50 | } 51 | 52 | /// Test the performance of change an element among unrealistically many siblings. 53 | func testChangeTextInWideParagraph() { 54 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 55 | var firstText = paragraph.child(at: 0) as! Text 56 | measure { 57 | firstText.string = "OK" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Performance/MarkupChildrenPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | import Markdown 13 | 14 | final class MarkupChildrenPerformanceTests: XCTestCase { 15 | /// Iteration over the children should be fast: no heap allocation should be necessary. 16 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 17 | func testIterateChildrenForward() { 18 | measure { 19 | for child in paragraph.children { 20 | _ = child 21 | } 22 | } 23 | } 24 | 25 | /// Iteration over the children in reverse should be fast: no heap allocation should be necessary. 26 | func testIterateChildrenReversed() { 27 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 28 | measure { 29 | for child in paragraph.children.reversed() { 30 | _ = child 31 | } 32 | } 33 | } 34 | 35 | func testDropFirst() { 36 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 37 | measure { 38 | for child in paragraph.children.dropFirst(5000) { 39 | _ = child 40 | } 41 | } 42 | } 43 | 44 | func testSuffix() { 45 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 46 | measure { 47 | for child in paragraph.children.suffix(5000) { 48 | _ = child 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Utility/AssertElementDidntChange.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | func assertElementDidntChange(_ element: Markup, assertedStructure expected: Markup, expectedId: MarkupIdentifier) { 15 | XCTAssertTrue(element.hasSameStructure(as: expected)) 16 | XCTAssertEqual(element._data.id, expectedId) 17 | } 18 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Visitors/Everything.md: -------------------------------------------------------------------------------- 1 | # Header 2 | 3 | *Emphasized* **strong** `inline code` [link](foo) ![image](foo). 4 | 5 | - this 6 | - is 7 | - a 8 | - list 9 | 10 | 1. eggs 11 | 1. milk 12 | 13 | > BlockQuote 14 | 15 | 2. flour 16 | 2. sugar 17 | 18 | - [x] Combine flour and baking soda. 19 | - [ ] Combine sugar and eggs. 20 | 21 | ```swift 22 | func foo() { 23 | let x = 1 24 | } 25 | ``` 26 | 27 | // Is this real code? Or just fantasy? 28 | 29 | This is an . 30 | 31 | --- 32 | 33 | 34 | An HTML Block. 35 | 36 | 37 | This is some

inline html

. 38 | 39 | line 40 | break 41 | 42 | soft 43 | break 44 | 45 | 46 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Visitors/MarkupVisitorTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | import Markdown 13 | 14 | class MarkupVisitorTests: XCTestCase { 15 | struct IntegerConverter: MarkupVisitor { 16 | var value: Int 17 | 18 | mutating func defaultVisit(_: Markdown.Markup) -> Int { 19 | defer { value += 1 } 20 | return value 21 | } 22 | } 23 | 24 | 25 | // A compile time check for PAT support 26 | func testMarkupVisitorPrimaryAssociatedType() { 27 | var visitor: some MarkupVisitor = IntegerConverter(value: 1) 28 | let markup = Text("") 29 | XCTAssertEqual(visitor.visit(markup), 1) 30 | XCTAssertEqual(visitor.visit(markup), 2) 31 | var mappedVisitor: some MarkupVisitor = visitor.map { $0 * $0 } 32 | XCTAssertEqual(mappedVisitor.visit(markup), 9) 33 | XCTAssertEqual(mappedVisitor.visit(markup), 16) 34 | XCTAssertEqual(visitor.visit(markup), 3) 35 | } 36 | } 37 | 38 | struct _MappVisitor: MarkupVisitor { 39 | typealias Result = B 40 | init(visitor: A, _ transform: @escaping (A.Result) -> B) { 41 | self.visitor = visitor 42 | self.transform = transform 43 | } 44 | private var visitor: A 45 | private let transform: (A.Result) -> B 46 | 47 | mutating func defaultVisit(_ markup: Markdown.Markup) -> B { 48 | transform(visitor.defaultVisit(markup)) 49 | } 50 | } 51 | 52 | extension MarkupVisitor { 53 | func map(_ transform: @escaping (Self.Result) -> U) -> some MarkupVisitor { 54 | _MappVisitor(visitor: self, transform) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tools/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | /* 3 | This source file is part of the Swift.org open source project 4 | 5 | Copyright (c) 2023 Apple Inc. and the Swift project authors 6 | Licensed under Apache License v2.0 with Runtime Library Exception 7 | 8 | See https://swift.org/LICENSE.txt for license information 9 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | */ 11 | 12 | import PackageDescription 13 | 14 | let package = Package( 15 | name: "Tools", 16 | products: [ 17 | .executable(name: "markdown-tool", targets: ["markdown-tool"]), 18 | ], 19 | dependencies: [ 20 | .package(name: "swift-markdown", path: "../."), 21 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.2"), 22 | ], 23 | targets: [ 24 | .executableTarget( 25 | name: "markdown-tool", 26 | dependencies: [ 27 | .product(name: "Markdown", package: "swift-markdown"), 28 | .product(name: "ArgumentParser", package: "swift-argument-parser") 29 | ], 30 | path: "markdown-tool" 31 | ), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /Tools/markdown-tool/Commands/DumpTreeCommand.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import ArgumentParser 12 | import Markdown 13 | 14 | extension MarkdownCommand { 15 | /// A command to dump the parsed input's debug tree representation. 16 | struct DumpTree: ParsableCommand { 17 | static let configuration = CommandConfiguration(commandName: "dump-tree", abstract: "Dump the parsed standard input as a tree representation for debugging") 18 | 19 | @Argument(help: "Optional input file path of a Markdown file to format; default: standard input") 20 | var inputFilePath: String? 21 | 22 | @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "Print source locations for each element where applicable") 23 | var sourceLocations: Bool = false 24 | 25 | @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "Print internal unique identifiers for each element") 26 | var uniqueIdentifiers: Bool = false 27 | 28 | @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "Parse block directives") 29 | var parseBlockDirectives: Bool = false 30 | 31 | @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "Parse a minimal set of Doxygen commands (requires --parse-block-directives)") 32 | var experimentalParseDoxygenCommands: Bool = false 33 | 34 | func run() throws { 35 | var parseOptions = ParseOptions() 36 | if parseBlockDirectives { 37 | parseOptions.insert(.parseBlockDirectives) 38 | } 39 | if experimentalParseDoxygenCommands { 40 | parseOptions.insert(.parseMinimalDoxygen) 41 | } 42 | 43 | let document: Document 44 | if let inputFilePath = inputFilePath { 45 | (_, document) = try MarkdownCommand.parseFile(at: inputFilePath, options: parseOptions) 46 | } else { 47 | (_, document) = try MarkdownCommand.parseStandardInput(options: parseOptions) 48 | } 49 | var dumpOptions = MarkupDumpOptions() 50 | if sourceLocations { 51 | dumpOptions.insert(.printSourceLocations) 52 | } 53 | if uniqueIdentifiers { 54 | dumpOptions.insert(.printUniqueIdentifiers) 55 | } 56 | print(document.debugDescription(options: dumpOptions)) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tools/markdown-tool/Commands/PrintHTMLCommand.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import ArgumentParser 12 | import Markdown 13 | 14 | extension MarkdownCommand { 15 | /// A command to render HTML for given Markdown content. 16 | struct PrintHTML: ParsableCommand { 17 | static var configuration = CommandConfiguration(commandName: "print-html", abstract: "Convert Markdown content into HTML") 18 | 19 | @Argument( 20 | help: "Markdown file to print (default: standard input)", 21 | completion: .file() 22 | ) 23 | var inputFile: String? 24 | 25 | @Flag( 26 | inversion: .prefixedNo, 27 | exclusivity: .chooseLast, 28 | help: "Parse block quotes as asides if they have an aside marker" 29 | ) 30 | var parseAsides: Bool = false 31 | 32 | @Flag( 33 | inversion: .prefixedNo, 34 | exclusivity: .chooseLast, 35 | help: "Parse inline attributes as JSON, and use the 'class' property as a 'class' attribute" 36 | ) 37 | var parseInlineAttributeClass: Bool = false 38 | 39 | func run() throws { 40 | let document: Document 41 | if let inputFilePath = inputFile { 42 | (_, document) = try MarkdownCommand.parseFile(at: inputFilePath, options: []) 43 | } else { 44 | (_, document) = try MarkdownCommand.parseStandardInput(options: []) 45 | } 46 | 47 | var formatterOptions = HTMLFormatterOptions() 48 | if parseAsides { 49 | formatterOptions.insert(.parseAsides) 50 | } 51 | if parseInlineAttributeClass { 52 | formatterOptions.insert(.parseInlineAttributeClass) 53 | } 54 | 55 | print(HTMLFormatter.format(document, options: formatterOptions)) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tools/markdown-tool/MarkdownCommand.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import ArgumentParser 12 | import Foundation 13 | import Markdown 14 | 15 | @main 16 | struct MarkdownCommand: ParsableCommand { 17 | static let configuration = CommandConfiguration(commandName: "markdown", shouldDisplay: false, subcommands: [ 18 | DumpTree.self, 19 | Format.self, 20 | PrintHTML.self, 21 | ]) 22 | 23 | static func parseFile(at path: String, options: ParseOptions) throws -> (source: String, parsed: Document) { 24 | let data = try Data(contentsOf: URL(fileURLWithPath: path)) 25 | let inputString = String(decoding: data, as: UTF8.self) 26 | return (inputString, Document(parsing: inputString, options: options)) 27 | } 28 | 29 | static func parseStandardInput(options: ParseOptions) throws -> (source: String, parsed: Document) { 30 | let stdinData: Data 31 | if #available(macOS 10.15.4, *) { 32 | stdinData = try FileHandle.standardInput.readToEnd() ?? Data() 33 | } else { 34 | stdinData = FileHandle.standardInput.readDataToEndOfFile() 35 | } 36 | let stdinString = String(decoding: stdinData, as: UTF8.self) 37 | return (stdinString, Document(parsing: stdinString, options: options)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This source file is part of the Swift.org open source project 4 | # 5 | # Copyright (c) 2021 Apple Inc. and the Swift project authors 6 | # Licensed under Apache License v2.0 with Runtime Library Exception 7 | # 8 | # See https://swift.org/LICENSE.txt for license information 9 | # See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | # 11 | 12 | set -eu 13 | 14 | # A `realpath` alternative using the default C implementation. 15 | filepath() { 16 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" 17 | } 18 | 19 | # First get the absolute path to this file so we can get the absolute file path to the SwiftMarkdown root source dir. 20 | SWIFT_MARKDOWN_ROOT="$(dirname $(dirname $(filepath $0)))" 21 | 22 | # Build SwiftMarkdown. 23 | swift test --parallel --package-path "$SWIFT_MARKDOWN_ROOT" 24 | 25 | # Run source code checks for the codebase. 26 | LC_ALL=C "$SWIFT_MARKDOWN_ROOT"/bin/check-source 27 | 28 | # Test utility scripts validity. 29 | printf "=> Validating scripts in bin subdirectory… " 30 | 31 | printf "\033[0;32mokay.\033[0m\n" 32 | 33 | -------------------------------------------------------------------------------- /bin/update-gh-pages-documentation-site: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This source file is part of the Swift.org open source project 4 | # 5 | # Copyright (c) 2022 Apple Inc. and the Swift project authors 6 | # Licensed under Apache License v2.0 with Runtime Library Exception 7 | # 8 | # See https://swift.org/LICENSE.txt for license information 9 | # See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | # 11 | # Updates the GitHub Pages documentation site thats published from the 'docs' 12 | # subdirectory in the 'gh-pages' branch of this repository. 13 | # 14 | # This script should be run by someone with commit access to the 'gh-pages' branch 15 | # at a regular frequency so that the documentation content on the GitHub Pages site 16 | # is up-to-date with the content in this repo. 17 | # 18 | 19 | set -eu 20 | 21 | # A `realpath` alternative using the default C implementation. 22 | filepath() { 23 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" 24 | } 25 | 26 | SWIFT_MARKDOWN_ROOT="$(dirname $(dirname $(filepath $0)))" 27 | 28 | # Set current directory to the repository root 29 | cd "$SWIFT_MARKDOWN_ROOT" 30 | 31 | # Use git worktree to checkout the gh-pages branch of this repository in a gh-pages sub-directory 32 | git fetch 33 | git worktree add --checkout gh-pages origin/gh-pages 34 | 35 | # Pretty print DocC JSON output so that it can be consistently diffed between commits 36 | export DOCC_JSON_PRETTYPRINT="YES" 37 | 38 | # Generate documentation for the 'Markdown' target and output it 39 | # to the /docs subdirectory in the gh-pages worktree directory. 40 | swift package \ 41 | --allow-writing-to-directory "$SWIFT_MARKDOWN_ROOT/gh-pages/docs" \ 42 | generate-documentation \ 43 | --target Markdown \ 44 | --disable-indexing \ 45 | --transform-for-static-hosting \ 46 | --hosting-base-path swift-markdown \ 47 | --output-path "$SWIFT_MARKDOWN_ROOT/gh-pages/docs" 48 | 49 | # Save the current commit we've just built documentation from in a variable 50 | CURRENT_COMMIT_HASH=`git rev-parse --short HEAD` 51 | 52 | # Commit and push our changes to the gh-pages branch 53 | cd gh-pages 54 | git add docs 55 | 56 | if [ -n "$(git status --porcelain)" ]; then 57 | echo "Documentation changes found. Committing the changes to the 'gh-pages' branch and pushing to origin." 58 | git commit -m "Update GitHub Pages documentation site to $CURRENT_COMMIT_HASH" 59 | git push origin HEAD:gh-pages 60 | else 61 | # No changes found, nothing to commit. 62 | echo "No documentation changes found." 63 | fi 64 | 65 | # Delete the git worktree we created 66 | cd .. 67 | git worktree remove gh-pages 68 | -------------------------------------------------------------------------------- /cmake/modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | set(SWIFT_MARKDOWN_EXPORTS_FILE 11 | ${CMAKE_CURRENT_BINARY_DIR}/SwiftMarkdownExports.cmake) 12 | 13 | configure_file(SwiftMarkdownConfig.cmake.in 14 | ${CMAKE_CURRENT_BINARY_DIR}/SwiftMarkdownConfig.cmake) 15 | 16 | get_property(SWIFT_MARKDOWN_EXPORTS GLOBAL PROPERTY SWIFT_MARKDOWN_EXPORTS) 17 | export(TARGETS ${SWIFT_MARKDOWN_EXPORTS} 18 | NAMESPACE SwiftMarkdown:: 19 | FILE ${SWIFT_MARKDOWN_EXPORTS_FILE} 20 | EXPORT_LINK_INTERFACE_LIBRARIES) 21 | -------------------------------------------------------------------------------- /cmake/modules/SwiftMarkdownConfig.cmake.in: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | if(NOT TARGET SwiftMarkdown) 11 | include("@SWIFT_MARKDOWN_EXPORTS_FILE@") 12 | endif() 13 | --------------------------------------------------------------------------------