├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── swiftlint.yml ├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .travis.yml ├── CHANGELOG.md ├── Down-Example ├── Down-Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ ├── Down-Example.xcscheme │ │ └── macOS Demo.xcscheme ├── Down-Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── Shared │ └── README-sample.md └── macOS Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── macOS Demo.entitlements ├── Down.podspec ├── Down.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── Down.xcscheme ├── Images └── ohhai.gif ├── LICENSE ├── LinuxMain.swift ├── Package.swift ├── README.md ├── Sources ├── Down │ ├── AST │ │ ├── Nodes │ │ │ ├── BaseNode.swift │ │ │ ├── BlockQuote.swift │ │ │ ├── ChildSequence.swift │ │ │ ├── Code.swift │ │ │ ├── CodeBlock.swift │ │ │ ├── CustomBlock.swift │ │ │ ├── CustomInline.swift │ │ │ ├── Document.swift │ │ │ ├── Emphasis.swift │ │ │ ├── Heading.swift │ │ │ ├── HtmlBlock.swift │ │ │ ├── HtmlInline.swift │ │ │ ├── Image.swift │ │ │ ├── Item.swift │ │ │ ├── LineBreak.swift │ │ │ ├── Link.swift │ │ │ ├── List.swift │ │ │ ├── Node.swift │ │ │ ├── Paragraph.swift │ │ │ ├── SoftBreak.swift │ │ │ ├── Strong.swift │ │ │ ├── Text.swift │ │ │ └── ThematicBreak.swift │ │ ├── Styling │ │ │ ├── Attribute Collections │ │ │ │ ├── ColorCollection.swift │ │ │ │ ├── FontCollection.swift │ │ │ │ └── ParagraphStyleCollection.swift │ │ │ ├── Custom Attributes │ │ │ │ ├── BlockBackgroundColorAttribute.swift │ │ │ │ ├── QuoteStripeAttribute.swift │ │ │ │ └── ThematicBreakAttribute.swift │ │ │ ├── Helpers │ │ │ │ ├── Extensions │ │ │ │ │ ├── CGPoint+Translate.swift │ │ │ │ │ ├── CGRect+Helpers.swift │ │ │ │ │ ├── NSAttributedString+Helpers.swift │ │ │ │ │ ├── NSMutableAttributedString+Attributes.swift │ │ │ │ │ └── UIFont+Traits.swift │ │ │ │ └── ListItemParagraphStyler.swift │ │ │ ├── Layout Managers │ │ │ │ ├── DownDebugLayoutManager.swift │ │ │ │ └── DownLayoutManager.swift │ │ │ ├── Options │ │ │ │ ├── CodeBlockOptions.swift │ │ │ │ ├── ListItemOptions.swift │ │ │ │ ├── QuoteStripeOptions.swift │ │ │ │ └── ThematicBreakOptions.swift │ │ │ ├── Stylers │ │ │ │ ├── DownStyler.swift │ │ │ │ ├── DownStylerConfiguration.swift │ │ │ │ └── Styler.swift │ │ │ └── Text Views │ │ │ │ ├── DownDebugTextView.swift │ │ │ │ └── DownTextView.swift │ │ └── Visitors │ │ │ ├── AttributedStringVisitor.swift │ │ │ ├── DebugVisitor.swift │ │ │ ├── ListItemPrefixGenerator.swift │ │ │ └── Visitor.swift │ ├── Down.h │ ├── Down.swift │ ├── Enums & Options │ │ ├── DownErrors.swift │ │ └── DownOptions.swift │ ├── Extensions │ │ ├── NSAttributedString+HTML.swift │ │ └── String+ToHTML.swift │ ├── Renderers │ │ ├── DownASTRenderable.swift │ │ ├── DownAttributedStringRenderable.swift │ │ ├── DownCommonMarkRenderable.swift │ │ ├── DownGroffRenderable.swift │ │ ├── DownHTMLRenderable.swift │ │ ├── DownLaTeXRenderable.swift │ │ ├── DownRenderable.swift │ │ └── DownXMLRenderable.swift │ ├── Resources │ │ ├── DownView (macOS).bundle │ │ │ ├── css │ │ │ │ └── down.min.css │ │ │ ├── index.html │ │ │ └── js │ │ │ │ ├── down.js │ │ │ │ └── highlight.min.js │ │ └── DownView.bundle │ │ │ ├── css │ │ │ └── down.min.css │ │ │ ├── index.html │ │ │ └── js │ │ │ ├── down.js │ │ │ └── highlight.min.js │ └── Views │ │ ├── BundleHelper.swift │ │ └── DownView.swift └── cmark │ ├── COPYING │ ├── blocks.c │ ├── buffer.c │ ├── buffer.h │ ├── case_fold_switch.inc │ ├── chunk.h │ ├── cmark.c │ ├── cmark.h │ ├── cmark_ctype.c │ ├── cmark_ctype.h │ ├── cmark_export.h │ ├── cmark_version.h │ ├── commonmark.c │ ├── config.h │ ├── entities.inc │ ├── houdini.h │ ├── houdini_href_e.c │ ├── houdini_html_e.c │ ├── houdini_html_u.c │ ├── html.c │ ├── include │ └── module.modulemap │ ├── inlines.c │ ├── inlines.h │ ├── iterator.c │ ├── iterator.h │ ├── latex.c │ ├── man.c │ ├── node.c │ ├── node.h │ ├── parser.h │ ├── references.c │ ├── references.h │ ├── render.c │ ├── render.h │ ├── scanners.c │ ├── scanners.h │ ├── utf8.c │ ├── utf8.h │ └── xml.c ├── Supporting Files ├── Configurations │ ├── Deployment-Targets.xcconfig │ ├── Universal-Framework-Target.xcconfig │ └── Universal-Target-Base.xcconfig ├── Down-Info.plist └── DownTests-Info.plist ├── Tests └── DownTests │ ├── AST │ ├── ListItemPrefixGeneratorTests.swift │ ├── NodeTests.swift │ ├── VisitorTests.swift │ └── __Snapshots__ │ │ └── VisitorTests │ │ ├── testAttributedStringVisitor.1.txt │ │ ├── testBlockQuote.1.txt │ │ ├── testBlockQuote.2.txt │ │ ├── testCodeBlock.1.txt │ │ ├── testCodeBlock.2.txt │ │ ├── testHeading.1.txt │ │ ├── testHeading.2.txt │ │ ├── testHtmlBlock.1.txt │ │ ├── testHtmlBlock.2.txt │ │ ├── testInline.1.txt │ │ ├── testInline.2.txt │ │ ├── testLineBreak.1.txt │ │ ├── testLineBreak.2.txt │ │ ├── testLink.1.txt │ │ ├── testLink.2.txt │ │ ├── testList.1.txt │ │ ├── testList.2.txt │ │ ├── testParagraph.1.txt │ │ ├── testParagraph.2.txt │ │ ├── testSimpleMarkdown.1.txt │ │ ├── testSoftBreak.1.txt │ │ ├── testSoftBreak.2.txt │ │ ├── testThematicBreak.1.txt │ │ └── testThematicBreak.2.txt │ ├── BindingTests.swift │ ├── DownViewTests.swift │ ├── Fixtures │ └── TestDownView.bundle │ │ ├── css │ │ └── down.min.css │ │ ├── index.html │ │ └── js │ │ ├── down.js │ │ └── highlight.min.js │ ├── NSAttributedStringTests.swift │ ├── StringTests.swift │ ├── Styler │ ├── BlockQuoteStyleTests.swift │ ├── CodeBlockStyleTests.swift │ ├── DownDebugLayoutManagerTests.swift │ ├── HeadingStyleTests.swift │ ├── Helpers │ │ ├── CGPointTranslateTests.swift │ │ ├── CGRectHelpersTests.swift │ │ ├── NSAttributedString+HelpersTests.swift │ │ └── NSMutableAttributedString+AttributesTests.swift │ ├── InlineStyleTests.swift │ ├── LinkStyleTests.swift │ ├── ListItemStyleTests.swift │ ├── StylerTestSuite.swift │ ├── ThematicBreakSyleTests.swift │ └── __Snapshots__ │ │ ├── BlockQuoteStyleTests │ │ ├── testThat_NestedQuotes_Have_TheirOwnStripes.1.png │ │ ├── testThat_QuoteAlignment_Obeys_TextContainerOffset.1.png │ │ ├── testThat_QuoteContent_Aligns.1.png │ │ ├── testThat_QuoteContent_Preserves_BlockElements.1.png │ │ ├── testThat_QuoteContent_Preserves_InlineElements.1.png │ │ ├── testThat_QuoteContent_Preserves_ListFormatting.1.png │ │ ├── testThat_QuoteContent_Preserves_ThematicBreak.1.png │ │ ├── testThat_QuoteStripe_AlignsTo_Margin.1.png │ │ ├── testThat_QuotedList_WithinA_ListItem_AlignsCorrectly.1.png │ │ └── testThat_Quotes_WithinA_ListItem_AlignsTo_ListItemContent.1.png │ │ ├── CodeBlockStyleTests │ │ ├── testThat_CodeBlock_IsStyled.1.png │ │ └── testThat_HtmlBlock_IsStyled.1.png │ │ ├── DownDebugLayoutManagerTests │ │ └── testThat_LineFragments_AreDrawn.1.png │ │ ├── HeadingStyleTests │ │ ├── testThat_HeadingStyle_Preserves_StrongEmphasisAndMonospaceTraits.1.png │ │ ├── testThat_Heading_LevelOne_IsStyled.1.png │ │ ├── testThat_Heading_LevelThree_IsStyled.1.png │ │ ├── testThat_Heading_LevelTwo_IsStyled.1.png │ │ └── testThat_Heading_LevelsThreeToSix_AreStyledEqually.1.png │ │ ├── InlineStyleTests │ │ ├── testThat_CodeText_IsStyled.1.png │ │ ├── testThat_EmphasizedCode_IsStyled.1.png │ │ ├── testThat_EmphasizedStrongCode_IsStyled.1.png │ │ ├── testThat_EmphasizedStrongText_IsStyled.1.png │ │ ├── testThat_EmphasizedText_IsStyled.1.png │ │ ├── testThat_StrongCode_IsStyled.1.png │ │ ├── testThat_StrongEmphasizedCode_IsStyled.1.png │ │ ├── testThat_StrongEmphasizedText_IsStyled.1.png │ │ └── testThat_StrongText_IsStyled.1.png │ │ ├── LinkStyleTests │ │ ├── testThat_Link_IsStyled.1.png │ │ └── testThat_Link_Preserves_InlineStyles.1.png │ │ ├── ListItemStyleTests │ │ ├── testThat_DigitAndBulletPrefixes_Align.1.png │ │ ├── testThat_DigitPrefixes_ExceedingMaxPrefixLength_DontPush_WrappedLines.1.png │ │ ├── testThat_DigitPrefixes_ExceedingMaxPrefixLength_Push_FirstLine.1.png │ │ ├── testThat_DigitPrefixes_UpToMaxPrefixLength_Align.1.png │ │ ├── testThat_FirstParagraph_WithLineBreaks_AlignTo_FirstLine.1.png │ │ ├── testThat_FirstParagraph_WrappedLines_AlignTo_FirstLine.1.png │ │ ├── testThat_ListItems_Preseve_InlineElements.1.png │ │ ├── testThat_NestedList_AlignsTo_OuterList.1.png │ │ ├── testThat_NestedList_InMiddleParagraph_AlignsTo_OuterList.1.png │ │ ├── testThat_NestedList_InTrailingParagraph_AlignsTo_OuterList.1.png │ │ ├── testThat_NestedList_With_MultipleParagraphs_Align.1.png │ │ ├── testThat_NestedLists_AlignTo_ParentLists.1.png │ │ ├── testThat_TrailingParagraphs_FirstLines_AlignTo_FirstParagraph.1.png │ │ └── testThat_TrailingParagraphs_WrappedLines_AlignTo_FirstLines.1.png │ │ └── ThematicBreakSyleTests │ │ ├── testThat_ThematicBreak_CanBe_Indented.1.png │ │ ├── testThat_ThematicBreak_InOffsetTextContainer_IsStyled.1.png │ │ └── testThat_ThematicBreak_IsStyled.1.png │ └── __Snapshots__ │ └── BindingTests │ ├── testCommonMarkBindngsWork.1.txt │ ├── testGroffBindingsWork.1.txt │ ├── testHTMLBindingsWork.1.txt │ ├── testLaTeXBindngsWork.1.txt │ └── testXMLBindingsWork.1.txt ├── codecov.yml └── docker ├── Dockerfile ├── README.md ├── docker-compose.yml └── down-rebuild.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Help isolate and fix bugs 4 | 5 | --- 6 | 7 | Please help prevent duplicate issues before submitting a new one: 8 | 9 | * [ ] I've searched other open/closed issues for duplicates before opening up this new issue. 10 | 11 | # Report 12 | 13 | ## What did you do? 14 | 15 | ℹ Please replace this with what you did. 16 | 17 | ## What did you expect to happen? 18 | 19 | ℹ Please replace this with what you expected to happen. 20 | 21 | ## What happened instead? 22 | 23 | ℹ Please replace this with what happened instead (e.g. the issue). 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an enhancement for this project 4 | 5 | --- 6 | 7 | Please help prevent duplicate requests before submitting a new one: 8 | 9 | * [ ] I've searched other open/closed issues for duplicates before opening up this new issue. 10 | 11 | # Feature Request 12 | 13 | ## Is your feature request related to a problem? Please describe. 14 | 15 | ℹ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 16 | 17 | ## Describe the solution you'd like 18 | 19 | ℹ A clear and concise description of what you want to happen. 20 | 21 | ## Describe alternatives you've considered 22 | 23 | ℹ A clear and concise description of any alternative solutions or features you've considered. 24 | -------------------------------------------------------------------------------- /.github/workflows/swiftlint.yml: -------------------------------------------------------------------------------- 1 | name: SwiftLint 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/workflows/swiftlint.yml' 7 | - '.swiftlint.yml' 8 | - '**/*.swift' 9 | 10 | jobs: 11 | SwiftLint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: GitHub Action for SwiftLint 16 | uses: norio-nomura/action-swiftlint@3.2.1 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | .build/ 3 | .DS_Store 4 | xcuserdata 5 | 6 | # additional ignores that will save us time 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcuserstate 22 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources/Down 3 | - Tests 4 | 5 | large_tuple: 6 | warning: 3 7 | error: 4 8 | 9 | cyclomatic_complexity: 10 | ignores_case_statements: true 11 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode12.2 2 | language: objective-c 3 | before_install: 4 | - set -o pipefail 5 | - xcrun simctl boot "iPhone 12" || echo "(Pre)Launched the simulator." 6 | script: 7 | - travis_retry xcodebuild -project Down.xcodeproj -scheme "Down" -sdk iphonesimulator -destination "platform=iOS Simulator,OS=14.2,name=iPhone 12" -enableCodeCoverage YES ONLY_ACTIVE_ARCH=YES -quiet test 8 | - bash <(curl -s https://codecov.io/bash) 9 | - travis_retry xcodebuild -project Down.xcodeproj -scheme "Down" -sdk macosx -destination 'platform=OS X,arch=x86_64' -enableCodeCoverage YES -quiet test 10 | - bash <(curl -s https://codecov.io/bash) 11 | - travis_retry xcodebuild -project Down.xcodeproj -scheme "Down" -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' -enableCodeCoverage YES -quiet test 12 | - bash <(curl -s https://codecov.io/bash) 13 | -------------------------------------------------------------------------------- /Down-Example/Down-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Down-Example/Down-Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Down-Example/Down-Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SnapshotTesting", 6 | "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c466812aa2e22898f27557e2e780d3aad7a27203", 10 | "version": "1.8.2" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Down-Example/Down-Example.xcodeproj/xcshareddata/xcschemes/Down-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Down-Example/Down-Example.xcodeproj/xcshareddata/xcschemes/macOS Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Down-Example/Down-Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Down-Example 4 | // 5 | // Created by Keaton Burleson on 7/1/17. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Down-Example/Down-Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Down-Example/Down-Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Down-Example/Down-Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Down-Example/Down-Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleDefault 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Down-Example/Down-Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Down-Example 4 | // 5 | // Created by Keaton Burleson on 7/1/17. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Down 11 | 12 | final class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | renderDownInWebView() 18 | } 19 | 20 | } 21 | 22 | private extension ViewController { 23 | 24 | func renderDownInWebView() { 25 | guard let readMeURL = Bundle.main.url(forResource: nil, withExtension: "md"), 26 | let readMeContents = try? String(contentsOf: readMeURL) 27 | else { 28 | showError(message: "Could not load readme contents.") 29 | return 30 | } 31 | 32 | do { 33 | let downView = try DownView(frame: view.bounds, markdownString: readMeContents, didLoadSuccessfully: { 34 | print("Markdown was rendered.") 35 | }) 36 | downView.translatesAutoresizingMaskIntoConstraints = false 37 | view.addSubview(downView) 38 | constrain(subview: downView) 39 | createStatusBarBackgrounds(above: downView) 40 | } catch { 41 | showError(message: error.localizedDescription) 42 | } 43 | } 44 | 45 | func createStatusBarBackgrounds(above subview: UIView) { 46 | let blurEffect = UIBlurEffect(style: .prominent) 47 | let blurEffectView = UIVisualEffectView(effect: blurEffect) 48 | blurEffectView.translatesAutoresizingMaskIntoConstraints = false 49 | view.insertSubview(blurEffectView, aboveSubview: subview) 50 | constrain(subview: blurEffectView, bottomAnchor: topLayoutGuide.bottomAnchor) 51 | } 52 | 53 | func constrain(subview: UIView, bottomAnchor: NSLayoutYAxisAnchor? = nil) { 54 | NSLayoutConstraint.activate([ 55 | subview.leadingAnchor.constraint(equalTo: view.leadingAnchor), 56 | subview.trailingAnchor.constraint(equalTo: view.trailingAnchor), 57 | subview.topAnchor.constraint(equalTo: topLayoutGuide.topAnchor), 58 | subview.bottomAnchor.constraint(equalTo: bottomAnchor ?? bottomLayoutGuide.bottomAnchor) 59 | ]) 60 | } 61 | 62 | func showError(message: String) { 63 | let alertController = UIAlertController(title: "DownView Render Error", 64 | message: message, 65 | preferredStyle: .alert) 66 | self.present(alertController, animated: true, completion: nil) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Down-Example/macOS Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS Demo 4 | // 5 | // Created by Chris Zielinski on 10/27/18. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate {} 13 | 14 | -------------------------------------------------------------------------------- /Down-Example/macOS Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Down-Example/macOS Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Down-Example/macOS Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2016-2019 Down. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Down-Example/macOS Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // macOS Demo 4 | // 5 | // Created by Chris Zielinski on 10/27/18. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Down 11 | 12 | final class ViewController: NSViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | renderDownInWebView() 18 | } 19 | 20 | } 21 | 22 | private extension ViewController { 23 | 24 | func renderDownInWebView() { 25 | let readMeURL = Bundle.main.url(forResource: nil, withExtension: "md")! 26 | let readMeContents = try! String(contentsOf: readMeURL) 27 | 28 | do { 29 | let downView = try DownView(frame: view.bounds, markdownString: readMeContents, didLoadSuccessfully: { 30 | print("Markdown was rendered.") 31 | }) 32 | downView.autoresizingMask = [.width, .height] 33 | view.addSubview(downView, positioned: .below, relativeTo: nil) 34 | } catch { 35 | NSApp.presentError(error) 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Down-Example/macOS Demo/macOS Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Down.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "Down" 3 | spec.summary = "Blazing fast Markdown rendering in Swift, built upon cmark." 4 | spec.version = "0.11.0" 5 | spec.homepage = "https://github.com/johnxnguyen/Down" 6 | spec.license = { :type => "MIT", :file => "LICENSE" } 7 | spec.authors = { "John Nguyen" => "polyxo@protonmail.com" } 8 | spec.source = { :git => "https://github.com/johnxnguyen/Down.git", :tag => "v" + spec.version.to_s } 9 | spec.source_files = "Sources/Down/{AST,Enums & Options,Extensions,Renderers}/**/*.swift", "Sources/cmark/*.{h,c}", "Sources/Down/*" 10 | spec.ios.source_files = "Sources/Down/Views/**" 11 | spec.osx.source_files = "Sources/Down/Views/**" 12 | spec.public_header_files = "Sources/Down/*.h" 13 | spec.ios.deployment_target = "9.0" 14 | spec.tvos.deployment_target = "9.0" 15 | spec.osx.deployment_target = "10.11" 16 | spec.requires_arc = true 17 | spec.module_name = "Down" 18 | spec.preserve_paths = "Sources/cmark/include/module.modulemap", "Sources/cmark/*.inc", "Sources/cmark/COPYING" 19 | spec.pod_target_xcconfig = { 'SWIFT_INCLUDE_PATHS' => '$(SRCROOT)/Down/Sources/cmark/**' } 20 | spec.ios.resource = 'Sources/Down/Resources/DownView.bundle' 21 | spec.osx.resource = 'Sources/Down/Resources/DownView.bundle' 22 | spec.swift_versions = ['5.0', '5.1'] 23 | end 24 | -------------------------------------------------------------------------------- /Down.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Down.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Down.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Down.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SnapshotTesting", 6 | "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c466812aa2e22898f27557e2e780d3aad7a27203", 10 | "version": "1.8.2" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Down.xcodeproj/xcshareddata/xcschemes/Down.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 64 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Images/ohhai.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Images/ohhai.gif -------------------------------------------------------------------------------- /LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | var tests = [XCTestCaseEntry]() 4 | 5 | // FIXME: Run on macOS `swift test --generate-linuxmain` and maintain Linux Tests. 6 | 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Down", 7 | platforms: [ 8 | .macOS("10.11"), 9 | .iOS("9.0"), 10 | .tvOS("9.0") 11 | ], 12 | products: [ 13 | .library( 14 | name: "Down", 15 | targets: ["Down"] 16 | ) 17 | ], 18 | targets: [ 19 | .target( 20 | name: "libcmark", 21 | dependencies: [], 22 | path: "Sources/cmark", 23 | exclude: [ 24 | "include", 25 | "case_fold_switch.inc", 26 | "entities.inc", 27 | "COPYING" 28 | ], 29 | publicHeadersPath: "./" 30 | ), 31 | .target( 32 | name: "Down", 33 | dependencies: ["libcmark"], 34 | path: "Sources/Down", 35 | exclude: ["Down.h"], 36 | resources: [ 37 | .copy("Resources/DownView.bundle"), 38 | .copy("Resources/DownView (macOS).bundle"), 39 | ] 40 | ), 41 | .testTarget( 42 | name: "DownTests", 43 | dependencies: ["Down"], 44 | path: "Tests/DownTests", 45 | exclude: [ 46 | "AST/VisitorTests.swift", 47 | "AST/__Snapshots__", 48 | "DownViewTests.swift", 49 | "Fixtures", 50 | "Styler/__Snapshots__", 51 | "Styler/BlockQuoteStyleTests.swift", 52 | "Styler/CodeBlockStyleTests.swift", 53 | "Styler/DownDebugLayoutManagerTests.swift", 54 | "Styler/HeadingStyleTests.swift", 55 | "Styler/LinkStyleTests.swift", 56 | "Styler/InlineStyleTests.swift", 57 | "Styler/ListItemStyleTests.swift", 58 | "Styler/StylerTestSuite.swift", 59 | "Styler/ThematicBreakSyleTests.swift" 60 | ] 61 | ) 62 | ], 63 | swiftLanguageVersions: [.v5] 64 | ) 65 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/BaseNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNode.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 21.04.19. 6 | // 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public class BaseNode: Node { 13 | 14 | // MARK: - Properties 15 | 16 | public let cmarkNode: CMarkNode 17 | 18 | public private(set) lazy var children: [Node] = Array(childSequence) 19 | 20 | public private(set) lazy var nestDepth: Int = { 21 | var depth = 0 22 | var next = cmarkNode.parent 23 | 24 | while let current = next { 25 | depth += current.type == cmarkNode.type ? 1 : 0 26 | next = current.parent 27 | } 28 | return depth 29 | }() 30 | 31 | // MARK: - Life cycle 32 | 33 | init(cmarkNode: CMarkNode) { 34 | self.cmarkNode = cmarkNode 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/BlockQuote.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlockQuote.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class BlockQuote: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension BlockQuote: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Block Quote" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/ChildSequence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildSequence.swift 3 | // Down 4 | // 5 | // Created by Sven Weidauer on 05.10.2020 6 | // 7 | 8 | import libcmark 9 | 10 | /// Sequence of child nodes. 11 | 12 | public struct ChildSequence: Sequence { 13 | 14 | // MARK: - Properties 15 | 16 | let node: CMarkNode 17 | 18 | // MARK: - Methods 19 | 20 | public func makeIterator() -> Iterator { 21 | return Iterator(node: cmark_node_first_child(node)) 22 | } 23 | 24 | } 25 | 26 | // MARK: - Iterator 27 | 28 | public extension ChildSequence { 29 | 30 | struct Iterator: IteratorProtocol { 31 | 32 | // MARK: - Properties 33 | 34 | var node: CMarkNode? 35 | 36 | // MARK: - Methods 37 | 38 | public mutating func next() -> Node? { 39 | guard let node = node else { return nil } 40 | defer { self.node = cmark_node_next(node) } 41 | 42 | guard let result = node.wrap() else { 43 | assertionFailure("Couldn't wrap node of type: \(node.type)") 44 | return nil 45 | } 46 | 47 | return result 48 | } 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Code.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Code.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Code: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The code content, if present. 16 | 17 | public private(set) lazy var literal: String? = cmarkNode.literal 18 | 19 | } 20 | 21 | // MARK: - Debug 22 | 23 | extension Code: CustomDebugStringConvertible { 24 | 25 | public var debugDescription: String { 26 | return "Code - \(literal ?? "nil")" 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/CodeBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeBlock.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class CodeBlock: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The code content, if present. 16 | 17 | public private(set) lazy var literal: String? = cmarkNode.literal 18 | 19 | /// The fence info is an optional string that trails the opening sequence of backticks. 20 | /// It can be used to provide some contextual information about the block, such as 21 | /// the name of a programming language. 22 | /// 23 | /// For example: 24 | /// ``` 25 | /// ''' 26 | /// 27 | /// ''' 28 | /// ``` 29 | /// 30 | 31 | public private(set) lazy var fenceInfo: String? = cmarkNode.fenceInfo 32 | 33 | } 34 | 35 | // MARK: - Debug 36 | 37 | extension CodeBlock: CustomDebugStringConvertible { 38 | 39 | public var debugDescription: String { 40 | let content = (literal ?? "nil").replacingOccurrences(of: "\n", with: "\\n") 41 | return "Code Block - fenceInfo: \(fenceInfo ?? "nil"), content: \(content)" 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/CustomBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomBlock.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class CustomBlock: BaseNode { 12 | 13 | // MARK: - Properfies 14 | 15 | /// The custom content, if present. 16 | 17 | public private(set) lazy var literal: String? = cmarkNode.literal 18 | 19 | } 20 | 21 | // MARK: - Debug 22 | 23 | extension CustomBlock: CustomDebugStringConvertible { 24 | 25 | public var debugDescription: String { 26 | return "Custom Block - \(literal ?? "nil")" 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/CustomInline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomInline.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class CustomInline: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The custom content, if present. 16 | 17 | public private(set) lazy var literal: String? = cmarkNode.literal 18 | } 19 | 20 | // MARK: - Debug 21 | 22 | extension CustomInline: CustomDebugStringConvertible { 23 | 24 | public var debugDescription: String { 25 | return "Custom Inline - \(literal ?? "nil")" 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Document.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Document.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Document: BaseNode { 12 | 13 | // MARK: - Life cycle 14 | 15 | deinit { 16 | cmark_node_free(cmarkNode) 17 | } 18 | 19 | // MARK: - Methods 20 | 21 | /// Accepts the given visitor and return its result. 22 | 23 | @discardableResult 24 | public func accept(_ visitor: T) -> T.Result { 25 | return visitor.visit(document: self) 26 | } 27 | 28 | } 29 | 30 | // MARK: - Debug 31 | 32 | extension Document: CustomDebugStringConvertible { 33 | 34 | public var debugDescription: String { 35 | return "Document" 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Emphasis.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Emphasis.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Emphasis: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension Emphasis: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Emphasis" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Heading.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Heading.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Heading: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The level of the heading, a value between 1 and 6. 16 | 17 | public private(set) lazy var headingLevel: Int = cmarkNode.headingLevel 18 | } 19 | 20 | // MARK: - Debug 21 | 22 | extension Heading: CustomDebugStringConvertible { 23 | 24 | public var debugDescription: String { 25 | return "Heading - L\(headingLevel)" 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/HtmlBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HtmlBlock.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class HtmlBlock: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The html content, if present. 16 | 17 | public private(set) lazy var literal: String? = cmarkNode.literal 18 | 19 | } 20 | 21 | // MARK: - Debug 22 | 23 | extension HtmlBlock: CustomDebugStringConvertible { 24 | 25 | public var debugDescription: String { 26 | let content = (literal ?? "nil").replacingOccurrences(of: "\n", with: "\\n") 27 | return "Html Block - content: \(content)" 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/HtmlInline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HtmlInline.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class HtmlInline: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The html tag, if present. 16 | 17 | public private(set) lazy var literal: String? = cmarkNode.literal 18 | 19 | } 20 | 21 | // MARK: - Debug 22 | 23 | extension HtmlInline: CustomDebugStringConvertible { 24 | 25 | public var debugDescription: String { 26 | return "Html Inline - \(literal ?? "nil")" 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Image: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The title of the image, if present. 16 | /// 17 | /// In the example below, the first line is a reference link, with the reference at the 18 | /// bottom. `` is literal text belonging to children nodes. The title occurs 19 | /// after the url and is optional. 20 | /// 21 | /// ``` 22 | /// ![][] 23 | /// ... 24 | /// []: "" 25 | /// ``` 26 | 27 | public private(set) lazy var title: String? = cmarkNode.title 28 | 29 | /// The url of the image, if present. 30 | /// 31 | /// For example: 32 | /// 33 | /// ``` 34 | /// ![<text>](<url>) 35 | /// ``` 36 | 37 | public private(set) lazy var url: String? = cmarkNode.url 38 | 39 | } 40 | 41 | // MARK: - Debug 42 | 43 | extension Image: CustomDebugStringConvertible { 44 | 45 | public var debugDescription: String { 46 | return "Image - title: \(title ?? "nil"), url: \(url ?? "nil"))" 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Item.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Item.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Item: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension Item: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Item" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/LineBreak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineBreak.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class LineBreak: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension LineBreak: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Line Break" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Link.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Link.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Link: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The title of the link, if present. 16 | /// 17 | /// In the example below, the first line is a reference link, with the reference at the 18 | /// bottom. `<text>` is literal text belonging to children nodes. The title occurs 19 | /// after the url and is optional. 20 | /// 21 | /// ``` 22 | /// [<text>][<id>] 23 | /// ... 24 | /// [<id>]: <url> "<title>" 25 | /// ``` 26 | 27 | public private(set) lazy var title: String? = cmarkNode.title 28 | 29 | /// The url of the link, if present. 30 | /// 31 | /// For example: 32 | /// 33 | /// ``` 34 | /// [<text>](<url>) 35 | /// ``` 36 | 37 | public private(set) lazy var url: String? = cmarkNode.url 38 | 39 | } 40 | 41 | // MARK: - Debug 42 | 43 | extension Link: CustomDebugStringConvertible { 44 | 45 | public var debugDescription: String { 46 | return "Link - title: \(title ?? "nil"), url: \(url ?? "nil"))" 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/List.swift: -------------------------------------------------------------------------------- 1 | // 2 | // List.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class List: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The type of the list, either bullet or ordered. 16 | 17 | public lazy var listType: ListType = { 18 | guard let type = ListType(cmarkNode: cmarkNode) else { 19 | assertionFailure("Unsupported or missing list type. Defaulting to .bullet.") 20 | return .bullet 21 | } 22 | 23 | return type 24 | }() 25 | 26 | /// The number of items in the list. 27 | 28 | public lazy var numberOfItems: Int = children.count 29 | 30 | /// Whether the list is "tight". 31 | /// 32 | /// If any of the list items are separated by a blank line, then this property is `false`. This value is 33 | /// a hint to render the list with more (loose) or less (tight) spacing between items. 34 | 35 | public lazy var isTight: Bool = cmark_node_get_list_tight(cmarkNode) == 1 36 | 37 | /// The list delimiter. 38 | 39 | public lazy var delimiter: Delimiter? = Delimiter(cmarkNode.listDelimiter) 40 | } 41 | 42 | // MARK: - List Type 43 | 44 | public extension List { 45 | 46 | enum Delimiter { 47 | case period 48 | case paren 49 | 50 | init?(_ cmark: cmark_delim_type) { 51 | switch cmark { 52 | case CMARK_NO_DELIM: return nil 53 | case CMARK_PERIOD_DELIM: self = .period 54 | case CMARK_PAREN_DELIM: self = .paren 55 | default: preconditionFailure("Invalid delim type") 56 | } 57 | } 58 | } 59 | 60 | enum ListType: CustomDebugStringConvertible { 61 | case bullet 62 | case ordered(start: Int) 63 | 64 | // MARK: - Properties 65 | 66 | public var debugDescription: String { 67 | switch self { 68 | case .bullet: return "Bullet" 69 | case .ordered(let start): return "Ordered (start: \(start))" 70 | } 71 | } 72 | 73 | // MARK: - Life cycle 74 | 75 | init?(cmarkNode: CMarkNode) { 76 | switch cmarkNode.listType { 77 | case CMARK_BULLET_LIST: self = .bullet 78 | case CMARK_ORDERED_LIST: self = .ordered(start: cmarkNode.listStart) 79 | default: return nil 80 | } 81 | } 82 | 83 | } 84 | } 85 | 86 | // MARK: - Debug 87 | 88 | extension List: CustomDebugStringConvertible { 89 | 90 | public var debugDescription: String { 91 | var result = "List - type: \(listType), isTight: \(isTight)" 92 | if let delim = delimiter { 93 | result += ", delimiter: \(delim)" 94 | } 95 | return result 96 | } 97 | 98 | } 99 | 100 | extension List.Delimiter: CustomDebugStringConvertible { 101 | public var debugDescription: String { 102 | switch self { 103 | case .paren: return "paren" 104 | case .period: return "period" 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Paragraph.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Paragraph.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Paragraph: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension Paragraph: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Paragraph" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/SoftBreak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SoftBreak.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class SoftBreak: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension SoftBreak: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Soft Break" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Strong.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Strong.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Strong: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension Strong: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Strong" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/Text.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Text.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class Text: BaseNode { 12 | 13 | // MARK: - Properties 14 | 15 | /// The text content, if present. 16 | 17 | public private(set) lazy var literal: String? = cmarkNode.literal 18 | 19 | } 20 | 21 | // MARK: - Debug 22 | 23 | extension Text: CustomDebugStringConvertible { 24 | 25 | public var debugDescription: String { 26 | return "Text - \(literal ?? "nil")" 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Down/AST/Nodes/ThematicBreak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThematicBreak.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | import libcmark 10 | 11 | public class ThematicBreak: BaseNode {} 12 | 13 | // MARK: - Debug 14 | 15 | extension ThematicBreak: CustomDebugStringConvertible { 16 | 17 | public var debugDescription: String { 18 | return "Thematic Break" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Attribute Collections/ColorCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorCollection.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 27.07.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | public typealias DownColor = UIColor 15 | 16 | #elseif canImport(AppKit) 17 | 18 | import AppKit 19 | public typealias DownColor = NSColor 20 | 21 | #endif 22 | 23 | public protocol ColorCollection { 24 | 25 | var heading1: DownColor { get } 26 | var heading2: DownColor { get } 27 | var heading3: DownColor { get } 28 | var heading4: DownColor { get } 29 | var heading5: DownColor { get } 30 | var heading6: DownColor { get } 31 | var body: DownColor { get } 32 | var code: DownColor { get } 33 | var link: DownColor { get } 34 | var quote: DownColor { get } 35 | var quoteStripe: DownColor { get } 36 | var thematicBreak: DownColor { get } 37 | var listItemPrefix: DownColor { get } 38 | var codeBlockBackground: DownColor { get } 39 | 40 | } 41 | 42 | public struct StaticColorCollection: ColorCollection { 43 | 44 | // MARK: - Properties 45 | 46 | public var heading1: DownColor 47 | public var heading2: DownColor 48 | public var heading3: DownColor 49 | public var heading4: DownColor 50 | public var heading5: DownColor 51 | public var heading6: DownColor 52 | public var body: DownColor 53 | public var code: DownColor 54 | public var link: DownColor 55 | public var quote: DownColor 56 | public var quoteStripe: DownColor 57 | public var thematicBreak: DownColor 58 | public var listItemPrefix: DownColor 59 | public var codeBlockBackground: DownColor 60 | 61 | // MARK: - Life cycle 62 | 63 | public init( 64 | heading1: DownColor = .black, 65 | heading2: DownColor = .black, 66 | heading3: DownColor = .black, 67 | heading4: DownColor = .black, 68 | heading5: DownColor = .black, 69 | heading6: DownColor = .black, 70 | body: DownColor = .black, 71 | code: DownColor = .black, 72 | link: DownColor = .blue, 73 | quote: DownColor = .darkGray, 74 | quoteStripe: DownColor = .darkGray, 75 | thematicBreak: DownColor = .init(white: 0.9, alpha: 1), 76 | listItemPrefix: DownColor = .lightGray, 77 | codeBlockBackground: DownColor = .init(red: 0.96, green: 0.97, blue: 0.98, alpha: 1) 78 | ) { 79 | self.heading1 = heading1 80 | self.heading2 = heading2 81 | self.heading3 = heading3 82 | self.heading4 = heading4 83 | self.heading5 = heading5 84 | self.heading6 = heading6 85 | self.body = body 86 | self.code = code 87 | self.link = link 88 | self.quote = quote 89 | self.quoteStripe = quoteStripe 90 | self.thematicBreak = thematicBreak 91 | self.listItemPrefix = listItemPrefix 92 | self.codeBlockBackground = codeBlockBackground 93 | } 94 | 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Attribute Collections/FontCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontCollection.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 22.06.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | public typealias DownFont = UIFont 15 | 16 | #elseif canImport(AppKit) 17 | 18 | import AppKit 19 | public typealias DownFont = NSFont 20 | 21 | #endif 22 | 23 | public protocol FontCollection { 24 | 25 | var heading1: DownFont { get } 26 | var heading2: DownFont { get } 27 | var heading3: DownFont { get } 28 | var heading4: DownFont { get } 29 | var heading5: DownFont { get } 30 | var heading6: DownFont { get } 31 | var body: DownFont { get } 32 | var code: DownFont { get } 33 | var listItemPrefix: DownFont { get } 34 | 35 | } 36 | 37 | public struct StaticFontCollection: FontCollection { 38 | 39 | // MARK: - Properties 40 | 41 | public var heading1: DownFont 42 | public var heading2: DownFont 43 | public var heading3: DownFont 44 | public var heading4: DownFont 45 | public var heading5: DownFont 46 | public var heading6: DownFont 47 | public var body: DownFont 48 | public var code: DownFont 49 | public var listItemPrefix: DownFont 50 | 51 | // MARK: - Life cycle 52 | 53 | public init( 54 | heading1: DownFont = .boldSystemFont(ofSize: 28), 55 | heading2: DownFont = .boldSystemFont(ofSize: 24), 56 | heading3: DownFont = .boldSystemFont(ofSize: 20), 57 | heading4: DownFont = .boldSystemFont(ofSize: 20), 58 | heading5: DownFont = .boldSystemFont(ofSize: 20), 59 | heading6: DownFont = .boldSystemFont(ofSize: 20), 60 | body: DownFont = .systemFont(ofSize: 17), 61 | code: DownFont = DownFont(name: "menlo", size: 17) ?? .systemFont(ofSize: 17), 62 | listItemPrefix: DownFont = DownFont.monospacedDigitSystemFont(ofSize: 17, weight: .regular) 63 | ) { 64 | self.heading1 = heading1 65 | self.heading2 = heading2 66 | self.heading3 = heading3 67 | self.heading4 = heading4 68 | self.heading5 = heading5 69 | self.heading6 = heading6 70 | self.body = body 71 | self.code = code 72 | self.listItemPrefix = listItemPrefix 73 | } 74 | 75 | } 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Attribute Collections/ParagraphStyleCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParagraphStyleCollection.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 27.07.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | public protocol ParagraphStyleCollection { 22 | 23 | var heading1: NSParagraphStyle { get } 24 | var heading2: NSParagraphStyle { get } 25 | var heading3: NSParagraphStyle { get } 26 | var heading4: NSParagraphStyle { get } 27 | var heading5: NSParagraphStyle { get } 28 | var heading6: NSParagraphStyle { get } 29 | var body: NSParagraphStyle { get } 30 | var code: NSParagraphStyle { get } 31 | 32 | } 33 | 34 | public struct StaticParagraphStyleCollection: ParagraphStyleCollection { 35 | 36 | // MARK: - Properties 37 | 38 | public var heading1: NSParagraphStyle 39 | public var heading2: NSParagraphStyle 40 | public var heading3: NSParagraphStyle 41 | public var heading4: NSParagraphStyle 42 | public var heading5: NSParagraphStyle 43 | public var heading6: NSParagraphStyle 44 | public var body: NSParagraphStyle 45 | public var code: NSParagraphStyle 46 | 47 | // MARK: - Life cycle 48 | 49 | public init() { 50 | let headingStyle = NSMutableParagraphStyle() 51 | headingStyle.paragraphSpacing = 8 52 | 53 | let bodyStyle = NSMutableParagraphStyle() 54 | bodyStyle.paragraphSpacingBefore = 8 55 | bodyStyle.paragraphSpacing = 8 56 | bodyStyle.lineSpacing = 8 57 | 58 | let codeStyle = NSMutableParagraphStyle() 59 | codeStyle.paragraphSpacingBefore = 8 60 | codeStyle.paragraphSpacing = 8 61 | 62 | heading1 = headingStyle 63 | heading2 = headingStyle 64 | heading3 = headingStyle 65 | heading4 = headingStyle 66 | heading5 = headingStyle 67 | heading6 = headingStyle 68 | body = bodyStyle 69 | code = codeStyle 70 | } 71 | 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Custom Attributes/BlockBackgroundColorAttribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlockBackgroundColorAttribute.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 11.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | struct BlockBackgroundColorAttribute { 22 | 23 | // MARK: - Properties 24 | 25 | var color: DownColor 26 | var inset: CGFloat 27 | 28 | } 29 | 30 | extension NSAttributedString.Key { 31 | 32 | static let blockBackgroundColor = NSAttributedString.Key("blockBackgroundColor") 33 | 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Custom Attributes/QuoteStripeAttribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuoteStripeAttrbute.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 03.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | struct QuoteStripeAttribute { 22 | 23 | // MARK: - Properties 24 | 25 | var color: DownColor 26 | var thickness: CGFloat 27 | var spacingAfter: CGFloat 28 | var locations: [CGFloat] 29 | 30 | var layoutWidth: CGFloat { 31 | return thickness + spacingAfter 32 | } 33 | 34 | // MARK: - Life cycle 35 | 36 | init(color: DownColor, thickness: CGFloat, spacingAfter: CGFloat, locations: [CGFloat]) { 37 | self.color = color 38 | self.thickness = thickness 39 | self.spacingAfter = spacingAfter 40 | self.locations = locations 41 | } 42 | 43 | init(level: Int, color: DownColor, options: QuoteStripeOptions) { 44 | self.init(color: color, thickness: options.thickness, spacingAfter: options.spacingAfter, locations: []) 45 | locations = (0..<level).map { CGFloat($0) * layoutWidth } 46 | } 47 | 48 | // MARK: - Methods 49 | 50 | func indented(by indentation: CGFloat) -> QuoteStripeAttribute { 51 | var copy = self 52 | copy.locations = locations.map { $0 + indentation } 53 | return copy 54 | } 55 | 56 | } 57 | 58 | extension NSAttributedString.Key { 59 | 60 | static let quoteStripe = NSAttributedString.Key(rawValue: "quoteStripe") 61 | 62 | } 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Custom Attributes/ThematicBreakAttribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThematicBreaAttributek.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 02.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | struct ThematicBreakAttribute { 22 | 23 | // MARK: - Properties 24 | 25 | var thickness: CGFloat 26 | var color: DownColor 27 | 28 | } 29 | 30 | extension NSAttributedString.Key { 31 | 32 | static let thematicBreak = NSAttributedString.Key(rawValue: "thematicBreak") 33 | 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Helpers/Extensions/CGPoint+Translate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint+Translate.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 12.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | extension CGPoint { 22 | 23 | func translated(by point: CGPoint) -> CGPoint { 24 | return CGPoint(x: x + point.x, y: y + point.y) 25 | } 26 | 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Helpers/Extensions/CGRect+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+Helpers.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 12.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | extension CGRect { 22 | 23 | init(minX: CGFloat, minY: CGFloat, maxX: CGFloat, maxY: CGFloat) { 24 | self.init(x: minX, y: minY, width: maxX - minX, height: maxY - minY) 25 | } 26 | 27 | func translated(by point: CGPoint) -> CGRect { 28 | return CGRect(origin: origin.translated(by: point), size: size) 29 | } 30 | 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Helpers/Extensions/NSAttributedString+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+Helpers.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 22.06.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSAttributedString { 12 | 13 | typealias Attributes = [NSAttributedString.Key: Any] 14 | 15 | // MARK: - Ranges 16 | 17 | var wholeRange: NSRange { 18 | return NSRange(location: 0, length: length) 19 | } 20 | 21 | func ranges(of key: Key) -> [NSRange] { 22 | return ranges(of: key, in: wholeRange) 23 | } 24 | 25 | func ranges(of key: Key, in range: NSRange) -> [NSRange] { 26 | return ranges(for: key, in: range, where: { $0 != nil }) 27 | } 28 | 29 | func rangesMissingAttribute(for key: Key) -> [NSRange] { 30 | return rangesMissingAttribute(for: key, in: wholeRange) 31 | } 32 | 33 | func rangesMissingAttribute(for key: Key, in range: NSRange) -> [NSRange] { 34 | return ranges(for: key, in: range, where: { $0 == nil }) 35 | } 36 | 37 | private func ranges(for key: Key, in range: NSRange, where predicate: (Any?) -> Bool) -> [NSRange] { 38 | var ranges = [NSRange]() 39 | 40 | enumerateAttribute(key, in: range, options: []) { value, attrRange, _ in 41 | if predicate(value) { 42 | ranges.append(attrRange) 43 | } 44 | } 45 | 46 | return ranges 47 | } 48 | 49 | func paragraphRanges() -> [NSRange] { 50 | guard length > 0 else { return [] } 51 | 52 | func nextParagraphRange(at location: Int) -> NSRange { 53 | return NSString(string: string).paragraphRange(for: NSRange(location: location, length: 1)) 54 | } 55 | 56 | var result = [nextParagraphRange(at: 0)] 57 | 58 | while let currentLocation = result.last?.upperBound, currentLocation < length { 59 | result.append(nextParagraphRange(at: currentLocation)) 60 | } 61 | 62 | return result.filter { $0.length > 1 } 63 | } 64 | 65 | // MARK: - Enumerate attributes 66 | 67 | func enumerateAttributes<A>(for key: Key, block: (_ attr: A, _ range: NSRange) -> Void) { 68 | enumerateAttributes(for: key, in: wholeRange, block: block) 69 | } 70 | 71 | func enumerateAttributes<A>(for key: Key, in range: NSRange, block: (_ attr: A, _ range: NSRange) -> Void) { 72 | enumerateAttribute(key, in: range, options: []) { value, range, _ in 73 | if let value = value as? A { 74 | block(value, range) 75 | } 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Helpers/Extensions/NSMutableAttributedString+Attributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+Attributes.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 22.06.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSMutableAttributedString { 12 | 13 | func setAttributes(_ attrs: Attributes) { 14 | setAttributes(attrs, range: wholeRange) 15 | } 16 | 17 | func addAttributes(_ attrs: Attributes) { 18 | addAttributes(attrs, range: wholeRange) 19 | } 20 | 21 | func addAttribute(for key: Key, value: Any) { 22 | addAttribute(key, value: value, range: wholeRange) 23 | } 24 | 25 | func removeAttribute(for key: Key) { 26 | removeAttribute(key, range: wholeRange) 27 | } 28 | 29 | func replaceAttribute(for key: Key, value: Any) { 30 | replaceAttribute(for: key, value: value, inRange: wholeRange) 31 | } 32 | 33 | func replaceAttribute(for key: Key, value: Any, inRange range: NSRange) { 34 | removeAttribute(key, range: range) 35 | addAttribute(key, value: value, range: range) 36 | } 37 | 38 | func updateExistingAttributes<A>(for key: Key, using transform: (A) -> A) { 39 | updateExistingAttributes(for: key, in: wholeRange, using: transform) 40 | } 41 | 42 | func updateExistingAttributes<A>(for key: Key, in range: NSRange, using transform: (A) -> A) { 43 | var existingValues = [(value: A, range: NSRange)]() 44 | enumerateAttributes(for: key, in: range) { existingValues.append(($0, $1)) } 45 | existingValues.forEach { addAttribute(key, value: transform($0.0), range: $0.1) } 46 | } 47 | 48 | func addAttributeInMissingRanges<A>(for key: Key, value: A) { 49 | addAttributeInMissingRanges(for: key, value: value, within: wholeRange) 50 | } 51 | 52 | func addAttributeInMissingRanges<A>(for key: Key, value: A, within range: NSRange) { 53 | rangesMissingAttribute(for: key, in: range).forEach { 54 | addAttribute(key, value: value, range: $0) 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Helpers/Extensions/UIFont+Traits.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+Traits.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 22.06.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | public typealias DownFontDescriptor = UIFontDescriptor 15 | 16 | #elseif canImport(AppKit) 17 | 18 | import AppKit 19 | public typealias DownFontDescriptor = NSFontDescriptor 20 | 21 | #endif 22 | 23 | extension DownFont { 24 | 25 | var isStrong: Bool { 26 | return contains(.strong) 27 | } 28 | 29 | var isEmphasized: Bool { 30 | return contains(.emphasis) 31 | } 32 | 33 | var isMonospace: Bool { 34 | return contains(.monoSpace) 35 | } 36 | 37 | var strong: DownFont { 38 | return with(.strong) ?? self 39 | } 40 | 41 | var emphasis: DownFont { 42 | return with(.emphasis) ?? self 43 | } 44 | 45 | var monospace: DownFont { 46 | return with(.monoSpace) ?? self 47 | } 48 | 49 | private func with(_ trait: DownFontDescriptor.SymbolicTraits) -> DownFont? { 50 | guard !contains(trait) else { return self } 51 | 52 | var traits = fontDescriptor.symbolicTraits 53 | traits.insert(trait) 54 | 55 | #if canImport(UIKit) 56 | guard let newDescriptor = fontDescriptor.withSymbolicTraits(traits) else { return self } 57 | return DownFont(descriptor: newDescriptor, size: pointSize) 58 | 59 | #elseif canImport(AppKit) 60 | let newDescriptor = fontDescriptor.withSymbolicTraits(traits) 61 | return DownFont(descriptor: newDescriptor, size: pointSize) 62 | 63 | #endif 64 | } 65 | 66 | private func contains(_ trait: DownFontDescriptor.SymbolicTraits) -> Bool { 67 | return fontDescriptor.symbolicTraits.contains(trait) 68 | } 69 | 70 | } 71 | 72 | #if canImport(UIKit) 73 | 74 | private extension DownFontDescriptor.SymbolicTraits { 75 | 76 | static let strong = DownFontDescriptor.SymbolicTraits.traitBold 77 | static let emphasis = DownFontDescriptor.SymbolicTraits.traitItalic 78 | static let monoSpace = DownFontDescriptor.SymbolicTraits.traitMonoSpace 79 | 80 | } 81 | 82 | #elseif canImport(AppKit) 83 | 84 | private extension DownFontDescriptor.SymbolicTraits { 85 | 86 | static let strong = DownFontDescriptor.SymbolicTraits.bold 87 | static let emphasis = DownFontDescriptor.SymbolicTraits.italic 88 | static let monoSpace = DownFontDescriptor.SymbolicTraits.monoSpace 89 | 90 | } 91 | 92 | #endif 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Helpers/ListItemParagraphStyler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParagraphStyler.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 25.06.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | /// A convenient class used to format lists, such that list item prefixes 22 | /// are right aligned and list item content left aligns. 23 | 24 | public class ListItemParagraphStyler { 25 | 26 | // MARK: - Properties 27 | 28 | public var indentation: CGFloat { 29 | return largestPrefixWidth + options.spacingAfterPrefix 30 | } 31 | 32 | /// The paragraph style intended for all paragraphs excluding the first. 33 | 34 | public var trailingParagraphStyle: NSParagraphStyle { 35 | let contentIndentation = indentation 36 | let style = baseStyle 37 | style.firstLineHeadIndent = contentIndentation 38 | style.headIndent = contentIndentation 39 | return style 40 | } 41 | 42 | private let options: ListItemOptions 43 | private let largestPrefixWidth: CGFloat 44 | 45 | private var baseStyle: NSMutableParagraphStyle { 46 | let style = NSMutableParagraphStyle() 47 | style.paragraphSpacingBefore = options.spacingAbove 48 | style.paragraphSpacing = options.spacingBelow 49 | style.alignment = options.alignment 50 | return style 51 | } 52 | 53 | // MARK: - Life cycle 54 | 55 | public init(options: ListItemOptions, prefixFont: DownFont) { 56 | self.options = options 57 | self.largestPrefixWidth = prefixFont.widthOfNumberedPrefix(digits: options.maxPrefixDigits) 58 | } 59 | 60 | // MARK: - Methods 61 | 62 | /// The paragraph style intended for the first paragraph of the list item. 63 | /// 64 | /// - Parameter prefixWidth: the width (in points) of the list item prefix. 65 | 66 | public func leadingParagraphStyle(prefixWidth: CGFloat) -> NSParagraphStyle { 67 | let contentIndentation = indentation 68 | let prefixIndentation: CGFloat = contentIndentation - options.spacingAfterPrefix - prefixWidth 69 | let prefixSpill = max(0, prefixWidth - largestPrefixWidth) 70 | let firstLineContentIndentation = contentIndentation + prefixSpill 71 | 72 | let style = baseStyle 73 | style.firstLineHeadIndent = prefixIndentation 74 | style.tabStops = [tabStop(at: firstLineContentIndentation)] 75 | style.headIndent = contentIndentation 76 | return style 77 | } 78 | 79 | private func tabStop(at location: CGFloat) -> NSTextTab { 80 | return NSTextTab(textAlignment: options.alignment, location: location, options: [:]) 81 | } 82 | 83 | } 84 | 85 | // MARK: - Helpers 86 | 87 | private extension DownFont { 88 | 89 | func widthOfNumberedPrefix(digits: UInt) -> CGFloat { 90 | return widthOfLargestDigit * CGFloat(digits) + widthOfPeriod 91 | } 92 | 93 | private var widthOfLargestDigit: CGFloat { 94 | return Int.decimalDigits 95 | .map { NSAttributedString(string: "\($0)", attributes: [.font: self]).size().width } 96 | .max()! 97 | } 98 | 99 | private var widthOfPeriod: CGFloat { 100 | return NSAttributedString(string: ".", attributes: [.font: self]) 101 | .size() 102 | .width 103 | } 104 | 105 | } 106 | 107 | private extension Int { 108 | 109 | static var decimalDigits: [Int] { 110 | return Array(0...9) 111 | } 112 | 113 | } 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Layout Managers/DownDebugLayoutManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownDebugLayoutManager.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 06.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | /// A layout manager that draws the line fragments. 22 | /// 23 | /// Line fragments are the areas with a document that contain lines of text. There 24 | /// are two types. 25 | /// 26 | /// 1. A *line rect* (drawn in red) indicates the maximum rect enclosing the line. 27 | /// This inlcudes not only the textual content, but also the padding (if any) around that text. 28 | /// 2. A *line used rect* (drawn in blue) is the smallest rect enclosing the textual content. 29 | /// 30 | /// The visualization of these rects is useful when determining the paragraph styles 31 | /// of a `DownStyler`. 32 | /// 33 | /// Insert this into a TextKit stack manually, or use the provided `DownDebugTextView`. 34 | 35 | public class DownDebugLayoutManager: DownLayoutManager { 36 | 37 | // MARK: - Drawing 38 | 39 | override public func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) { 40 | super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin) 41 | drawLineFragments(forGlyphRange: glyphsToShow, at: origin) 42 | } 43 | 44 | private func drawLineFragments(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) { 45 | enumerateLineFragments(forGlyphRange: glyphsToShow) { rect, usedRect, _, _, _ in 46 | [(usedRect, DownColor.blue), (rect, DownColor.red)].forEach { rectToDraw, color in 47 | let adjustedRect = rectToDraw.translated(by: origin) 48 | self.drawRect(adjustedRect, color: color.cgColor) 49 | } 50 | } 51 | } 52 | 53 | private func drawRect(_ rect: CGRect, color: CGColor) { 54 | guard let context = context else { return } 55 | push(context: context) 56 | defer { popContext() } 57 | 58 | context.setStrokeColor(color) 59 | context.stroke(rect) 60 | } 61 | 62 | } 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Options/CodeBlockOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeBlockOptions.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 12.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | public struct CodeBlockOptions { 22 | 23 | // MARK: - Properties 24 | 25 | public var containerInset: CGFloat 26 | 27 | // MARK: - Life cycle 28 | 29 | public init(containerInset: CGFloat = 8) { 30 | self.containerInset = containerInset 31 | } 32 | 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Options/ListItemOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListItemOptions.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 04.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | public struct ListItemOptions { 22 | 23 | // MARK: - Properties 24 | 25 | public var maxPrefixDigits: UInt 26 | public var spacingAfterPrefix: CGFloat 27 | public var spacingAbove: CGFloat 28 | public var spacingBelow: CGFloat 29 | public var alignment: NSTextAlignment 30 | 31 | // MARK: - Life cycle 32 | 33 | public init(maxPrefixDigits: UInt = 2, 34 | spacingAfterPrefix: CGFloat = 8, 35 | spacingAbove: CGFloat = 4, 36 | spacingBelow: CGFloat = 8, 37 | alignment: NSTextAlignment = .natural) { 38 | 39 | self.maxPrefixDigits = maxPrefixDigits 40 | self.spacingAfterPrefix = spacingAfterPrefix 41 | self.spacingAbove = spacingAbove 42 | self.spacingBelow = spacingBelow 43 | self.alignment = alignment 44 | } 45 | 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Options/QuoteStripeOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuoteStripeOptions.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 04.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | public struct QuoteStripeOptions { 22 | 23 | // MARK: - Properties 24 | 25 | public var thickness: CGFloat 26 | public var spacingAfter: CGFloat 27 | 28 | // MARK: - Life cycle 29 | 30 | public init(thickness: CGFloat = 2, spacingAfter: CGFloat = 8) { 31 | self.thickness = thickness 32 | self.spacingAfter = spacingAfter 33 | } 34 | 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Options/ThematicBreakOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThematicBreakOptions.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 04.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | public struct ThematicBreakOptions { 22 | 23 | // MARK: - Properties 24 | 25 | public var thickness: CGFloat 26 | public var indentation: CGFloat 27 | 28 | // MARK: - Life cycle 29 | 30 | public init(thickness: CGFloat = 1, indentation: CGFloat = 0) { 31 | self.thickness = thickness 32 | self.indentation = indentation 33 | } 34 | 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Stylers/DownStylerConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownStylerConfiguration.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 10.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | /// A configuration object used to initialze the `DownStyler`. 12 | 13 | public struct DownStylerConfiguration { 14 | 15 | // MARK: - Properties 16 | 17 | public var fonts: FontCollection 18 | public var colors: ColorCollection 19 | public var paragraphStyles: ParagraphStyleCollection 20 | 21 | public var listItemOptions: ListItemOptions 22 | public var quoteStripeOptions: QuoteStripeOptions 23 | public var thematicBreakOptions: ThematicBreakOptions 24 | public var codeBlockOptions: CodeBlockOptions 25 | 26 | // MARK: - Life cycle 27 | 28 | public init(fonts: FontCollection = StaticFontCollection(), 29 | colors: ColorCollection = StaticColorCollection(), 30 | paragraphStyles: ParagraphStyleCollection = StaticParagraphStyleCollection(), 31 | listItemOptions: ListItemOptions = ListItemOptions(), 32 | quoteStripeOptions: QuoteStripeOptions = QuoteStripeOptions(), 33 | thematicBreakOptions: ThematicBreakOptions = ThematicBreakOptions(), 34 | codeBlockOptions: CodeBlockOptions = CodeBlockOptions() 35 | ) { 36 | self.fonts = fonts 37 | self.colors = colors 38 | self.paragraphStyles = paragraphStyles 39 | self.listItemOptions = listItemOptions 40 | self.quoteStripeOptions = quoteStripeOptions 41 | self.thematicBreakOptions = thematicBreakOptions 42 | self.codeBlockOptions = codeBlockOptions 43 | } 44 | 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Text Views/DownDebugTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownDebugTextView.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 06.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | 15 | #elseif canImport(AppKit) 16 | 17 | import AppKit 18 | 19 | #endif 20 | 21 | /// A text view capable of parsing and rendering markdown via the AST, as well as line fragments. 22 | /// 23 | /// See `DownDebugLayoutManager`. 24 | 25 | public class DownDebugTextView: DownTextView { 26 | 27 | // MARK: - Life cycle 28 | 29 | public init(frame: CGRect, styler: Styler = DownStyler()) { 30 | super.init(frame: frame, styler: styler, layoutManager: DownDebugLayoutManager()) 31 | } 32 | 33 | required public init?(coder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /Sources/Down/AST/Styling/Text Views/DownTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownTextView.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 03.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(watchOS) && !os(Linux) 10 | 11 | #if canImport(UIKit) 12 | 13 | import UIKit 14 | public typealias TextView = UITextView 15 | 16 | #elseif canImport(AppKit) 17 | 18 | import AppKit 19 | public typealias TextView = NSTextView 20 | 21 | #endif 22 | 23 | /// A text view capable of parsing and rendering markdown via the AST. 24 | 25 | open class DownTextView: TextView { 26 | 27 | // MARK: - Properties 28 | 29 | open var styler: Styler { 30 | didSet { 31 | try? render() 32 | } 33 | } 34 | 35 | #if canImport(UIKit) 36 | 37 | open override var text: String! { 38 | didSet { 39 | guard oldValue != text else { return } 40 | try? render() 41 | } 42 | } 43 | 44 | #elseif canImport(AppKit) 45 | 46 | open override var string: String { 47 | didSet { 48 | guard oldValue != string else { return } 49 | try? render() 50 | } 51 | } 52 | 53 | #endif 54 | 55 | // MARK: - Life cycle 56 | 57 | public convenience init(frame: CGRect, styler: Styler = DownStyler()) { 58 | self.init(frame: frame, styler: styler, layoutManager: DownLayoutManager()) 59 | } 60 | 61 | public init(frame: CGRect, styler: Styler, layoutManager: NSLayoutManager) { 62 | self.styler = styler 63 | 64 | let textStorage = NSTextStorage() 65 | let textContainer = NSTextContainer() 66 | 67 | textStorage.addLayoutManager(layoutManager) 68 | layoutManager.addTextContainer(textContainer) 69 | 70 | super.init(frame: frame, textContainer: textContainer) 71 | 72 | // We don't want the text view to overwrite link attributes set 73 | // by the styler. 74 | linkTextAttributes = [:] 75 | } 76 | 77 | required public init?(coder: NSCoder) { 78 | fatalError("init(coder:) has not been implemented") 79 | } 80 | 81 | // MARK: - Methods 82 | 83 | open func render() throws { 84 | #if canImport(UIKit) 85 | let down = Down(markdownString: text) 86 | let markdown = try down.toAttributedString(styler: styler) 87 | attributedText = markdown 88 | 89 | #elseif canImport(AppKit) 90 | guard let textStorage = textStorage else { return } 91 | let down = Down(markdownString: string) 92 | let markdown = try down.toAttributedString(styler: styler) 93 | textStorage.replaceCharacters(in: textStorage.wholeRange, with: markdown) 94 | 95 | #endif 96 | } 97 | 98 | } 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /Sources/Down/AST/Visitors/DebugVisitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebugVisitor.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 09.04.19. 6 | // 7 | 8 | import Foundation 9 | 10 | /// This visitor will generate the debug description of an entire abstract syntax tree, 11 | /// indicating relationships between nodes with indentation. 12 | 13 | public class DebugVisitor: Visitor { 14 | 15 | // MARK: - Properties 16 | 17 | private var depth = 0 18 | 19 | private var indent: String { 20 | return String(repeating: " ", count: depth) 21 | } 22 | 23 | // MARK: - Life cycle 24 | 25 | public init() {} 26 | 27 | // MARK: - Helpers 28 | 29 | private func report(_ node: Node) -> String { 30 | return "\(indent)\(node is Document ? "" : "↳ ")\(String(reflecting: node))\n" 31 | } 32 | 33 | private func reportWithChildren(_ node: Node) -> String { 34 | let thisNode = report(node) 35 | depth += 1 36 | let children = visitChildren(of: node).joined() 37 | depth -= 1 38 | return "\(thisNode)\(children)" 39 | } 40 | 41 | // MARK: - Visitor 42 | 43 | public typealias Result = String 44 | 45 | public func visit(document node: Document) -> String { 46 | return reportWithChildren(node) 47 | } 48 | 49 | public func visit(blockQuote node: BlockQuote) -> String { 50 | return reportWithChildren(node) 51 | } 52 | 53 | public func visit(list node: List) -> String { 54 | return reportWithChildren(node) 55 | } 56 | 57 | public func visit(item node: Item) -> String { 58 | return reportWithChildren(node) 59 | } 60 | 61 | public func visit(codeBlock node: CodeBlock) -> String { 62 | return reportWithChildren(node) 63 | } 64 | 65 | public func visit(htmlBlock node: HtmlBlock) -> String { 66 | return reportWithChildren(node) 67 | } 68 | 69 | public func visit(customBlock node: CustomBlock) -> String { 70 | return reportWithChildren(node) 71 | } 72 | 73 | public func visit(paragraph node: Paragraph) -> String { 74 | return reportWithChildren(node) 75 | } 76 | 77 | public func visit(heading node: Heading) -> String { 78 | return reportWithChildren(node) 79 | } 80 | 81 | public func visit(thematicBreak node: ThematicBreak) -> String { 82 | return report(node) 83 | } 84 | 85 | public func visit(text node: Text) -> String { 86 | return report(node) 87 | } 88 | 89 | public func visit(softBreak node: SoftBreak) -> String { 90 | return report(node) 91 | } 92 | 93 | public func visit(lineBreak node: LineBreak) -> String { 94 | return report(node) 95 | } 96 | 97 | public func visit(code node: Code) -> String { 98 | return report(node) 99 | } 100 | 101 | public func visit(htmlInline node: HtmlInline) -> String { 102 | return report(node) 103 | } 104 | 105 | public func visit(customInline node: CustomInline) -> String { 106 | return report(node) 107 | } 108 | 109 | public func visit(emphasis node: Emphasis) -> String { 110 | return reportWithChildren(node) 111 | } 112 | 113 | public func visit(strong node: Strong) -> String { 114 | return reportWithChildren(node) 115 | } 116 | 117 | public func visit(link node: Link) -> String { 118 | return reportWithChildren(node) 119 | } 120 | 121 | public func visit(image node: Image) -> String { 122 | return reportWithChildren(node) 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /Sources/Down/AST/Visitors/ListItemPrefixGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListItemPrefixGenerator.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 23.06.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A ListItemPrefixGenerator is an object used to generate list item prefix. 12 | public protocol ListItemPrefixGenerator { 13 | init(listType: List.ListType, numberOfItems: Int, nestDepth: Int) 14 | func next() -> String? 15 | } 16 | 17 | public extension ListItemPrefixGenerator { 18 | init(list: List) { 19 | self.init(listType: list.listType, numberOfItems: list.numberOfItems, nestDepth: list.nestDepth) 20 | } 21 | } 22 | 23 | /// Default implementation of `ListItemPrefixGenerator`. 24 | /// Generating the following symbol based on `List.ListType`: 25 | /// - List.ListType is bullet => "•" 26 | /// - List.ListType is ordered => "X." (where is the item number) 27 | public class StaticListItemPrefixGenerator: ListItemPrefixGenerator { 28 | 29 | // MARK: - Properties 30 | 31 | private var prefixes: IndexingIterator<[String]> 32 | 33 | // MARK: - Life cycle 34 | 35 | required public init(listType: List.ListType, numberOfItems: Int, nestDepth: Int) { 36 | switch listType { 37 | case .bullet: 38 | prefixes = [String](repeating: "•", count: numberOfItems) 39 | .makeIterator() 40 | 41 | case .ordered(let start): 42 | prefixes = (start..<(start + numberOfItems)) 43 | .map { "\($0)." } 44 | .makeIterator() 45 | } 46 | } 47 | 48 | // MARK: - Methods 49 | 50 | public func next() -> String? { 51 | prefixes.next() 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Down/AST/Visitors/Visitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Vistor.swift 3 | // Down 4 | // 5 | // Created by John Nguyen on 07.04.19. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Visitor describes a type that is able to traverse the abstract syntax tree. It visits 11 | /// each node of the tree and produces some result for that node. A visitor is "accepted" by 12 | /// the root node (of type `Document`), which will start the traversal by first invoking 13 | /// `visit(document:)`. 14 | 15 | public protocol Visitor { 16 | 17 | associatedtype Result 18 | 19 | func visit(document node: Document) -> Result 20 | func visit(blockQuote node: BlockQuote) -> Result 21 | func visit(list node: List) -> Result 22 | func visit(item node: Item) -> Result 23 | func visit(codeBlock node: CodeBlock) -> Result 24 | func visit(htmlBlock node: HtmlBlock) -> Result 25 | func visit(customBlock node: CustomBlock) -> Result 26 | func visit(paragraph node: Paragraph) -> Result 27 | func visit(heading node: Heading) -> Result 28 | func visit(thematicBreak node: ThematicBreak) -> Result 29 | func visit(text node: Text) -> Result 30 | func visit(softBreak node: SoftBreak) -> Result 31 | func visit(lineBreak node: LineBreak) -> Result 32 | func visit(code node: Code) -> Result 33 | func visit(htmlInline node: HtmlInline) -> Result 34 | func visit(customInline node: CustomInline) -> Result 35 | func visit(emphasis node: Emphasis) -> Result 36 | func visit(strong node: Strong) -> Result 37 | func visit(link node: Link) -> Result 38 | func visit(image node: Image) -> Result 39 | func visitChildren(of node: Node) -> [Result] 40 | 41 | } 42 | 43 | extension Visitor { 44 | 45 | public func visitChildren(of node: Node) -> [Result] { 46 | return node.childSequence.compactMap { child in 47 | switch child { 48 | case let child as Document: return visit(document: child) 49 | case let child as BlockQuote: return visit(blockQuote: child) 50 | case let child as List: return visit(list: child) 51 | case let child as Item: return visit(item: child) 52 | case let child as CodeBlock: return visit(codeBlock: child) 53 | case let child as HtmlBlock: return visit(htmlBlock: child) 54 | case let child as CustomBlock: return visit(customBlock: child) 55 | case let child as Paragraph: return visit(paragraph: child) 56 | case let child as Heading: return visit(heading: child) 57 | case let child as ThematicBreak: return visit(thematicBreak: child) 58 | case let child as Text: return visit(text: child) 59 | case let child as SoftBreak: return visit(softBreak: child) 60 | case let child as LineBreak: return visit(lineBreak: child) 61 | case let child as Code: return visit(code: child) 62 | case let child as HtmlInline: return visit(htmlInline: child) 63 | case let child as CustomInline: return visit(customInline: child) 64 | case let child as Emphasis: return visit(emphasis: child) 65 | case let child as Strong: return visit(strong: child) 66 | case let child as Link: return visit(link: child) 67 | case let child as Image: return visit(image: child) 68 | default: 69 | assertionFailure("Unexpected child") 70 | return nil 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Sources/Down/Down.h: -------------------------------------------------------------------------------- 1 | // 2 | // Down.h 3 | // Down 4 | // 5 | // Created by Rob Phillips on 6/1/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | //! Project version number for Down. 12 | FOUNDATION_EXPORT double DownVersionNumber; 13 | 14 | //! Project version string for Down. 15 | FOUNDATION_EXPORT const unsigned char DownVersionString[]; 16 | -------------------------------------------------------------------------------- /Sources/Down/Down.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Down.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/28/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Down: DownASTRenderable, DownHTMLRenderable, DownXMLRenderable, 12 | DownLaTeXRenderable, DownGroffRenderable, DownCommonMarkRenderable { 13 | /// A string containing CommonMark Markdown 14 | public var markdownString: String 15 | 16 | /// Initializes the container with a CommonMark Markdown string which can then be 17 | /// rendered depending on protocol conformance. 18 | /// 19 | /// - Parameter markdownString: A string containing CommonMark Markdown 20 | public init(markdownString: String) { 21 | self.markdownString = markdownString 22 | } 23 | } 24 | 25 | #if !os(Linux) 26 | extension Down: DownAttributedStringRenderable { } 27 | #endif // !os(Linux) 28 | -------------------------------------------------------------------------------- /Sources/Down/Enums & Options/DownErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownErrors.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/28/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum DownErrors: Error { 12 | 13 | /// Thrown when there was an issue converting the Markdown into an abstract syntax tree. 14 | 15 | case markdownToASTError 16 | 17 | /// Thrown when the abstract syntax tree could not be rendered into another format. 18 | 19 | case astRenderingError 20 | 21 | /// Thrown when an HTML string cannot be converted into an `NSData` representation. 22 | 23 | case htmlDataConversionError 24 | 25 | #if os(macOS) 26 | 27 | /// Thrown when a custom template bundle has a non-standard bundle format. 28 | /// 29 | /// Specifically, the file URL of the bundle’s subdirectory containing resource files could 30 | /// not be found (i.e. the bundle's `resourceURL` property is nil). 31 | 32 | case nonStandardBundleFormatError 33 | 34 | #endif 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Down/Enums & Options/DownOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownOptions.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/28/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public struct DownOptions: OptionSet { 13 | 14 | // MARK: - Properties 15 | 16 | public let rawValue: Int32 17 | 18 | // MARK: - Life cycle 19 | 20 | public init(rawValue: Int32) { self.rawValue = rawValue } 21 | 22 | /// Default options. 23 | 24 | public static let `default` = DownOptions(rawValue: CMARK_OPT_DEFAULT) 25 | 26 | // MARK: - Rendering Options 27 | 28 | /// Include a `data-sourcepos` attribute on all block elements. 29 | 30 | public static let sourcePos = DownOptions(rawValue: CMARK_OPT_SOURCEPOS) 31 | 32 | /// Render `softbreak` elements as hard line breaks. 33 | 34 | public static let hardBreaks = DownOptions(rawValue: CMARK_OPT_HARDBREAKS) 35 | 36 | /// Suppress raw HTML and unsafe links (`javascript:`, `vbscript:`, 37 | /// `file:`, and `data:`, except for `image/png`, `image/gif`, 38 | /// `image/jpeg`, or `image/webp` mime types). Raw HTML is replaced 39 | /// by a placeholder HTML comment. Unsafe links are replaced by 40 | /// empty strings. 41 | /// 42 | /// Note: this is the default option as of cmark v0.29.0. Use `unsafe` 43 | /// to disable this behavior. 44 | 45 | public static let safe = DownOptions(rawValue: CMARK_OPT_SAFE) 46 | 47 | /// Render raw HTML and unsafe links (`javascript:`, `vbscript:`, 48 | /// `file:`, and `data:`, except for `image/png`, `image/gif`, 49 | /// `image/jpeg`, or `image/webp` mime types). By default, 50 | /// raw HTML is replaced by a placeholder HTML comment. Unsafe 51 | /// links are replaced by empty strings. 52 | /// 53 | /// Note: `safe` is the default as of cmark v0.29.0 54 | 55 | public static let unsafe = DownOptions(rawValue: CMARK_OPT_UNSAFE) 56 | 57 | // MARK: - Parsing Options 58 | 59 | /// Normalize tree by consolidating adjacent text nodes. 60 | 61 | public static let normalize = DownOptions(rawValue: CMARK_OPT_NORMALIZE) 62 | 63 | /// Validate UTF-8 in the input before parsing, replacing illegal 64 | /// sequences with the replacement character U+FFFD. 65 | 66 | public static let validateUTF8 = DownOptions(rawValue: CMARK_OPT_VALIDATE_UTF8) 67 | 68 | /// Convert straight quotes to curly, --- to em dashes, -- to en dashes. 69 | 70 | public static let smart = DownOptions(rawValue: CMARK_OPT_SMART) 71 | 72 | // MARK: - Combo Options 73 | 74 | /// Combines 'unsafe' and 'smart' to render raw HTML and produce smart typography. 75 | 76 | public static let smartUnsafe = DownOptions(rawValue: CMARK_OPT_SMART + CMARK_OPT_UNSAFE) 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Sources/Down/Extensions/NSAttributedString+HTML.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+HTML.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 6/1/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(Linux) 10 | 11 | #if os(macOS) 12 | 13 | import AppKit 14 | 15 | #else 16 | 17 | import UIKit 18 | 19 | #endif 20 | 21 | extension NSAttributedString { 22 | 23 | /// Instantiates an attributed string with the given HTML string 24 | /// 25 | /// - Parameters: 26 | /// - htmlString: An HTML string. 27 | /// 28 | /// - Throws: 29 | /// `HTMLDataConversionError` or an instantiation error. 30 | 31 | convenience init(htmlString: String) throws { 32 | guard let data = htmlString.data(using: String.Encoding.utf8) else { 33 | throw DownErrors.htmlDataConversionError 34 | } 35 | 36 | let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [ 37 | .documentType: NSAttributedString.DocumentType.html, 38 | .characterEncoding: NSNumber(value: String.Encoding.utf8.rawValue) 39 | ] 40 | 41 | try self.init(data: data, options: options, documentAttributes: nil) 42 | } 43 | 44 | } 45 | 46 | #endif // !os(Linux) 47 | -------------------------------------------------------------------------------- /Sources/Down/Extensions/String+ToHTML.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+ToHTML.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 6/1/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | extension String { 13 | 14 | /// Generates an HTML string from the contents of the string (self), which should contain CommonMark Markdown. 15 | /// 16 | /// - Parameters: 17 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 18 | /// - Returns: 19 | /// An HTML string. 20 | /// 21 | /// - Throws: 22 | /// `DownErrors` depending on the scenario. 23 | 24 | public func toHTML(_ options: DownOptions = .default) throws -> String { 25 | let ast = try DownASTRenderer.stringToAST(self, options: options) 26 | let html = try DownHTMLRenderer.astToHTML(ast, options: options) 27 | cmark_node_free(ast) 28 | return html 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownASTRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownASTRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/31/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public protocol DownASTRenderable: DownRenderable { 13 | 14 | func toAST(_ options: DownOptions) throws -> CMarkNode 15 | 16 | } 17 | 18 | extension DownASTRenderable { 19 | 20 | /// Generates an abstract syntax tree from the `markdownString` property. 21 | /// 22 | /// - Parametera: 23 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 24 | /// 25 | /// - Returns: 26 | /// An abstract syntax tree representation of the Markdown input. 27 | /// 28 | /// - Throws: 29 | /// `MarkdownToASTError` if conversion fails. 30 | 31 | public func toAST(_ options: DownOptions = .default) throws -> CMarkNode { 32 | return try DownASTRenderer.stringToAST(markdownString, options: options) 33 | } 34 | 35 | /// Parses the `markdownString` property into an abstract syntax tree and returns the root `Document` node. 36 | /// 37 | /// - Parameters: 38 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 39 | /// 40 | /// - Returns: 41 | /// The root Document node for the abstract syntax tree representation of the Markdown input. 42 | /// 43 | /// - Throws: 44 | /// `MarkdownToASTError` if conversion fails. 45 | 46 | public func toDocument(_ options: DownOptions = .default) throws -> Document { 47 | let tree = try toAST(options) 48 | 49 | guard tree.type == CMARK_NODE_DOCUMENT else { 50 | throw DownErrors.astRenderingError 51 | } 52 | 53 | return Document(cmarkNode: tree) 54 | } 55 | 56 | } 57 | 58 | public struct DownASTRenderer { 59 | 60 | /// Generates an abstract syntax tree from the given CommonMark Markdown string. 61 | /// 62 | /// **Important:** It is the caller's responsibility to call `cmark_node_free(ast)` on the returned value. 63 | /// 64 | /// - Parameters: 65 | /// - string: A string containing CommonMark Markdown. 66 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 67 | /// 68 | /// - Returns: 69 | /// An abstract syntax tree representation of the Markdown input. 70 | /// 71 | /// - Throws: 72 | /// `MarkdownToASTError` if conversion fails. 73 | public static func stringToAST(_ string: String, options: DownOptions = .default) throws -> CMarkNode { 74 | var tree: CMarkNode? 75 | 76 | string.withCString { 77 | let stringLength = Int(strlen($0)) 78 | tree = cmark_parse_document($0, stringLength, options.rawValue) 79 | } 80 | 81 | guard let ast = tree else { 82 | throw DownErrors.markdownToASTError 83 | } 84 | 85 | return ast 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownAttributedStringRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownAttributedStringRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 6/1/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if !os(Linux) 10 | 11 | import Foundation 12 | import libcmark 13 | 14 | public protocol DownAttributedStringRenderable: DownHTMLRenderable, DownASTRenderable { 15 | 16 | func toAttributedString(_ options: DownOptions, stylesheet: String?) throws -> NSAttributedString 17 | func toAttributedString(_ options: DownOptions, styler: Styler) throws -> NSAttributedString 18 | 19 | } 20 | 21 | extension DownAttributedStringRenderable { 22 | 23 | /// Generates an `NSAttributedString` from the `markdownString` property. 24 | /// 25 | /// **Note:** The attributed string is constructed and rendered via WebKit from html generated from the 26 | /// abstract syntax tree. This process is not background safe and must be executed on the main 27 | /// thread. Additionally, it may be slow to render. For an efficient background safe render, 28 | /// use the `toAttributedString(options: styler:)` method below. 29 | /// 30 | /// - Parameters: 31 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 32 | /// - stylesheet: a `String` to use as the CSS stylesheet when rendering, defaulting 33 | /// to a style that uses the `NSAttributedString` default font. 34 | /// 35 | /// - Returns: 36 | /// An `NSAttributedString`. 37 | /// - Throws: 38 | /// `DownErrors` depending on the scenario. 39 | 40 | public func toAttributedString(_ options: DownOptions = .default, 41 | stylesheet: String? = nil) throws -> NSAttributedString { 42 | 43 | let html = try self.toHTML(options) 44 | let defaultStylesheet = "* {font-family: Helvetica } code, pre { font-family: Menlo }" 45 | return try NSAttributedString(htmlString: "<style>" + (stylesheet ?? defaultStylesheet) + "</style>" + html) 46 | } 47 | 48 | /// Generates an `NSAttributedString` from the `markdownString` property. 49 | /// 50 | /// **Note:** The attributed string is constructed directly by traversing the abstract syntax tree. It is 51 | /// much faster than the `toAttributedString(options: stylesheet)` method and it can be also be 52 | /// rendered in a background thread. 53 | /// 54 | /// - Parameters: 55 | /// - options: `DownOptions` to modify parsing or rendering. 56 | /// - styler: a class/struct conforming to `Styler` to use when rendering the various 57 | /// elements of the attributed string 58 | /// 59 | /// - Returns: 60 | /// An `NSAttributedString`. 61 | /// 62 | /// - Throws: 63 | /// `DownErrors` depending on the scenario. 64 | 65 | public func toAttributedString(_ options: DownOptions = .default, styler: Styler) throws -> NSAttributedString { 66 | let document = try self.toDocument(options) 67 | let visitor = AttributedStringVisitor(styler: styler, options: options) 68 | return document.accept(visitor) 69 | } 70 | 71 | } 72 | 73 | #endif // !os(Linux) 74 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownCommonMarkRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownCommonMarkRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/31/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public protocol DownCommonMarkRenderable: DownRenderable { 13 | 14 | func toCommonMark(_ options: DownOptions, width: Int32) throws -> String 15 | 16 | } 17 | 18 | extension DownCommonMarkRenderable { 19 | 20 | /// Generates a CommonMark Markdown string from the `markdownString` property. 21 | /// 22 | /// - Parameters: 23 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 24 | /// - width: The width to break on, defaulting to 0. 25 | /// 26 | /// - Returns: 27 | /// A CommonMark Markdown string. 28 | /// 29 | /// - Throws: 30 | /// `DownErrors` depending on the scenario. 31 | 32 | public func toCommonMark(_ options: DownOptions = .default, width: Int32 = 0) throws -> String { 33 | let ast = try DownASTRenderer.stringToAST(markdownString, options: options) 34 | let commonMark = try DownCommonMarkRenderer.astToCommonMark(ast, options: options, width: width) 35 | cmark_node_free(ast) 36 | return commonMark 37 | } 38 | 39 | } 40 | 41 | public struct DownCommonMarkRenderer { 42 | 43 | /// Generates a CommonMark Markdown string from the given abstract syntax tree. 44 | /// 45 | /// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns. 46 | /// 47 | /// - Parameters: 48 | /// - ast: The `cmark_node` representing the abstract syntax tree. 49 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 50 | /// - width: The width to break on, defaulting to 0. 51 | /// 52 | /// - Returns: 53 | /// A CommonMark Markdown string. 54 | /// 55 | /// - Throws: 56 | /// `ASTRenderingError` if the AST could not be converted. 57 | 58 | public static func astToCommonMark(_ ast: CMarkNode, 59 | options: DownOptions = .default, 60 | width: Int32 = 0) throws -> String { 61 | 62 | guard let cCommonMarkString = cmark_render_commonmark(ast, options.rawValue, width) else { 63 | throw DownErrors.astRenderingError 64 | } 65 | 66 | defer { 67 | free(cCommonMarkString) 68 | } 69 | 70 | guard let commonMarkString = String(cString: cCommonMarkString, encoding: String.Encoding.utf8) else { 71 | throw DownErrors.astRenderingError 72 | } 73 | 74 | return commonMarkString 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownGroffRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownGroffRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/31/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public protocol DownGroffRenderable: DownRenderable { 13 | 14 | func toGroff(_ options: DownOptions, width: Int32) throws -> String 15 | 16 | } 17 | 18 | extension DownGroffRenderable { 19 | 20 | /// Generates a groff man string from the `markdownString` property. 21 | /// 22 | /// - Parameters: 23 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 24 | /// - width: The width to break on, defaulting to 0. 25 | /// 26 | /// - Returns: 27 | /// A groff man string. 28 | /// 29 | /// - Throws: 30 | /// `DownErrors` depending on the scenario. 31 | 32 | public func toGroff(_ options: DownOptions = .default, width: Int32 = 0) throws -> String { 33 | let ast = try DownASTRenderer.stringToAST(markdownString, options: options) 34 | let groff = try DownGroffRenderer.astToGroff(ast, options: options, width: width) 35 | cmark_node_free(ast) 36 | return groff 37 | } 38 | 39 | } 40 | 41 | public struct DownGroffRenderer { 42 | 43 | /// Generates a groff man string from the given abstract syntax tree. 44 | /// 45 | /// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns. 46 | /// 47 | /// - Parameters: 48 | /// - ast: The `cmark_node` representing the abstract syntax tree. 49 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 50 | /// - width: The width to break on, defaulting to 0. 51 | /// 52 | /// - Returns: 53 | /// A groff man string. 54 | /// 55 | /// - Throws: 56 | /// `ASTRenderingError` if the AST could not be converted. 57 | 58 | public static func astToGroff(_ ast: CMarkNode, 59 | options: DownOptions = .default, 60 | width: Int32 = 0) throws -> String { 61 | 62 | guard let cGroffString = cmark_render_man(ast, options.rawValue, width) else { 63 | throw DownErrors.astRenderingError 64 | } 65 | 66 | defer { 67 | free(cGroffString) 68 | } 69 | 70 | guard let groffString = String(cString: cGroffString, encoding: String.Encoding.utf8) else { 71 | throw DownErrors.astRenderingError 72 | } 73 | 74 | return groffString 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownHTMLRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownHTMLRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/28/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public protocol DownHTMLRenderable: DownRenderable { 13 | 14 | func toHTML(_ options: DownOptions) throws -> String 15 | 16 | } 17 | 18 | extension DownHTMLRenderable { 19 | 20 | /// Generates an HTML string from the `markdownString` property. 21 | /// 22 | /// - Parameters: 23 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 24 | /// 25 | /// - Returns: 26 | /// An HTML string. 27 | /// 28 | /// - Throws: 29 | /// `DownErrors` depending on the scenario. 30 | 31 | public func toHTML(_ options: DownOptions = .default) throws -> String { 32 | return try markdownString.toHTML(options) 33 | } 34 | 35 | } 36 | 37 | public struct DownHTMLRenderer { 38 | 39 | /// Generates an HTML string from the given abstract syntax tree. 40 | /// 41 | /// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns. 42 | /// 43 | /// - Parameters: 44 | /// - ast: The `cmark_node` representing the abstract syntax tree. 45 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 46 | /// 47 | /// - Returns: 48 | /// An HTML string. 49 | /// 50 | /// - Throws: 51 | /// `ASTRenderingError` if the AST could not be converted. 52 | 53 | public static func astToHTML(_ ast: CMarkNode, options: DownOptions = .default) throws -> String { 54 | guard let cHTMLString = cmark_render_html(ast, options.rawValue) else { 55 | throw DownErrors.astRenderingError 56 | } 57 | 58 | defer { 59 | free(cHTMLString) 60 | } 61 | 62 | guard let htmlString = String(cString: cHTMLString, encoding: String.Encoding.utf8) else { 63 | throw DownErrors.astRenderingError 64 | } 65 | 66 | return htmlString 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownLaTeXRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownLaTeXRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/31/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public protocol DownLaTeXRenderable: DownRenderable { 13 | 14 | func toLaTeX(_ options: DownOptions, width: Int32) throws -> String 15 | 16 | } 17 | 18 | extension DownLaTeXRenderable { 19 | 20 | /// Generates a LaTeX string from the `markdownString` property. 21 | /// 22 | /// - Parameters: 23 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 24 | /// - width: The width to break on, defaulting to 0. 25 | /// 26 | /// - Returns: 27 | /// A LaTeX string. 28 | /// 29 | /// - Throws: 30 | /// `DownErrors` depending on the scenario. 31 | 32 | public func toLaTeX(_ options: DownOptions = .default, width: Int32 = 0) throws -> String { 33 | let ast = try DownASTRenderer.stringToAST(markdownString, options: options) 34 | let latex = try DownLaTeXRenderer.astToLaTeX(ast, options: options, width: width) 35 | cmark_node_free(ast) 36 | return latex 37 | } 38 | 39 | } 40 | 41 | public struct DownLaTeXRenderer { 42 | 43 | /// Generates a LaTeX string from the given abstract syntax tree. 44 | /// 45 | /// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns. 46 | /// 47 | /// - Parameters: 48 | /// - ast: The `cmark_node` representing the abstract syntax tree. 49 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 50 | /// - width: The width to break on, defaulting to 0. 51 | /// 52 | /// - Returns: 53 | /// A LaTeX string. 54 | /// 55 | /// - Throws: 56 | /// `ASTRenderingError` if the AST could not be converted. 57 | 58 | public static func astToLaTeX(_ ast: CMarkNode, 59 | options: DownOptions = .default, 60 | width: Int32 = 0) throws -> String { 61 | 62 | guard let cLatexString = cmark_render_latex(ast, options.rawValue, width) else { 63 | throw DownErrors.astRenderingError 64 | } 65 | 66 | defer { 67 | free(cLatexString) 68 | } 69 | 70 | guard let latexString = String(cString: cLatexString, encoding: String.Encoding.utf8) else { 71 | throw DownErrors.astRenderingError 72 | } 73 | 74 | return latexString 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/28/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol DownRenderable { 12 | 13 | /// A string containing CommonMark Markdown. 14 | 15 | var markdownString: String { get set } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Down/Renderers/DownXMLRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownXMLRenderable.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 5/31/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import libcmark 11 | 12 | public protocol DownXMLRenderable: DownRenderable { 13 | 14 | func toXML(_ options: DownOptions) throws -> String 15 | 16 | } 17 | 18 | extension DownXMLRenderable { 19 | 20 | /// Generates an XML string from the `markdownString` property. 21 | /// 22 | /// - Parameters: 23 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 24 | /// 25 | /// - Returns: 26 | /// An XML string. 27 | /// 28 | /// - Throws: 29 | /// `DownErrors` depending on the scenario. 30 | 31 | public func toXML(_ options: DownOptions = .default) throws -> String { 32 | let ast = try DownASTRenderer.stringToAST(markdownString, options: options) 33 | let xml = try DownXMLRenderer.astToXML(ast, options: options) 34 | cmark_node_free(ast) 35 | return xml 36 | } 37 | 38 | } 39 | 40 | public struct DownXMLRenderer { 41 | 42 | /// Generates an XML string from the given abstract syntax tree 43 | /// 44 | /// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns. 45 | /// 46 | /// - Parameters: 47 | /// - ast: The `cmark_node` representing the abstract syntax tree. 48 | /// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`. 49 | /// 50 | /// - Returns: 51 | /// An XML string. 52 | /// 53 | /// - Throws: 54 | /// `ASTRenderingError` if the AST could not be converted. 55 | 56 | public static func astToXML(_ ast: CMarkNode, options: DownOptions = .default) throws -> String { 57 | guard let cXMLString = cmark_render_xml(ast, options.rawValue) else { 58 | throw DownErrors.astRenderingError 59 | } 60 | 61 | defer { 62 | free(cXMLString) 63 | } 64 | 65 | guard let xmlString = String(cString: cXMLString, encoding: String.Encoding.utf8) else { 66 | throw DownErrors.astRenderingError 67 | } 68 | 69 | return xmlString 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Down/Resources/DownView (macOS).bundle/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <meta charset="utf-8"> 5 | <meta name="viewport" content="width=640"/> 6 | <link charset="utf-8" href="css/down.min.css" rel="stylesheet"> 7 | <script charset="utf-8" src="js/highlight.min.js" type="text/javascript"></script> 8 | <script charset="utf-8" src="js/down.js" type="text/javascript"></script> 9 | <title> 10 | 11 | 26 | 27 | DOWN_HTML 28 | 29 | 30 | -------------------------------------------------------------------------------- /Sources/Down/Resources/DownView (macOS).bundle/js/down.js: -------------------------------------------------------------------------------- 1 | hljs.initHighlightingOnLoad(); 2 | -------------------------------------------------------------------------------- /Sources/Down/Resources/DownView.bundle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | DOWN_HTML 13 | 14 | -------------------------------------------------------------------------------- /Sources/Down/Resources/DownView.bundle/js/down.js: -------------------------------------------------------------------------------- 1 | hljs.initHighlightingOnLoad(); -------------------------------------------------------------------------------- /Sources/Down/Views/BundleHelper.swift: -------------------------------------------------------------------------------- 1 | import class Foundation.Bundle 2 | 3 | // This helps us find the bundle when imported through the Swift Package Manager. 4 | 5 | private class BundleFinder {} 6 | 7 | extension Foundation.Bundle { 8 | 9 | /// Returns the resource bundle associated with the current Swift module. 10 | 11 | static var moduleBundle: Bundle? = { 12 | let bundleName = "Down_Down" 13 | 14 | let candidates = [ 15 | // Bundle should be present here when the package is linked into an App. 16 | Bundle.main.resourceURL, 17 | 18 | // Bundle should be present here when the package is linked into a framework. 19 | Bundle(for: BundleFinder.self).resourceURL, 20 | 21 | // For command-line tools. 22 | Bundle.main.bundleURL 23 | ] 24 | 25 | for candidate in candidates { 26 | let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle") 27 | if let bundle = bundlePath.flatMap(Bundle.init(url:)) { 28 | return bundle 29 | } 30 | } 31 | return nil 32 | }() 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/cmark/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_BUFFER_H 2 | #define CMARK_BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "config.h" 10 | #include "cmark.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | typedef int32_t bufsize_t; 17 | 18 | typedef struct { 19 | cmark_mem *mem; 20 | unsigned char *ptr; 21 | bufsize_t asize, size; 22 | } cmark_strbuf; 23 | 24 | extern unsigned char cmark_strbuf__initbuf[]; 25 | 26 | #define CMARK_BUF_INIT(mem) \ 27 | { mem, cmark_strbuf__initbuf, 0, 0 } 28 | 29 | /** 30 | * Initialize a cmark_strbuf structure. 31 | * 32 | * For the cases where CMARK_BUF_INIT cannot be used to do static 33 | * initialization. 34 | */ 35 | void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf, 36 | bufsize_t initial_size); 37 | 38 | /** 39 | * Grow the buffer to hold at least `target_size` bytes. 40 | */ 41 | void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size); 42 | 43 | void cmark_strbuf_free(cmark_strbuf *buf); 44 | void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b); 45 | 46 | bufsize_t cmark_strbuf_len(const cmark_strbuf *buf); 47 | 48 | int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b); 49 | 50 | unsigned char *cmark_strbuf_detach(cmark_strbuf *buf); 51 | void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize, 52 | const cmark_strbuf *buf); 53 | 54 | static CMARK_INLINE const char *cmark_strbuf_cstr(const cmark_strbuf *buf) { 55 | return (char *)buf->ptr; 56 | } 57 | 58 | #define cmark_strbuf_at(buf, n) ((buf)->ptr[n]) 59 | 60 | void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data, 61 | bufsize_t len); 62 | void cmark_strbuf_sets(cmark_strbuf *buf, const char *string); 63 | void cmark_strbuf_putc(cmark_strbuf *buf, int c); 64 | void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data, 65 | bufsize_t len); 66 | void cmark_strbuf_puts(cmark_strbuf *buf, const char *string); 67 | void cmark_strbuf_clear(cmark_strbuf *buf); 68 | 69 | bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos); 70 | bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos); 71 | void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n); 72 | void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len); 73 | void cmark_strbuf_rtrim(cmark_strbuf *buf); 74 | void cmark_strbuf_trim(cmark_strbuf *buf); 75 | void cmark_strbuf_normalize_whitespace(cmark_strbuf *s); 76 | void cmark_strbuf_unescape(cmark_strbuf *s); 77 | 78 | #ifdef __cplusplus 79 | } 80 | #endif 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /Sources/cmark/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CHUNK_H 2 | #define CMARK_CHUNK_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "cmark.h" 8 | #include "buffer.h" 9 | #include "cmark_ctype.h" 10 | 11 | #define CMARK_CHUNK_EMPTY \ 12 | { NULL, 0, 0 } 13 | 14 | typedef struct { 15 | unsigned char *data; 16 | bufsize_t len; 17 | bufsize_t alloc; // also implies a NULL-terminated string 18 | } cmark_chunk; 19 | 20 | static CMARK_INLINE void cmark_chunk_free(cmark_mem *mem, cmark_chunk *c) { 21 | if (c->alloc) 22 | mem->free(c->data); 23 | 24 | c->data = NULL; 25 | c->alloc = 0; 26 | c->len = 0; 27 | } 28 | 29 | static CMARK_INLINE void cmark_chunk_ltrim(cmark_chunk *c) { 30 | assert(!c->alloc); 31 | 32 | while (c->len && cmark_isspace(c->data[0])) { 33 | c->data++; 34 | c->len--; 35 | } 36 | } 37 | 38 | static CMARK_INLINE void cmark_chunk_rtrim(cmark_chunk *c) { 39 | assert(!c->alloc); 40 | 41 | while (c->len > 0) { 42 | if (!cmark_isspace(c->data[c->len - 1])) 43 | break; 44 | 45 | c->len--; 46 | } 47 | } 48 | 49 | static CMARK_INLINE void cmark_chunk_trim(cmark_chunk *c) { 50 | cmark_chunk_ltrim(c); 51 | cmark_chunk_rtrim(c); 52 | } 53 | 54 | static CMARK_INLINE bufsize_t cmark_chunk_strchr(cmark_chunk *ch, int c, 55 | bufsize_t offset) { 56 | const unsigned char *p = 57 | (unsigned char *)memchr(ch->data + offset, c, ch->len - offset); 58 | return p ? (bufsize_t)(p - ch->data) : ch->len; 59 | } 60 | 61 | static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_mem *mem, 62 | cmark_chunk *c) { 63 | unsigned char *str; 64 | 65 | if (c->alloc) { 66 | return (char *)c->data; 67 | } 68 | str = (unsigned char *)mem->calloc(c->len + 1, 1); 69 | if (c->len > 0) { 70 | memcpy(str, c->data, c->len); 71 | } 72 | str[c->len] = 0; 73 | c->data = str; 74 | c->alloc = 1; 75 | 76 | return (char *)str; 77 | } 78 | 79 | static CMARK_INLINE void cmark_chunk_set_cstr(cmark_mem *mem, cmark_chunk *c, 80 | const char *str) { 81 | unsigned char *old = c->alloc ? c->data : NULL; 82 | if (str == NULL) { 83 | c->len = 0; 84 | c->data = NULL; 85 | c->alloc = 0; 86 | } else { 87 | c->len = (bufsize_t)strlen(str); 88 | c->data = (unsigned char *)mem->calloc(c->len + 1, 1); 89 | c->alloc = 1; 90 | memcpy(c->data, str, c->len + 1); 91 | } 92 | if (old != NULL) { 93 | mem->free(old); 94 | } 95 | } 96 | 97 | static CMARK_INLINE cmark_chunk cmark_chunk_literal(const char *data) { 98 | bufsize_t len = data ? (bufsize_t)strlen(data) : 0; 99 | cmark_chunk c = {(unsigned char *)data, len, 0}; 100 | return c; 101 | } 102 | 103 | static CMARK_INLINE cmark_chunk cmark_chunk_dup(const cmark_chunk *ch, 104 | bufsize_t pos, bufsize_t len) { 105 | cmark_chunk c = {ch->data + pos, len, 0}; 106 | return c; 107 | } 108 | 109 | static CMARK_INLINE cmark_chunk cmark_chunk_buf_detach(cmark_strbuf *buf) { 110 | cmark_chunk c; 111 | 112 | c.len = buf->size; 113 | c.data = cmark_strbuf_detach(buf); 114 | c.alloc = 1; 115 | 116 | return c; 117 | } 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /Sources/cmark/cmark.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "node.h" 5 | #include "houdini.h" 6 | #include "cmark.h" 7 | #include "buffer.h" 8 | 9 | int cmark_version() { return CMARK_VERSION; } 10 | 11 | const char *cmark_version_string() { return CMARK_VERSION_STRING; } 12 | 13 | static void *xcalloc(size_t nmem, size_t size) { 14 | void *ptr = calloc(nmem, size); 15 | if (!ptr) { 16 | fprintf(stderr, "[cmark] calloc returned null pointer, aborting\n"); 17 | abort(); 18 | } 19 | return ptr; 20 | } 21 | 22 | static void *xrealloc(void *ptr, size_t size) { 23 | void *new_ptr = realloc(ptr, size); 24 | if (!new_ptr) { 25 | fprintf(stderr, "[cmark] realloc returned null pointer, aborting\n"); 26 | abort(); 27 | } 28 | return new_ptr; 29 | } 30 | 31 | cmark_mem DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, free}; 32 | 33 | char *cmark_markdown_to_html(const char *text, size_t len, int options) { 34 | cmark_node *doc; 35 | char *result; 36 | 37 | doc = cmark_parse_document(text, len, options); 38 | 39 | result = cmark_render_html(doc, options); 40 | cmark_node_free(doc); 41 | 42 | return result; 43 | } 44 | -------------------------------------------------------------------------------- /Sources/cmark/cmark_ctype.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cmark_ctype.h" 4 | 5 | /** 1 = space, 2 = punct, 3 = digit, 4 = alpha, 0 = other 6 | */ 7 | static const uint8_t cmark_ctype_class[256] = { 8 | /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ 9 | /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 10 | /* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11 | /* 2 */ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12 | /* 3 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 13 | /* 4 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 14 | /* 5 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 15 | /* 6 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 16 | /* 7 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 0, 17 | /* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | /* 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 | /* a */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | /* b */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21 | /* c */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22 | /* d */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | /* e */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | /* f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 25 | 26 | /** 27 | * Returns 1 if c is a "whitespace" character as defined by the spec. 28 | */ 29 | int cmark_isspace(char c) { return cmark_ctype_class[(uint8_t)c] == 1; } 30 | 31 | /** 32 | * Returns 1 if c is an ascii punctuation character. 33 | */ 34 | int cmark_ispunct(char c) { return cmark_ctype_class[(uint8_t)c] == 2; } 35 | 36 | int cmark_isalnum(char c) { 37 | uint8_t result; 38 | result = cmark_ctype_class[(uint8_t)c]; 39 | return (result == 3 || result == 4); 40 | } 41 | 42 | int cmark_isdigit(char c) { return cmark_ctype_class[(uint8_t)c] == 3; } 43 | 44 | int cmark_isalpha(char c) { return cmark_ctype_class[(uint8_t)c] == 4; } 45 | -------------------------------------------------------------------------------- /Sources/cmark/cmark_ctype.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CMARK_CTYPE_H 2 | #define CMARK_CMARK_CTYPE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | /** Locale-independent versions of functions from ctype.h. 9 | * We want cmark to behave the same no matter what the system locale. 10 | */ 11 | 12 | int cmark_isspace(char c); 13 | 14 | int cmark_ispunct(char c); 15 | 16 | int cmark_isalnum(char c); 17 | 18 | int cmark_isdigit(char c); 19 | 20 | int cmark_isalpha(char c); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/cmark/cmark_export.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CMARK_EXPORT_H 3 | #define CMARK_EXPORT_H 4 | 5 | #ifdef CMARK_STATIC_DEFINE 6 | # define CMARK_EXPORT 7 | # define CMARK_NO_EXPORT 8 | #else 9 | # ifndef CMARK_EXPORT 10 | # ifdef libcmark_EXPORTS 11 | /* We are building this library */ 12 | # define CMARK_EXPORT __attribute__((visibility("default"))) 13 | # else 14 | /* We are using this library */ 15 | # define CMARK_EXPORT __attribute__((visibility("default"))) 16 | # endif 17 | # endif 18 | 19 | # ifndef CMARK_NO_EXPORT 20 | # define CMARK_NO_EXPORT __attribute__((visibility("hidden"))) 21 | # endif 22 | #endif 23 | 24 | #ifndef CMARK_DEPRECATED 25 | # define CMARK_DEPRECATED __attribute__ ((__deprecated__)) 26 | #endif 27 | 28 | #ifndef CMARK_DEPRECATED_EXPORT 29 | # define CMARK_DEPRECATED_EXPORT CMARK_EXPORT CMARK_DEPRECATED 30 | #endif 31 | 32 | #ifndef CMARK_DEPRECATED_NO_EXPORT 33 | # define CMARK_DEPRECATED_NO_EXPORT CMARK_NO_EXPORT CMARK_DEPRECATED 34 | #endif 35 | 36 | #if 0 /* DEFINE_NO_DEPRECATED */ 37 | # ifndef CMARK_NO_DEPRECATED 38 | # define CMARK_NO_DEPRECATED 39 | # endif 40 | #endif 41 | 42 | #endif /* CMARK_EXPORT_H */ 43 | -------------------------------------------------------------------------------- /Sources/cmark/cmark_version.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_VERSION_H 2 | #define CMARK_VERSION_H 3 | 4 | #define CMARK_VERSION ((0 << 16) | (29 << 8) | 0) 5 | #define CMARK_VERSION_STRING "0.29.0" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Sources/cmark/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CONFIG_H 2 | #define CMARK_CONFIG_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #define HAVE_STDBOOL_H 9 | 10 | #ifdef HAVE_STDBOOL_H 11 | #include 12 | #elif !defined(__cplusplus) 13 | typedef char bool; 14 | #endif 15 | 16 | #define HAVE___BUILTIN_EXPECT 17 | 18 | #define HAVE___ATTRIBUTE__ 19 | 20 | #ifdef HAVE___ATTRIBUTE__ 21 | #define CMARK_ATTRIBUTE(list) __attribute__ (list) 22 | #else 23 | #define CMARK_ATTRIBUTE(list) 24 | #endif 25 | 26 | #ifndef CMARK_INLINE 27 | #if defined(_MSC_VER) && !defined(__cplusplus) 28 | #define CMARK_INLINE __inline 29 | #else 30 | #define CMARK_INLINE inline 31 | #endif 32 | #endif 33 | 34 | /* snprintf and vsnprintf fallbacks for MSVC before 2015, 35 | due to Valentin Milea http://stackoverflow.com/questions/2915672/ 36 | */ 37 | 38 | #if defined(_MSC_VER) && _MSC_VER < 1900 39 | 40 | #include 41 | #include 42 | 43 | #define snprintf c99_snprintf 44 | #define vsnprintf c99_vsnprintf 45 | 46 | CMARK_INLINE int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) 47 | { 48 | int count = -1; 49 | 50 | if (size != 0) 51 | count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); 52 | if (count == -1) 53 | count = _vscprintf(format, ap); 54 | 55 | return count; 56 | } 57 | 58 | CMARK_INLINE int c99_snprintf(char *outBuf, size_t size, const char *format, ...) 59 | { 60 | int count; 61 | va_list ap; 62 | 63 | va_start(ap, format); 64 | count = c99_vsnprintf(outBuf, size, format, ap); 65 | va_end(ap); 66 | 67 | return count; 68 | } 69 | 70 | #endif 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /Sources/cmark/houdini.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_HOUDINI_H 2 | #define CMARK_HOUDINI_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include "config.h" 10 | #include "buffer.h" 11 | 12 | #ifdef HAVE___BUILTIN_EXPECT 13 | #define likely(x) __builtin_expect((x), 1) 14 | #define unlikely(x) __builtin_expect((x), 0) 15 | #else 16 | #define likely(x) (x) 17 | #define unlikely(x) (x) 18 | #endif 19 | 20 | #ifdef HOUDINI_USE_LOCALE 21 | #define _isxdigit(c) isxdigit(c) 22 | #define _isdigit(c) isdigit(c) 23 | #else 24 | /* 25 | * Helper _isdigit methods -- do not trust the current locale 26 | * */ 27 | #define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL) 28 | #define _isdigit(c) ((c) >= '0' && (c) <= '9') 29 | #endif 30 | 31 | #define HOUDINI_ESCAPED_SIZE(x) (((x)*12) / 10) 32 | #define HOUDINI_UNESCAPED_SIZE(x) (x) 33 | 34 | extern bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src, 35 | bufsize_t size); 36 | extern int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, 37 | bufsize_t size); 38 | extern int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, 39 | bufsize_t size, int secure); 40 | extern int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src, 41 | bufsize_t size); 42 | extern void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src, 43 | bufsize_t size); 44 | extern int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, 45 | bufsize_t size); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /Sources/cmark/houdini_href_e.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "houdini.h" 6 | 7 | /* 8 | * The following characters will not be escaped: 9 | * 10 | * -_.+!*'(),%#@?=;:/,+&$ alphanum 11 | * 12 | * Note that this character set is the addition of: 13 | * 14 | * - The characters which are safe to be in an URL 15 | * - The characters which are *not* safe to be in 16 | * an URL because they are RESERVED characters. 17 | * 18 | * We assume (lazily) that any RESERVED char that 19 | * appears inside an URL is actually meant to 20 | * have its native function (i.e. as an URL 21 | * component/separator) and hence needs no escaping. 22 | * 23 | * There are two exceptions: the chacters & (amp) 24 | * and ' (single quote) do not appear in the table. 25 | * They are meant to appear in the URL as components, 26 | * yet they require special HTML-entity escaping 27 | * to generate valid HTML markup. 28 | * 29 | * All other characters will be escaped to %XX. 30 | * 31 | */ 32 | static const char HREF_SAFE[] = { 33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 35 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 37 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 38 | 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44 | }; 45 | 46 | int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) { 47 | static const uint8_t hex_chars[] = "0123456789ABCDEF"; 48 | bufsize_t i = 0, org; 49 | uint8_t hex_str[3]; 50 | 51 | hex_str[0] = '%'; 52 | 53 | while (i < size) { 54 | org = i; 55 | while (i < size && HREF_SAFE[src[i]] != 0) 56 | i++; 57 | 58 | if (likely(i > org)) 59 | cmark_strbuf_put(ob, src + org, i - org); 60 | 61 | /* escaping */ 62 | if (i >= size) 63 | break; 64 | 65 | switch (src[i]) { 66 | /* amp appears all the time in URLs, but needs 67 | * HTML-entity escaping to be inside an href */ 68 | case '&': 69 | cmark_strbuf_puts(ob, "&"); 70 | break; 71 | 72 | /* the single quote is a valid URL character 73 | * according to the standard; it needs HTML 74 | * entity escaping too */ 75 | case '\'': 76 | cmark_strbuf_puts(ob, "'"); 77 | break; 78 | 79 | /* the space can be escaped to %20 or a plus 80 | * sign. we're going with the generic escape 81 | * for now. the plus thing is more commonly seen 82 | * when building GET strings */ 83 | #if 0 84 | case ' ': 85 | cmark_strbuf_putc(ob, '+'); 86 | break; 87 | #endif 88 | 89 | /* every other character goes with a %XX escaping */ 90 | default: 91 | hex_str[1] = hex_chars[(src[i] >> 4) & 0xF]; 92 | hex_str[2] = hex_chars[src[i] & 0xF]; 93 | cmark_strbuf_put(ob, hex_str, 3); 94 | } 95 | 96 | i++; 97 | } 98 | 99 | return 1; 100 | } 101 | -------------------------------------------------------------------------------- /Sources/cmark/houdini_html_e.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "houdini.h" 6 | 7 | /** 8 | * According to the OWASP rules: 9 | * 10 | * & --> & 11 | * < --> < 12 | * > --> > 13 | * " --> " 14 | * ' --> ' ' is not recommended 15 | * / --> / forward slash is included as it helps end an HTML entity 16 | * 17 | */ 18 | static const char HTML_ESCAPE_TABLE[] = { 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, 21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | }; 31 | 32 | static const char *HTML_ESCAPES[] = {"", """, "&", "'", 33 | "/", "<", ">"}; 34 | 35 | int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, bufsize_t size, 36 | int secure) { 37 | bufsize_t i = 0, org, esc = 0; 38 | 39 | while (i < size) { 40 | org = i; 41 | while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0) 42 | i++; 43 | 44 | if (i > org) 45 | cmark_strbuf_put(ob, src + org, i - org); 46 | 47 | /* escaping */ 48 | if (unlikely(i >= size)) 49 | break; 50 | 51 | /* The forward slash is only escaped in secure mode */ 52 | if ((src[i] == '/' || src[i] == '\'') && !secure) { 53 | cmark_strbuf_putc(ob, src[i]); 54 | } else { 55 | cmark_strbuf_puts(ob, HTML_ESCAPES[esc]); 56 | } 57 | 58 | i++; 59 | } 60 | 61 | return 1; 62 | } 63 | 64 | int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) { 65 | return houdini_escape_html0(ob, src, size, 1); 66 | } 67 | -------------------------------------------------------------------------------- /Sources/cmark/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module libcmark [system][extern_c] { 2 | header "../node.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Sources/cmark/inlines.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_INLINES_H 2 | #define CMARK_INLINES_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | 10 | cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url); 11 | cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title); 12 | 13 | void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent, 14 | cmark_reference_map *refmap, int options); 15 | 16 | bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_chunk *input, 17 | cmark_reference_map *refmap); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Sources/cmark/iterator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.h" 5 | #include "node.h" 6 | #include "cmark.h" 7 | #include "iterator.h" 8 | 9 | static const int S_leaf_mask = 10 | (1 << CMARK_NODE_HTML_BLOCK) | (1 << CMARK_NODE_THEMATIC_BREAK) | 11 | (1 << CMARK_NODE_CODE_BLOCK) | (1 << CMARK_NODE_TEXT) | 12 | (1 << CMARK_NODE_SOFTBREAK) | (1 << CMARK_NODE_LINEBREAK) | 13 | (1 << CMARK_NODE_CODE) | (1 << CMARK_NODE_HTML_INLINE); 14 | 15 | cmark_iter *cmark_iter_new(cmark_node *root) { 16 | if (root == NULL) { 17 | return NULL; 18 | } 19 | cmark_mem *mem = root->content.mem; 20 | cmark_iter *iter = (cmark_iter *)mem->calloc(1, sizeof(cmark_iter)); 21 | iter->mem = mem; 22 | iter->root = root; 23 | iter->cur.ev_type = CMARK_EVENT_NONE; 24 | iter->cur.node = NULL; 25 | iter->next.ev_type = CMARK_EVENT_ENTER; 26 | iter->next.node = root; 27 | return iter; 28 | } 29 | 30 | void cmark_iter_free(cmark_iter *iter) { iter->mem->free(iter); } 31 | 32 | static bool S_is_leaf(cmark_node *node) { 33 | return ((1 << node->type) & S_leaf_mask) != 0; 34 | } 35 | 36 | cmark_event_type cmark_iter_next(cmark_iter *iter) { 37 | cmark_event_type ev_type = iter->next.ev_type; 38 | cmark_node *node = iter->next.node; 39 | 40 | iter->cur.ev_type = ev_type; 41 | iter->cur.node = node; 42 | 43 | if (ev_type == CMARK_EVENT_DONE) { 44 | return ev_type; 45 | } 46 | 47 | /* roll forward to next item, setting both fields */ 48 | if (ev_type == CMARK_EVENT_ENTER && !S_is_leaf(node)) { 49 | if (node->first_child == NULL) { 50 | /* stay on this node but exit */ 51 | iter->next.ev_type = CMARK_EVENT_EXIT; 52 | } else { 53 | iter->next.ev_type = CMARK_EVENT_ENTER; 54 | iter->next.node = node->first_child; 55 | } 56 | } else if (node == iter->root) { 57 | /* don't move past root */ 58 | iter->next.ev_type = CMARK_EVENT_DONE; 59 | iter->next.node = NULL; 60 | } else if (node->next) { 61 | iter->next.ev_type = CMARK_EVENT_ENTER; 62 | iter->next.node = node->next; 63 | } else if (node->parent) { 64 | iter->next.ev_type = CMARK_EVENT_EXIT; 65 | iter->next.node = node->parent; 66 | } else { 67 | assert(false); 68 | iter->next.ev_type = CMARK_EVENT_DONE; 69 | iter->next.node = NULL; 70 | } 71 | 72 | return ev_type; 73 | } 74 | 75 | void cmark_iter_reset(cmark_iter *iter, cmark_node *current, 76 | cmark_event_type event_type) { 77 | iter->next.ev_type = event_type; 78 | iter->next.node = current; 79 | cmark_iter_next(iter); 80 | } 81 | 82 | cmark_node *cmark_iter_get_node(cmark_iter *iter) { return iter->cur.node; } 83 | 84 | cmark_event_type cmark_iter_get_event_type(cmark_iter *iter) { 85 | return iter->cur.ev_type; 86 | } 87 | 88 | cmark_node *cmark_iter_get_root(cmark_iter *iter) { return iter->root; } 89 | 90 | void cmark_consolidate_text_nodes(cmark_node *root) { 91 | if (root == NULL) { 92 | return; 93 | } 94 | cmark_iter *iter = cmark_iter_new(root); 95 | cmark_strbuf buf = CMARK_BUF_INIT(iter->mem); 96 | cmark_event_type ev_type; 97 | cmark_node *cur, *tmp, *next; 98 | 99 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 100 | cur = cmark_iter_get_node(iter); 101 | if (ev_type == CMARK_EVENT_ENTER && cur->type == CMARK_NODE_TEXT && 102 | cur->next && cur->next->type == CMARK_NODE_TEXT) { 103 | cmark_strbuf_clear(&buf); 104 | cmark_strbuf_put(&buf, cur->as.literal.data, cur->as.literal.len); 105 | tmp = cur->next; 106 | while (tmp && tmp->type == CMARK_NODE_TEXT) { 107 | cmark_iter_next(iter); // advance pointer 108 | cmark_strbuf_put(&buf, tmp->as.literal.data, tmp->as.literal.len); 109 | cur->end_column = tmp->end_column; 110 | next = tmp->next; 111 | cmark_node_free(tmp); 112 | tmp = next; 113 | } 114 | cmark_chunk_free(iter->mem, &cur->as.literal); 115 | cur->as.literal = cmark_chunk_buf_detach(&buf); 116 | } 117 | } 118 | 119 | cmark_strbuf_free(&buf); 120 | cmark_iter_free(iter); 121 | } 122 | -------------------------------------------------------------------------------- /Sources/cmark/iterator.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_ITERATOR_H 2 | #define CMARK_ITERATOR_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark.h" 9 | 10 | typedef struct { 11 | cmark_event_type ev_type; 12 | cmark_node *node; 13 | } cmark_iter_state; 14 | 15 | struct cmark_iter { 16 | cmark_mem *mem; 17 | cmark_node *root; 18 | cmark_iter_state cur; 19 | cmark_iter_state next; 20 | }; 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/cmark/node.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_NODE_H 2 | #define CMARK_NODE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #include "cmark.h" 12 | #include "buffer.h" 13 | #include "chunk.h" 14 | 15 | typedef struct { 16 | cmark_list_type list_type; 17 | int marker_offset; 18 | int padding; 19 | int start; 20 | cmark_delim_type delimiter; 21 | unsigned char bullet_char; 22 | bool tight; 23 | } cmark_list; 24 | 25 | typedef struct { 26 | cmark_chunk info; 27 | cmark_chunk literal; 28 | uint8_t fence_length; 29 | uint8_t fence_offset; 30 | unsigned char fence_char; 31 | int8_t fenced; 32 | } cmark_code; 33 | 34 | typedef struct { 35 | int level; 36 | bool setext; 37 | } cmark_heading; 38 | 39 | typedef struct { 40 | cmark_chunk url; 41 | cmark_chunk title; 42 | } cmark_link; 43 | 44 | typedef struct { 45 | cmark_chunk on_enter; 46 | cmark_chunk on_exit; 47 | } cmark_custom; 48 | 49 | enum cmark_node__internal_flags { 50 | CMARK_NODE__OPEN = (1 << 0), 51 | CMARK_NODE__LAST_LINE_BLANK = (1 << 1), 52 | CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), 53 | }; 54 | 55 | struct cmark_node { 56 | cmark_strbuf content; 57 | 58 | struct cmark_node *next; 59 | struct cmark_node *prev; 60 | struct cmark_node *parent; 61 | struct cmark_node *first_child; 62 | struct cmark_node *last_child; 63 | 64 | void *user_data; 65 | 66 | int start_line; 67 | int start_column; 68 | int end_line; 69 | int end_column; 70 | int internal_offset; 71 | uint16_t type; 72 | uint16_t flags; 73 | 74 | union { 75 | cmark_chunk literal; 76 | cmark_list list; 77 | cmark_code code; 78 | cmark_heading heading; 79 | cmark_link link; 80 | cmark_custom custom; 81 | int html_block_type; 82 | } as; 83 | }; 84 | 85 | static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) { 86 | return node->content.mem; 87 | } 88 | CMARK_EXPORT int cmark_node_check(cmark_node *node, FILE *out); 89 | 90 | #ifdef __cplusplus 91 | } 92 | #endif 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /Sources/cmark/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_AST_H 2 | #define CMARK_AST_H 3 | 4 | #include 5 | #include "references.h" 6 | #include "node.h" 7 | #include "buffer.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #define MAX_LINK_LABEL_LENGTH 1000 14 | 15 | struct cmark_parser { 16 | struct cmark_mem *mem; 17 | struct cmark_reference_map *refmap; 18 | struct cmark_node *root; 19 | struct cmark_node *current; 20 | int line_number; 21 | bufsize_t offset; 22 | bufsize_t column; 23 | bufsize_t first_nonspace; 24 | bufsize_t first_nonspace_column; 25 | bufsize_t thematic_break_kill_pos; 26 | int indent; 27 | bool blank; 28 | bool partially_consumed_tab; 29 | cmark_strbuf curline; 30 | bufsize_t last_line_length; 31 | cmark_strbuf linebuf; 32 | int options; 33 | bool last_buffer_ended_with_cr; 34 | }; 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /Sources/cmark/references.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_REFERENCES_H 2 | #define CMARK_REFERENCES_H 3 | 4 | #include "chunk.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #define REFMAP_SIZE 16 11 | 12 | struct cmark_reference { 13 | struct cmark_reference *next; 14 | unsigned char *label; 15 | cmark_chunk url; 16 | cmark_chunk title; 17 | unsigned int hash; 18 | }; 19 | 20 | typedef struct cmark_reference cmark_reference; 21 | 22 | struct cmark_reference_map { 23 | cmark_mem *mem; 24 | cmark_reference *table[REFMAP_SIZE]; 25 | }; 26 | 27 | typedef struct cmark_reference_map cmark_reference_map; 28 | 29 | cmark_reference_map *cmark_reference_map_new(cmark_mem *mem); 30 | void cmark_reference_map_free(cmark_reference_map *map); 31 | cmark_reference *cmark_reference_lookup(cmark_reference_map *map, 32 | cmark_chunk *label); 33 | extern void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label, 34 | cmark_chunk *url, cmark_chunk *title); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /Sources/cmark/render.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_RENDER_H 2 | #define CMARK_RENDER_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include "buffer.h" 10 | #include "chunk.h" 11 | 12 | typedef enum { LITERAL, NORMAL, TITLE, URL } cmark_escaping; 13 | 14 | struct cmark_renderer { 15 | cmark_mem *mem; 16 | cmark_strbuf *buffer; 17 | cmark_strbuf *prefix; 18 | int column; 19 | int width; 20 | int need_cr; 21 | bufsize_t last_breakable; 22 | bool begin_line; 23 | bool begin_content; 24 | bool no_linebreaks; 25 | bool in_tight_list_item; 26 | void (*outc)(struct cmark_renderer *, cmark_escaping, int32_t, unsigned char); 27 | void (*cr)(struct cmark_renderer *); 28 | void (*blankline)(struct cmark_renderer *); 29 | void (*out)(struct cmark_renderer *, const char *, bool, cmark_escaping); 30 | }; 31 | 32 | typedef struct cmark_renderer cmark_renderer; 33 | 34 | void cmark_render_ascii(cmark_renderer *renderer, const char *s); 35 | 36 | void cmark_render_code_point(cmark_renderer *renderer, uint32_t c); 37 | 38 | char *cmark_render(cmark_node *root, int options, int width, 39 | void (*outc)(cmark_renderer *, cmark_escaping, int32_t, 40 | unsigned char), 41 | int (*render_node)(cmark_renderer *renderer, 42 | cmark_node *node, 43 | cmark_event_type ev_type, int options)); 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/cmark/scanners.h: -------------------------------------------------------------------------------- 1 | #include "cmark.h" 2 | #include "chunk.h" 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, 9 | bufsize_t offset); 10 | bufsize_t _scan_scheme(const unsigned char *p); 11 | bufsize_t _scan_autolink_uri(const unsigned char *p); 12 | bufsize_t _scan_autolink_email(const unsigned char *p); 13 | bufsize_t _scan_html_tag(const unsigned char *p); 14 | bufsize_t _scan_html_block_start(const unsigned char *p); 15 | bufsize_t _scan_html_block_start_7(const unsigned char *p); 16 | bufsize_t _scan_html_block_end_1(const unsigned char *p); 17 | bufsize_t _scan_html_block_end_2(const unsigned char *p); 18 | bufsize_t _scan_html_block_end_3(const unsigned char *p); 19 | bufsize_t _scan_html_block_end_4(const unsigned char *p); 20 | bufsize_t _scan_html_block_end_5(const unsigned char *p); 21 | bufsize_t _scan_link_title(const unsigned char *p); 22 | bufsize_t _scan_spacechars(const unsigned char *p); 23 | bufsize_t _scan_atx_heading_start(const unsigned char *p); 24 | bufsize_t _scan_setext_heading_line(const unsigned char *p); 25 | bufsize_t _scan_open_code_fence(const unsigned char *p); 26 | bufsize_t _scan_close_code_fence(const unsigned char *p); 27 | bufsize_t _scan_entity(const unsigned char *p); 28 | bufsize_t _scan_dangerous_url(const unsigned char *p); 29 | 30 | #define scan_scheme(c, n) _scan_at(&_scan_scheme, c, n) 31 | #define scan_autolink_uri(c, n) _scan_at(&_scan_autolink_uri, c, n) 32 | #define scan_autolink_email(c, n) _scan_at(&_scan_autolink_email, c, n) 33 | #define scan_html_tag(c, n) _scan_at(&_scan_html_tag, c, n) 34 | #define scan_html_block_start(c, n) _scan_at(&_scan_html_block_start, c, n) 35 | #define scan_html_block_start_7(c, n) _scan_at(&_scan_html_block_start_7, c, n) 36 | #define scan_html_block_end_1(c, n) _scan_at(&_scan_html_block_end_1, c, n) 37 | #define scan_html_block_end_2(c, n) _scan_at(&_scan_html_block_end_2, c, n) 38 | #define scan_html_block_end_3(c, n) _scan_at(&_scan_html_block_end_3, c, n) 39 | #define scan_html_block_end_4(c, n) _scan_at(&_scan_html_block_end_4, c, n) 40 | #define scan_html_block_end_5(c, n) _scan_at(&_scan_html_block_end_5, c, n) 41 | #define scan_link_title(c, n) _scan_at(&_scan_link_title, c, n) 42 | #define scan_spacechars(c, n) _scan_at(&_scan_spacechars, c, n) 43 | #define scan_atx_heading_start(c, n) _scan_at(&_scan_atx_heading_start, c, n) 44 | #define scan_setext_heading_line(c, n) \ 45 | _scan_at(&_scan_setext_heading_line, c, n) 46 | #define scan_open_code_fence(c, n) _scan_at(&_scan_open_code_fence, c, n) 47 | #define scan_close_code_fence(c, n) _scan_at(&_scan_close_code_fence, c, n) 48 | #define scan_entity(c, n) _scan_at(&_scan_entity, c, n) 49 | #define scan_dangerous_url(c, n) _scan_at(&_scan_dangerous_url, c, n) 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Sources/cmark/utf8.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_UTF8_H 2 | #define CMARK_UTF8_H 3 | 4 | #include 5 | #include "buffer.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str, 12 | bufsize_t len); 13 | void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf); 14 | int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len, int32_t *dst); 15 | void cmark_utf8proc_check(cmark_strbuf *dest, const uint8_t *line, 16 | bufsize_t size); 17 | int cmark_utf8proc_is_space(int32_t uc); 18 | int cmark_utf8proc_is_punctuation(int32_t uc); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Supporting Files/Configurations/Deployment-Targets.xcconfig: -------------------------------------------------------------------------------- 1 | SWIFT_VERSION = 5.0 2 | 3 | IPHONEOS_DEPLOYMENT_TARGET = 9.0 4 | MACOSX_DEPLOYMENT_TARGET = 10.11 5 | TVOS_DEPLOYMENT_TARGET = 9.0 6 | -------------------------------------------------------------------------------- /Supporting Files/Configurations/Universal-Framework-Target.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Universal-Target-Base.xcconfig" 2 | 3 | // OSX-specific default settings 4 | FRAMEWORK_VERSION[sdk=macosx*] = A 5 | COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES 6 | 7 | // iOS-specific default settings 8 | TARGETED_DEVICE_FAMILY[sdk=iphonesimulator*] = 1,2 9 | TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 10 | 11 | // TV-specific default settings 12 | TARGETED_DEVICE_FAMILY[sdk=appletvsimulator*] = 3 13 | TARGETED_DEVICE_FAMILY[sdk=appletv*] = 3 14 | 15 | ENABLE_BITCODE[sdk=macosx*] = NO 16 | ENABLE_BITCODE[sdk=iphonesimulator*] = NO 17 | ENABLE_BITCODE[sdk=iphone*] = YES 18 | ENABLE_BITCODE[sdk=appletvsimulator*] = YES 19 | ENABLE_BITCODE[sdk=appletv*] = YES 20 | -------------------------------------------------------------------------------- /Supporting Files/Configurations/Universal-Target-Base.xcconfig: -------------------------------------------------------------------------------- 1 | SUPPORTED_PLATFORMS = macosx iphonesimulator iphoneos appletvos appletvsimulator 2 | VALID_ARCHS[sdk=macosx*] = i386 x86_64 arm64 3 | VALID_ARCHS[sdk=iphoneos*] = arm64 armv7 armv7s 4 | VALID_ARCHS[sdk=iphonesimulator*] = i386 x86_64 arm64 5 | VALID_ARCHS[sdk=appletv*] = arm64 6 | VALID_ARCHS[sdk=appletvsimulator*] = x86_64 7 | 8 | // Dynamic linking uses different default copy paths 9 | LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) '@executable_path/../Frameworks' '@loader_path/../Frameworks' 10 | LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 11 | LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 12 | LD_RUNPATH_SEARCH_PATHS[sdk=appletvos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 13 | LD_RUNPATH_SEARCH_PATHS[sdk=appletvsimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 14 | -------------------------------------------------------------------------------- /Supporting Files/Down-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2016-2019 Down. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Supporting Files/DownTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2016-2019 Down. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/ListItemPrefixGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListItemPrefixGeneratorTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 13.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Down 11 | 12 | class ListItemPrefixGeneratorTests: XCTestCase { 13 | 14 | func testNumberStaticPrefixGeneration() { 15 | // Given 16 | let sut = StaticListItemPrefixGenerator(listType: .ordered(start: 3), numberOfItems: 3, nestDepth: 1) 17 | 18 | // Then 19 | XCTAssertEqual("3.", sut.next()) 20 | XCTAssertEqual("4.", sut.next()) 21 | XCTAssertEqual("5.", sut.next()) 22 | XCTAssertNil(sut.next()) 23 | } 24 | 25 | func testBulletStaticPrefixGeneration() { 26 | // Given 27 | let sut = StaticListItemPrefixGenerator(listType: .bullet, numberOfItems: 3, nestDepth: 1) 28 | 29 | // Then 30 | XCTAssertEqual("•", sut.next()) 31 | XCTAssertEqual("•", sut.next()) 32 | XCTAssertEqual("•", sut.next()) 33 | XCTAssertNil(sut.next()) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/NodeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 23.06.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Down 11 | 12 | class NodeTests: XCTestCase { 13 | 14 | func testListDepth() throws { 15 | // Given 16 | let sut = NodeVisitor() 17 | let markdown = """ 18 | 1. A1 19 | 2. B1 20 | * A2 21 | 1. A3 22 | 2. B3 23 | * B2 24 | 3. C1 25 | """ 26 | 27 | // When 28 | parse(markdown, andVisitWith: sut) 29 | 30 | // Then 31 | XCTAssertEqual(sut.listNestDepthResults, [0, 1, 2]) 32 | } 33 | 34 | } 35 | 36 | // MARK: - Helpers 37 | 38 | extension NodeTests { 39 | 40 | private func parse(_ markdown: String, andVisitWith visitor: NodeVisitor) { 41 | do { 42 | let document = try Down(markdownString: markdown).toDocument() 43 | document.accept(visitor) 44 | } catch { 45 | XCTFail("Failed to generate document.") 46 | } 47 | } 48 | 49 | } 50 | 51 | private class NodeVisitor: DebugVisitor { 52 | 53 | var listNestDepthResults = [Int]() 54 | 55 | override func visit(list node: List) -> String { 56 | listNestDepthResults.append(node.nestDepth) 57 | return super.visit(list: node) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testAttributedStringVisitor.1.txt: -------------------------------------------------------------------------------- 1 | Heading
This is a paragraph with inline elements


This is followed by a hard linebreak
This is after the linebreak
​ 2 | this is a link this is an image
this is a quote
code block
code block

 block

1. first item
2. second item -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testBlockQuote.1.txt: -------------------------------------------------------------------------------- 1 | Text text.
Quote Quote
Quote Quote -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testBlockQuote.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ Block Quote 5 | ↳ Paragraph 6 | ↳ Text - Quote 7 | ↳ Soft Break 8 | ↳ Text - Quote 9 | ↳ Block Quote 10 | ↳ Paragraph 11 | ↳ Text - Quote 12 | ↳ Soft Break 13 | ↳ Text - Quote 14 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testCodeBlock.1.txt: -------------------------------------------------------------------------------- 1 | Text text.
Code block
Code block
Text text. -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testCodeBlock.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ Code Block - fenceInfo: nil, content: Code block\nCode block\n 5 | ↳ Paragraph 6 | ↳ Text - Text text. 7 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testHeading.1.txt: -------------------------------------------------------------------------------- 1 | Text text.
Heading
Text text. -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testHeading.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ Heading - L1 5 | ↳ Text - Heading 6 | ↳ Paragraph 7 | ↳ Text - Text text. 8 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testHtmlBlock.1.txt: -------------------------------------------------------------------------------- 1 | Text text.

 

Text text. -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testHtmlBlock.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ Html Block - content: \n \n\n 5 | ↳ Paragraph 6 | ↳ Text - Text text. 7 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testInline.1.txt: -------------------------------------------------------------------------------- 1 | Text strong emphasis code -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testInline.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text 4 | ↳ Strong 5 | ↳ Text - strong 6 | ↳ Emphasis 7 | ↳ Text - emphasis 8 | ↳ Code - code 9 | ↳ Text - 10 | ↳ Html Inline - 11 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testLineBreak.1.txt: -------------------------------------------------------------------------------- 1 | Text text.
Text text. -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testLineBreak.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ Line Break 5 | ↳ Text - Text text. 6 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testLink.1.txt: -------------------------------------------------------------------------------- 1 | Text link text image -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testLink.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text 4 | ↳ Link - title: nil, url: www.example.com) 5 | ↳ Text - link 6 | ↳ Text - text 7 | ↳ Image - title: nil, url: www.example.com) 8 | ↳ Text - image 9 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testList.1.txt: -------------------------------------------------------------------------------- 1 | Text text.
3. One
4. Two
• Three
• Four
5. Five
Text text. -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testList.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ List - type: Ordered (start: 3), isTight: true, delimiter: period 5 | ↳ Item 6 | ↳ Paragraph 7 | ↳ Text - One 8 | ↳ Item 9 | ↳ Paragraph 10 | ↳ Text - Two 11 | ↳ List - type: Bullet, isTight: true 12 | ↳ Item 13 | ↳ Paragraph 14 | ↳ Text - Three 15 | ↳ Item 16 | ↳ Paragraph 17 | ↳ Text - Four 18 | ↳ Item 19 | ↳ Paragraph 20 | ↳ Text - Five 21 | ↳ Paragraph 22 | ↳ Text - Text text. 23 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testParagraph.1.txt: -------------------------------------------------------------------------------- 1 | Text text.
Text text.
Text text. -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testParagraph.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ Paragraph 5 | ↳ Text - Text text. 6 | ↳ Paragraph 7 | ↳ Text - Text text. 8 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testSimpleMarkdown.1.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Heading - L1 3 | ↳ Text - Hello 4 | ↳ Paragraph 5 | ↳ Text - This is a 6 | ↳ Strong 7 | ↳ Text - test! 8 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testSoftBreak.1.txt: -------------------------------------------------------------------------------- 1 | Text text text text -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testSoftBreak.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text 4 | ↳ Soft Break 5 | ↳ Text - text text 6 | -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testThematicBreak.1.txt: -------------------------------------------------------------------------------- 1 | Text text.
​ 2 | Text text. -------------------------------------------------------------------------------- /Tests/DownTests/AST/__Snapshots__/VisitorTests/testThematicBreak.2.txt: -------------------------------------------------------------------------------- 1 | Document 2 | ↳ Paragraph 3 | ↳ Text - Text text. 4 | ↳ Thematic Break 5 | ↳ Paragraph 6 | ↳ Text - Text text. 7 | -------------------------------------------------------------------------------- /Tests/DownTests/BindingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BindingTests.swift 3 | // DownTests 4 | // 5 | // Created by Rob Phillips on 5/28/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SnapshotTesting 11 | @testable import Down 12 | 13 | class BindingTests: XCTestCase { 14 | 15 | let down = Down(markdownString: "## [Down](https://github.com/johnxnnguyen/Down)") 16 | 17 | func testASTBindingsWork() throws { 18 | _ = try down.toAST() 19 | } 20 | 21 | func testHTMLBindingsWork() throws { 22 | let html = try down.toHTML() 23 | assertSnapshot(matching: html, as: .lines) 24 | } 25 | 26 | func testXMLBindingsWork() throws { 27 | let xml = try down.toXML() 28 | assertSnapshot(matching: xml, as: .lines) 29 | } 30 | 31 | func testGroffBindingsWork() throws { 32 | let man = try down.toGroff() 33 | assertSnapshot(matching: man, as: .lines) 34 | } 35 | 36 | func testLaTeXBindngsWork() throws { 37 | let latex = try down.toLaTeX() 38 | assertSnapshot(matching: latex, as: .lines) 39 | } 40 | 41 | func testCommonMarkBindngsWork() throws { 42 | let commonMark = try down.toCommonMark() 43 | assertSnapshot(matching: commonMark, as: .lines) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Tests/DownTests/Fixtures/TestDownView.bundle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | DOWN_HTML 13 | But also, custom HTML! 14 | 15 | 16 | -------------------------------------------------------------------------------- /Tests/DownTests/Fixtures/TestDownView.bundle/js/down.js: -------------------------------------------------------------------------------- 1 | hljs.initHighlightingOnLoad(); -------------------------------------------------------------------------------- /Tests/DownTests/NSAttributedStringTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringTests.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 6/2/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Down 11 | 12 | class NSAttributedStringTests: XCTestCase { 13 | 14 | func testAttributedStringBindingsWork() { 15 | let markdown = "## [Down](https://github.com/johnxnguyen/Down)" 16 | let attributedString = try? Down(markdownString: markdown).toAttributedString() 17 | XCTAssertNotNil(attributedString) 18 | XCTAssertTrue(attributedString!.string == "Down\n") 19 | } 20 | 21 | func testInstantiation() { 22 | let attributedString = try? NSAttributedString(htmlString: "

Oh Hai

") 23 | XCTAssertNotNil(attributedString) 24 | XCTAssertTrue(attributedString!.string == "Oh Hai\n") 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Tests/DownTests/StringTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringTests.swift 3 | // Down 4 | // 5 | // Created by Rob Phillips on 6/2/16. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Down 11 | 12 | class StringTests: XCTestCase { 13 | 14 | func testStringToHTML() { 15 | // String is assumed to contain valid Markdown 16 | let string = "## [Down](https://github.com/iwasrobbed/Down)" 17 | let down = try? string.toHTML() 18 | XCTAssertNotNil(down) 19 | XCTAssertTrue(down == "

Down

\n") 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/CodeBlockStyleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeBlockStyleTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 05.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | class CodeBlockStyleTests: StylerTestSuite { 12 | 13 | /// # Important 14 | /// 15 | /// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise 16 | /// the comparison may fail. These tests were recorded on the **iPhone 12** simulator. 17 | /// 18 | 19 | func testThat_CodeBlock_IsStyled() { 20 | // Given 21 | let markdown = """ 22 | Etiam vel dui id purus finibus auctor. Donec in semper lectus. Vestibulum vel eleifend justo. 23 | 24 | ``` 25 | func greet(person: Person) { 26 | print("Hello, \\(person.name)" 27 | } 28 | ``` 29 | 30 | Duis ultrices dapibus diam nec mollis. Mauris scelerisque massa nec tristique dapibus. Mauris sed tempor lorem. 31 | 32 | Duis ultrices dapibus diam nec mollis. Mauris scelerisque massa nec tristique dapibus. Mauris sed tempor lorem. 33 | """ 34 | 35 | // Then 36 | assertStyle(for: markdown, width: .wide) 37 | } 38 | 39 | func testThat_HtmlBlock_IsStyled() { 40 | // Given 41 | let markdown = """ 42 | Etiam vel dui id purus finibus auctor. Donec in semper lectus. Vestibulum vel eleifend justo. 43 | 44 | 45 | 46 | 47 | 48 | 49 | Duis ultrices dapibus diam nec mollis. Mauris scelerisque massa nec tristique dapibus. Mauris sed tempor lorem. 50 | """ 51 | 52 | // Then 53 | assertStyle(for: markdown, width: .wide) 54 | } 55 | 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/DownDebugLayoutManagerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownDebugLayoutManagerTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 08.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | class DownDebugLayoutManagerTests: StylerTestSuite { 12 | 13 | /// # Important 14 | /// 15 | /// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise 16 | /// the comparison may fail. These tests were recorded on the **iPhone 12** simulator. 17 | /// 18 | 19 | func testThat_LineFragments_AreDrawn() { 20 | // Given 21 | let markdown = """ 22 | # Curabitur fringilla lacus eget nunc dictum dignissim. 23 | --- 24 | 25 | Etiam vel dui id purus finibus auctor. Donec in semper lectus. Vestibulum vel eleifend justo. 26 | 27 | > Curabitur fringilla lacus eget nunc dictum dignissim. Donec cursus magna a libero vulputate maximus. 28 | > Donec dignissim iaculis orci et pharetra. Curabitur ac viverra augue, id placerat sapien. 29 | 30 | Duis ultrices dapibus diam nec mollis. Mauris scelerisque massa nec tristique dapibus. Mauris sed tempor lorem. 31 | 32 | 1. Etiam vel dui id purus finibus auctor. 33 | 2. Donec in semper lectus. Vestibulum vel eleifend justo. 34 | > Curabitur fringilla lacus eget nunc dictum dignissim. 35 | > Donec cursus magna a libero vulputate maximus. 36 | 3. Nunc vitae tellus eget purus sagittis aliquet. 37 | 38 | Suspendisse egestas ex at bibendum tempor. Integer tellus tortor. 39 | """ 40 | 41 | // Then 42 | assertStyle(for: markdown, width: .wide, showLineFragments: true) 43 | } 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/Helpers/CGPointTranslateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint_TranslateTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 13.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Down 11 | 12 | class CGPointTranslateTests: XCTestCase { 13 | 14 | func testPointTranslation() { 15 | // Given 16 | let sut = CGPoint(x: 1, y: 2) 17 | 18 | // When 19 | let result = sut.translated(by: CGPoint(x: 3, y: 4)) 20 | 21 | // Then 22 | XCTAssertEqual(CGPoint(x: 4, y: 6), result) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/Helpers/CGRectHelpersTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect_HelpersTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 13.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Down 11 | 12 | class CGRectHelpersTests: XCTestCase { 13 | 14 | func testRectInitializationWithBoundaries() { 15 | // When 16 | let result = CGRect(minX: 1, minY: 2, maxX: 3, maxY: 4) 17 | 18 | // Then 19 | XCTAssertEqual(CGRect(x: 1, y: 2, width: 2, height: 2), result) 20 | } 21 | 22 | func testRectTranslation() { 23 | // Given 24 | let sut = CGRect(x: 1, y: 2, width: 3, height: 4) 25 | 26 | // When 27 | let result = sut.translated(by: CGPoint(x: 5, y: 6)) 28 | 29 | // Then 30 | XCTAssertEqual(CGRect(x: 6, y: 8, width: 3, height: 4), result) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/InlineStyleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InlineStyleTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 29.07.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | class InlineStyleTests: StylerTestSuite { 12 | 13 | // # Important 14 | // 15 | // Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise 16 | // the comparison may fail. These tests were recorded on the **iPhone 12** simulator. 17 | 18 | // MARK: - Simple 19 | 20 | func testThat_StrongText_IsStyled() { 21 | // Given 22 | let markdown = "Text **strong** text." 23 | 24 | // Then 25 | assertStyle(for: markdown, width: .wide) 26 | } 27 | 28 | func testThat_EmphasizedText_IsStyled() { 29 | // Given 30 | let markdown = "Text _emphasized_ text." 31 | 32 | // Then 33 | assertStyle(for: markdown, width: .wide) 34 | } 35 | 36 | func testThat_CodeText_IsStyled() { 37 | // Given 38 | let markdown = "Text `code` text text." 39 | 40 | // Then 41 | assertStyle(for: markdown, width: .wide) 42 | } 43 | 44 | // MARK: - Double Combinations 45 | 46 | func testThat_StrongEmphasizedText_IsStyled() { 47 | // Given 48 | let markdown = "Text **strong _emphasized_ strong** text." 49 | 50 | // Then 51 | assertStyle(for: markdown, width: .wide) 52 | } 53 | 54 | func testThat_EmphasizedStrongText_IsStyled() { 55 | // Given 56 | let markdown = "Text _emphasized **strong** emphasized_ text." 57 | 58 | // Then 59 | assertStyle(for: markdown, width: .wide) 60 | } 61 | 62 | func testThat_StrongCode_IsStyled() { 63 | // Given 64 | let markdown = "Text **strong `code` strong strong** text." 65 | 66 | // Then 67 | assertStyle(for: markdown, width: .wide) 68 | } 69 | 70 | func testThat_EmphasizedCode_IsStyled() { 71 | // Given 72 | let markdown = "Text _emphasized `code` emphasized emphasized_ text." 73 | 74 | // Then 75 | assertStyle(for: markdown, width: .wide) 76 | } 77 | 78 | // MARK: - Triple Combinations 79 | 80 | func testThat_StrongEmphasizedCode_IsStyled() { 81 | // Given 82 | let markdown = "Text **strong _emphasized `code` emphasized emphasized_ strong** text." 83 | 84 | // Then 85 | assertStyle(for: markdown, width: .wide) 86 | } 87 | 88 | func testThat_EmphasizedStrongCode_IsStyled() { 89 | // Given 90 | let markdown = "Text _emphasized **strong `code` strong strong** emphasized_ text." 91 | 92 | // Then 93 | assertStyle(for: markdown, width: .wide) 94 | } 95 | 96 | } 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/LinkStyleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinkStyleTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 08.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | class LinkStyleTests: StylerTestSuite { 12 | 13 | /// # Important 14 | /// 15 | /// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise 16 | /// the comparison may fail. These tests were recorded on the **iPhone 12** simulator. 17 | /// 18 | 19 | func testThat_Link_IsStyled() { 20 | // Given 21 | let markdown = """ 22 | Praesent facilisis [pellentesque](www.example.com) ipsum at pulvinar. Sed consectetur augue. 23 | """ 24 | 25 | // Then 26 | assertStyle(for: markdown, width: .narrow) 27 | } 28 | 29 | func testThat_Link_Preserves_InlineStyles() { 30 | // Given 31 | let markdown = """ 32 | Praesent facilisis [**pellentesque _ipsum `at` _**](www.example.com). Sed consectetur augue. 33 | """ 34 | 35 | // Then 36 | assertStyle(for: markdown, width: .narrow) 37 | } 38 | 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/ThematicBreakSyleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThematicBreakSyleTests.swift 3 | // DownTests 4 | // 5 | // Created by John Nguyen on 03.08.19. 6 | // Copyright © 2016-2019 Down. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | class ThematicBreakSyleTests: StylerTestSuite { 12 | 13 | /// # Important 14 | /// 15 | /// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise 16 | /// the comparison may fail. These tests were recorded on the **iPhone 12** simulator. 17 | /// 18 | 19 | func testThat_ThematicBreak_IsStyled() { 20 | // Given 21 | let markdown = """ 22 | # Praesent facilisis pellentesque ipsum at pulvinar. 23 | 24 | --- 25 | 26 | Quisque molestie auctor neque. Donec vitae risus non odio viverra hendrerit. Pellentesque non vulputate felis. 27 | Curabitur aliquam, nisl vitae vulputate eleifend, metus sapien eleifend. 28 | """ 29 | 30 | // Then 31 | assertStyle(for: markdown, width: .wide) 32 | } 33 | 34 | func testThat_ThematicBreak_InOffsetTextContainer_IsStyled() { 35 | // Given 36 | let markdown = """ 37 | # Praesent facilisis pellentesque ipsum at pulvinar. 38 | 39 | --- 40 | 41 | Quisque molestie auctor neque. Donec vitae risus non odio viverra hendrerit. Pellentesque non vulputate felis. 42 | Curabitur aliquam, nisl vitae vulputate eleifend, metus sapien eleifend. Quisque molestie auctor neque. 43 | Donec vitae risus non odio viverra hendrerit. 44 | """ 45 | 46 | textContainerInset = .init(top: 30, left: 60, bottom: 30, right: 20) 47 | 48 | // Then 49 | assertStyle(for: markdown, width: .wide) 50 | } 51 | 52 | func testThat_ThematicBreak_CanBe_Indented() { 53 | // Given 54 | let markdown = """ 55 | # Praesent facilisis pellentesque ipsum at pulvinar. 56 | 57 | --- 58 | 59 | Quisque molestie auctor neque. Donec vitae risus non odio viverra hendrerit. Pellentesque non vulputate felis. 60 | Curabitur aliquam, nisl vitae vulputate eleifend, metus sapien eleifend. 61 | """ 62 | 63 | var configuration = self.configuration 64 | configuration.thematicBreakOptions.indentation = 30 65 | 66 | // Then 67 | assertStyle(for: markdown, width: .wide, configuration: configuration) 68 | } 69 | 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_NestedQuotes_Have_TheirOwnStripes.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_NestedQuotes_Have_TheirOwnStripes.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteAlignment_Obeys_TextContainerOffset.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteAlignment_Obeys_TextContainerOffset.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Aligns.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Aligns.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_BlockElements.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_BlockElements.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_InlineElements.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_InlineElements.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_ListFormatting.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_ListFormatting.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_ThematicBreak.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteContent_Preserves_ThematicBreak.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteStripe_AlignsTo_Margin.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuoteStripe_AlignsTo_Margin.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuotedList_WithinA_ListItem_AlignsCorrectly.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_QuotedList_WithinA_ListItem_AlignsCorrectly.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_Quotes_WithinA_ListItem_AlignsTo_ListItemContent.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/BlockQuoteStyleTests/testThat_Quotes_WithinA_ListItem_AlignsTo_ListItemContent.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/CodeBlockStyleTests/testThat_CodeBlock_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/CodeBlockStyleTests/testThat_CodeBlock_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/CodeBlockStyleTests/testThat_HtmlBlock_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/CodeBlockStyleTests/testThat_HtmlBlock_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/DownDebugLayoutManagerTests/testThat_LineFragments_AreDrawn.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/DownDebugLayoutManagerTests/testThat_LineFragments_AreDrawn.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_HeadingStyle_Preserves_StrongEmphasisAndMonospaceTraits.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_HeadingStyle_Preserves_StrongEmphasisAndMonospaceTraits.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelOne_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelOne_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelThree_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelThree_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelTwo_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelTwo_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelsThreeToSix_AreStyledEqually.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/HeadingStyleTests/testThat_Heading_LevelsThreeToSix_AreStyledEqually.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_CodeText_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_CodeText_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedCode_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedCode_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedStrongCode_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedStrongCode_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedStrongText_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedStrongText_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedText_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_EmphasizedText_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongCode_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongCode_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongEmphasizedCode_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongEmphasizedCode_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongEmphasizedText_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongEmphasizedText_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongText_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/InlineStyleTests/testThat_StrongText_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/LinkStyleTests/testThat_Link_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/LinkStyleTests/testThat_Link_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/LinkStyleTests/testThat_Link_Preserves_InlineStyles.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/LinkStyleTests/testThat_Link_Preserves_InlineStyles.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitAndBulletPrefixes_Align.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitAndBulletPrefixes_Align.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitPrefixes_ExceedingMaxPrefixLength_DontPush_WrappedLines.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitPrefixes_ExceedingMaxPrefixLength_DontPush_WrappedLines.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitPrefixes_ExceedingMaxPrefixLength_Push_FirstLine.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitPrefixes_ExceedingMaxPrefixLength_Push_FirstLine.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitPrefixes_UpToMaxPrefixLength_Align.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_DigitPrefixes_UpToMaxPrefixLength_Align.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_FirstParagraph_WithLineBreaks_AlignTo_FirstLine.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_FirstParagraph_WithLineBreaks_AlignTo_FirstLine.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_FirstParagraph_WrappedLines_AlignTo_FirstLine.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_FirstParagraph_WrappedLines_AlignTo_FirstLine.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_ListItems_Preseve_InlineElements.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_ListItems_Preseve_InlineElements.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_AlignsTo_OuterList.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_AlignsTo_OuterList.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_InMiddleParagraph_AlignsTo_OuterList.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_InMiddleParagraph_AlignsTo_OuterList.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_InTrailingParagraph_AlignsTo_OuterList.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_InTrailingParagraph_AlignsTo_OuterList.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_With_MultipleParagraphs_Align.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedList_With_MultipleParagraphs_Align.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedLists_AlignTo_ParentLists.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_NestedLists_AlignTo_ParentLists.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_TrailingParagraphs_FirstLines_AlignTo_FirstParagraph.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_TrailingParagraphs_FirstLines_AlignTo_FirstParagraph.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_TrailingParagraphs_WrappedLines_AlignTo_FirstLines.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ListItemStyleTests/testThat_TrailingParagraphs_WrappedLines_AlignTo_FirstLines.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ThematicBreakSyleTests/testThat_ThematicBreak_CanBe_Indented.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ThematicBreakSyleTests/testThat_ThematicBreak_CanBe_Indented.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ThematicBreakSyleTests/testThat_ThematicBreak_InOffsetTextContainer_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ThematicBreakSyleTests/testThat_ThematicBreak_InOffsetTextContainer_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/Styler/__Snapshots__/ThematicBreakSyleTests/testThat_ThematicBreak_IsStyled.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnxnguyen/Down/e754ab1c80920dd51a8e08290c912ac1c2ac8b58/Tests/DownTests/Styler/__Snapshots__/ThematicBreakSyleTests/testThat_ThematicBreak_IsStyled.1.png -------------------------------------------------------------------------------- /Tests/DownTests/__Snapshots__/BindingTests/testCommonMarkBindngsWork.1.txt: -------------------------------------------------------------------------------- 1 | ## [Down](https://github.com/johnxnnguyen/Down) 2 | -------------------------------------------------------------------------------- /Tests/DownTests/__Snapshots__/BindingTests/testGroffBindingsWork.1.txt: -------------------------------------------------------------------------------- 1 | .SS 2 | Down (https://github.com/johnxnnguyen/Down) 3 | -------------------------------------------------------------------------------- /Tests/DownTests/__Snapshots__/BindingTests/testHTMLBindingsWork.1.txt: -------------------------------------------------------------------------------- 1 |

Down

2 | -------------------------------------------------------------------------------- /Tests/DownTests/__Snapshots__/BindingTests/testLaTeXBindngsWork.1.txt: -------------------------------------------------------------------------------- 1 | \subsection{\href{https://github.com/johnxnnguyen/Down}{Down}} 2 | -------------------------------------------------------------------------------- /Tests/DownTests/__Snapshots__/BindingTests/testXMLBindingsWork.1.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Down 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Sources/cmark/" 3 | - "Down-Example/" 4 | - "Tests/" 5 | 6 | coverage: 7 | status: 8 | project: 9 | default: 10 | target: auto 11 | threshold: 3% 12 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # You can set the Swift version to what you need for your app. Versions can be found here: https://hub.docker.com/_/swift 2 | FROM swift:5.1 3 | 4 | RUN apt-get -qq update \ 5 | && apt-get -q -y install libssl-dev zlib1g-dev \ 6 | && rm -r /var/lib/apt/lists/* 7 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Building on Linux (Docker) 2 | 3 | - Go to "docker" directory: `cd $REPO/docker` 4 | - Compose docket image: `docker-compose build`. 5 | - Build sources: `docker-compose run --rm down`. 6 | - Login docker image (i.e. to troubleshoot build failures): `docker-compose run --rm down bash`. After that you can run `swift build --package-path /app` or `cd /app && swift build`. 7 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | down: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - ../:/app 9 | working_dir: /app 10 | stdin_open: true 11 | tty: true 12 | command: /app/docker/down-rebuild.sh 13 | -------------------------------------------------------------------------------- /docker/down-rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "- Cleaning App" 4 | swift package clean 5 | if [ $? != 0 ]; then 6 | echo "❌ Linux build failed." 7 | exit 1 8 | fi 9 | 10 | echo "- Building App" 11 | swift build --configuration release 12 | if [ $? != 0 ]; then 13 | echo "❌ Linux build failed." 14 | exit 1 15 | fi 16 | 17 | echo "✅ Linux build completed!" 18 | --------------------------------------------------------------------------------