Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e754ab1c80 | |||
| 3b07bb52cf | |||
| 055f818ed7 | |||
| 9997dc00cb | |||
| e2b597d469 | |||
| bf24fcbb2d | |||
| f34b166be1 | |||
| 0586cec03b | |||
| 2524b62470 | |||
| f83e9a2566 | |||
| 3ac921e3a9 | |||
| d127a624d3 |
@@ -0,0 +1,17 @@
|
||||
name: SwiftLint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
|
||||
jobs:
|
||||
SwiftLint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: GitHub Action for SwiftLint
|
||||
uses: norio-nomura/action-swiftlint@3.2.1
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
included:
|
||||
- Sources/Down
|
||||
- Tests
|
||||
|
||||
large_tuple:
|
||||
warning: 3
|
||||
error: 4
|
||||
|
||||
cyclomatic_complexity:
|
||||
ignores_case_statements: true
|
||||
+5
-5
@@ -4,9 +4,9 @@ before_install:
|
||||
- set -o pipefail
|
||||
- xcrun simctl boot "iPhone 12" || echo "(Pre)Launched the simulator."
|
||||
script:
|
||||
- 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 test
|
||||
- travis_retry xcodebuild -project Down.xcodeproj -scheme "Down" -sdk macosx -destination 'platform=OS X,arch=x86_64' -enableCodeCoverage YES test
|
||||
- travis_retry xcodebuild -project Down.xcodeproj -scheme "Down" -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' -enableCodeCoverage YES test
|
||||
- travis_retry xcodebuild -project Down.xcodeproj -scheme "DownSnapshotTests" -sdk iphonesimulator -destination "platform=iOS Simulator,OS=14.2,name=iPhone 12" -enableCodeCoverage YES ONLY_ACTIVE_ARCH=YES test
|
||||
after_success:
|
||||
- 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
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
- travis_retry xcodebuild -project Down.xcodeproj -scheme "Down" -sdk macosx -destination 'platform=OS X,arch=x86_64' -enableCodeCoverage YES -quiet test
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
- travis_retry xcodebuild -project Down.xcodeproj -scheme "Down" -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' -enableCodeCoverage YES -quiet test
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
+41
-2
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [v0.11.0](https://github.com/johnxnguyen/Down/tree/v0.11.0) (2021-05-04)
|
||||
|
||||
[Full Changelog](https://github.com/johnxnguyen/Down/compare/v0.10.0...v0.11.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- \[CodeCoverage\] Improve accuracy for combined code coverage reporting [\#205](https://github.com/johnxnguyen/Down/issues/205)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- DownStyler not included when installed via CocoaPods [\#254](https://github.com/johnxnguyen/Down/issues/254)
|
||||
- \[Commonmark\] Strikethrough not working / not supported [\#253](https://github.com/johnxnguyen/Down/issues/253)
|
||||
- \[Attributed Strings\] Unordered list items with a single line appear further indented than those with multiple lines when using a custom font [\#246](https://github.com/johnxnguyen/Down/issues/246)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- \[Feature\] Custom list prefixes for AttributedStringVisitor [\#255](https://github.com/johnxnguyen/Down/pull/255) ([dloic](https://github.com/dloic))
|
||||
- \[Chore\] Add SwiftLint [\#252](https://github.com/johnxnguyen/Down/pull/252) ([johnxnguyen](https://github.com/johnxnguyen))
|
||||
- \[Chore\] Fix codecov report [\#251](https://github.com/johnxnguyen/Down/pull/251) ([johnxnguyen](https://github.com/johnxnguyen))
|
||||
|
||||
## [v0.10.0](https://github.com/johnxnguyen/Down/tree/v0.10.0) (2021-02-28)
|
||||
|
||||
[Full Changelog](https://github.com/johnxnguyen/Down/compare/v0.9.5...v0.10.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Does not build in 12.5 [\#244](https://github.com/johnxnguyen/Down/issues/244)
|
||||
- \[Crash\] Missing resource bundle when using SPM [\#243](https://github.com/johnxnguyen/Down/issues/243)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- \[Improvement\] Expose DownTextView's designated initializer [\#250](https://github.com/johnxnguyen/Down/pull/250) ([max-potapov](https://github.com/max-potapov))
|
||||
- \[Chore\] Add arm64 as valid arch when building for simulator on M1 macs [\#249](https://github.com/johnxnguyen/Down/pull/249) ([michaelknoch](https://github.com/michaelknoch))
|
||||
- \[Chore\] Reorganize project structure for SPM [\#248](https://github.com/johnxnguyen/Down/pull/248) ([johnxnguyen](https://github.com/johnxnguyen))
|
||||
- \[Chore\] Use SPM to manage snapshot testing dependency [\#247](https://github.com/johnxnguyen/Down/pull/247) ([johnxnguyen](https://github.com/johnxnguyen))
|
||||
|
||||
## [v0.9.5](https://github.com/johnxnguyen/Down/tree/v0.9.5) (2021-02-12)
|
||||
|
||||
[Full Changelog](https://github.com/johnxnguyen/Down/compare/v0.9.4...v0.9.5)
|
||||
@@ -71,6 +107,7 @@
|
||||
**Merged pull requests:**
|
||||
|
||||
- Swift 5.1 Support [\#204](https://github.com/johnxnguyen/Down/pull/204) ([ghost](https://github.com/ghost))
|
||||
- Resolves Swift Package Manager issue related to swift-snapshot-testing [\#203](https://github.com/johnxnguyen/Down/pull/203) ([ghost](https://github.com/ghost))
|
||||
|
||||
## [v0.9.1](https://github.com/johnxnguyen/Down/tree/v0.9.1) (2020-02-28)
|
||||
|
||||
@@ -99,7 +136,6 @@
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Resolves Swift Package Manager issue related to swift-snapshot-testing [\#203](https://github.com/johnxnguyen/Down/pull/203) ([ghost](https://github.com/ghost))
|
||||
- Improve configurability of DownStyler [\#188](https://github.com/johnxnguyen/Down/pull/188) ([mgacy](https://github.com/mgacy))
|
||||
- Make color & font collection initializers public [\#184](https://github.com/johnxnguyen/Down/pull/184) ([johnxnguyen](https://github.com/johnxnguyen))
|
||||
|
||||
@@ -236,7 +272,6 @@
|
||||
**Merged pull requests:**
|
||||
|
||||
- Wrap openURL so that Down compiles in app extensions [\#133](https://github.com/johnxnguyen/Down/pull/133) ([nheagy](https://github.com/nheagy))
|
||||
- Fix Swift module errors when used alongside Firestore [\#125](https://github.com/johnxnguyen/Down/pull/125) ([vzsg](https://github.com/vzsg))
|
||||
|
||||
## [v0.6.5](https://github.com/johnxnguyen/Down/tree/v0.6.5) (2019-04-02)
|
||||
|
||||
@@ -255,6 +290,10 @@
|
||||
- Support SwiftPM [\#128](https://github.com/johnxnguyen/Down/issues/128)
|
||||
- \[DownView\] Load multiple .md files \(link support\) [\#68](https://github.com/johnxnguyen/Down/issues/68)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix Swift module errors when used alongside Firestore [\#125](https://github.com/johnxnguyen/Down/pull/125) ([vzsg](https://github.com/vzsg))
|
||||
|
||||
## [v0.6.3](https://github.com/johnxnguyen/Down/tree/v0.6.3) (2019-03-27)
|
||||
|
||||
[Full Changelog](https://github.com/johnxnguyen/Down/compare/v0.6.2...v0.6.3)
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "SnapshotTesting",
|
||||
"repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "c466812aa2e22898f27557e2e780d3aad7a27203",
|
||||
"version": "1.8.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Down"
|
||||
spec.summary = "Blazing fast Markdown rendering in Swift, built upon cmark."
|
||||
spec.version = "0.10.0"
|
||||
spec.version = "0.11.0"
|
||||
spec.homepage = "https://github.com/johnxnguyen/Down"
|
||||
spec.license = { :type => "MIT", :file => "LICENSE" }
|
||||
spec.authors = { "John Nguyen" => "polyxo@protonmail.com" }
|
||||
|
||||
+59
-160
@@ -66,8 +66,19 @@
|
||||
907C64651EC133780095FEE1 /* TestDownView.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 907C64621EC120530095FEE1 /* TestDownView.bundle */; };
|
||||
90A40A9C1EC03292004F2E91 /* Down.framework in Copy Bundled Frameworks */ = {isa = PBXBuildFile; fileRef = 8A569F401E6B3E50008BE2AC /* Down.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
EE0E54F82300800E0070C83F /* BlockBackgroundColorAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0E54F72300800E0070C83F /* BlockBackgroundColorAttribute.swift */; };
|
||||
EE408A39230338B600E5278A /* CGPoint_TranslateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE408A38230338B600E5278A /* CGPoint_TranslateTests.swift */; };
|
||||
EE408A3B2303399B00E5278A /* CGRect_HelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE408A3A2303399B00E5278A /* CGRect_HelpersTests.swift */; };
|
||||
EE3E7E38260400E800170A52 /* DownDebugLayoutManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5D2DF822FC9E89009EC13E /* DownDebugLayoutManagerTests.swift */; };
|
||||
EE3E7E3F260400EE00170A52 /* LinkStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5D2DF522FC99A6009EC13E /* LinkStyleTests.swift */; };
|
||||
EE3E7E46260400F100170A52 /* CodeBlockStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE64722A22F8BB0B00C5F0BA /* CodeBlockStyleTests.swift */; };
|
||||
EE3E7E4D260400F500170A52 /* BlockQuoteStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA2BDC722F705B900D0C72C /* BlockQuoteStyleTests.swift */; };
|
||||
EE3E7E54260400F900170A52 /* ThematicBreakSyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA5C02A22F58B8000B91D60 /* ThematicBreakSyleTests.swift */; };
|
||||
EE3E7E5B260400FC00170A52 /* HeadingStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99E3DD22F0C83F00BCE15B /* HeadingStyleTests.swift */; };
|
||||
EE3E7E622604010000170A52 /* InlineStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9E886222EF76040005948C /* InlineStyleTests.swift */; };
|
||||
EE3E7E692604010300170A52 /* ListItemStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE335C4922EDC85900648842 /* ListItemStyleTests.swift */; };
|
||||
EE3E7E702604010700170A52 /* StylerTestSuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE59C30122ECF0BD006EE8A8 /* StylerTestSuite.swift */; };
|
||||
EE3E7E812604019300170A52 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = EE3E7E802604019300170A52 /* SnapshotTesting */; };
|
||||
EE3E7E88260401F000170A52 /* VisitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE64FEEF225BEB3900A35B34 /* VisitorTests.swift */; };
|
||||
EE408A39230338B600E5278A /* CGPointTranslateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE408A38230338B600E5278A /* CGPointTranslateTests.swift */; };
|
||||
EE408A3B2303399B00E5278A /* CGRectHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE408A3A2303399B00E5278A /* CGRectHelpersTests.swift */; };
|
||||
EE408A3D23033B6B00E5278A /* ListItemPrefixGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE408A3C23033B6B00E5278A /* ListItemPrefixGeneratorTests.swift */; };
|
||||
EE44848B2301E51C0065C836 /* CodeBlockOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE44848A2301E51C0065C836 /* CodeBlockOptions.swift */; };
|
||||
EE4484912301F2920065C836 /* CGPoint+Translate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4484902301F2920065C836 /* CGPoint+Translate.swift */; };
|
||||
@@ -86,19 +97,7 @@
|
||||
EEA2BDCA22F7152B00D0C72C /* ThematicBreakOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA2BDC922F7152B00D0C72C /* ThematicBreakOptions.swift */; };
|
||||
EEA5C02922F58A0900B91D60 /* DownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA5C02822F58A0900B91D60 /* DownTextView.swift */; };
|
||||
EEA5C02D22F5C96B00B91D60 /* QuoteStripeAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA5C02C22F5C96B00B91D60 /* QuoteStripeAttribute.swift */; };
|
||||
EEBA15422344849600B54ECB /* Down.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A569F401E6B3E50008BE2AC /* Down.framework */; };
|
||||
EEBA1546234484BF00B54ECB /* DownDebugLayoutManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5D2DF822FC9E89009EC13E /* DownDebugLayoutManagerTests.swift */; };
|
||||
EEBA1547234484BF00B54ECB /* LinkStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5D2DF522FC99A6009EC13E /* LinkStyleTests.swift */; };
|
||||
EEBA1548234484BF00B54ECB /* CodeBlockStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE64722A22F8BB0B00C5F0BA /* CodeBlockStyleTests.swift */; };
|
||||
EEBA1549234484BF00B54ECB /* BlockQuoteStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA2BDC722F705B900D0C72C /* BlockQuoteStyleTests.swift */; };
|
||||
EEBA154A234484BF00B54ECB /* ThematicBreakSyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA5C02A22F58B8000B91D60 /* ThematicBreakSyleTests.swift */; };
|
||||
EEBA154B234484BF00B54ECB /* HeadingStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE99E3DD22F0C83F00BCE15B /* HeadingStyleTests.swift */; };
|
||||
EEBA154C234484BF00B54ECB /* InlineStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9E886222EF76040005948C /* InlineStyleTests.swift */; };
|
||||
EEBA154D234484BF00B54ECB /* ListItemStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE335C4922EDC85900648842 /* ListItemStyleTests.swift */; };
|
||||
EEBA154E234484BF00B54ECB /* StylerTestSuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE59C30122ECF0BD006EE8A8 /* StylerTestSuite.swift */; };
|
||||
EEBA154F234484C300B54ECB /* VisitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE64FEEF225BEB3900A35B34 /* VisitorTests.swift */; };
|
||||
EEBE62F025E28F3D005CCAD6 /* BundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBE62EF25E28F3D005CCAD6 /* BundleHelper.swift */; };
|
||||
EED7FED725E303000033E33A /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = EED7FED625E303000033E33A /* SnapshotTesting */; };
|
||||
EED7FED825E3133C0033E33A /* DownView (macOS).bundle in Resources */ = {isa = PBXBuildFile; fileRef = 14C5E33621877FCD00D5380C /* DownView (macOS).bundle */; };
|
||||
EED8DA8E22BE404F00E54492 /* DownStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED8DA8D22BE404F00E54492 /* DownStyler.swift */; };
|
||||
EED8DA9022BECBAE00E54492 /* NSAttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED8DA8F22BECBAE00E54492 /* NSAttributedString+Helpers.swift */; };
|
||||
@@ -268,8 +267,9 @@
|
||||
D4F948DB1D00A4A800C9C0F6 /* NSAttributedStringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSAttributedStringTests.swift; sourceTree = "<group>"; };
|
||||
EE0E54F72300800E0070C83F /* BlockBackgroundColorAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockBackgroundColorAttribute.swift; sourceTree = "<group>"; };
|
||||
EE335C4922EDC85900648842 /* ListItemStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemStyleTests.swift; sourceTree = "<group>"; };
|
||||
EE408A38230338B600E5278A /* CGPoint_TranslateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint_TranslateTests.swift; sourceTree = "<group>"; };
|
||||
EE408A3A2303399B00E5278A /* CGRect_HelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRect_HelpersTests.swift; sourceTree = "<group>"; };
|
||||
EE39B28026063E0D002C4F8D /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
|
||||
EE408A38230338B600E5278A /* CGPointTranslateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPointTranslateTests.swift; sourceTree = "<group>"; };
|
||||
EE408A3A2303399B00E5278A /* CGRectHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectHelpersTests.swift; sourceTree = "<group>"; };
|
||||
EE408A3C23033B6B00E5278A /* ListItemPrefixGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemPrefixGeneratorTests.swift; sourceTree = "<group>"; };
|
||||
EE44848A2301E51C0065C836 /* CodeBlockOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeBlockOptions.swift; sourceTree = "<group>"; };
|
||||
EE4484902301F2920065C836 /* CGPoint+Translate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint+Translate.swift"; sourceTree = "<group>"; };
|
||||
@@ -298,9 +298,6 @@
|
||||
EEA5C02822F58A0900B91D60 /* DownTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownTextView.swift; sourceTree = "<group>"; };
|
||||
EEA5C02A22F58B8000B91D60 /* ThematicBreakSyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThematicBreakSyleTests.swift; sourceTree = "<group>"; };
|
||||
EEA5C02C22F5C96B00B91D60 /* QuoteStripeAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteStripeAttribute.swift; sourceTree = "<group>"; };
|
||||
EEBA153A2344845500B54ECB /* DownSnapshotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DownSnapshotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EEBA153C2344845500B54ECB /* DownSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
EEBA153E2344845500B54ECB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
EEBE62EF25E28F3D005CCAD6 /* BundleHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BundleHelper.swift; sourceTree = "<group>"; };
|
||||
EED8DA8D22BE404F00E54492 /* DownStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownStyler.swift; sourceTree = "<group>"; };
|
||||
EED8DA8F22BECBAE00E54492 /* NSAttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Helpers.swift"; sourceTree = "<group>"; };
|
||||
@@ -344,15 +341,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8AFAEB001E6E32E900E09B68 /* Down.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
EEBA15372344845500B54ECB /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EEBA15422344849600B54ECB /* Down.framework in Frameworks */,
|
||||
EED7FED725E303000033E33A /* SnapshotTesting in Frameworks */,
|
||||
EE3E7E812604019300170A52 /* SnapshotTesting in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -398,10 +387,10 @@
|
||||
D4201E761CFA5151008EEC6E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EE39B28026063E0D002C4F8D /* .swiftlint.yml */,
|
||||
D4201E9B1CFA59A5008EEC6E /* Sources */,
|
||||
D41689B41CFFE6BB00E5802B /* Supporting Files */,
|
||||
D4201EC41CFA59A5008EEC6E /* Tests */,
|
||||
EEBA153B2344845500B54ECB /* DownSnapshotTests */,
|
||||
D4201E811CFA5151008EEC6E /* Products */,
|
||||
EE54F96D22EB9CE400628683 /* Frameworks */,
|
||||
);
|
||||
@@ -414,7 +403,6 @@
|
||||
children = (
|
||||
8A569F401E6B3E50008BE2AC /* Down.framework */,
|
||||
8AFAEAFB1E6E32E900E09B68 /* DownTests.xctest */,
|
||||
EEBA153A2344845500B54ECB /* DownSnapshotTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -524,8 +512,8 @@
|
||||
EE408A3E23033DFE00E5278A /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EE408A3A2303399B00E5278A /* CGRect_HelpersTests.swift */,
|
||||
EE408A38230338B600E5278A /* CGPoint_TranslateTests.swift */,
|
||||
EE408A3A2303399B00E5278A /* CGRectHelpersTests.swift */,
|
||||
EE408A38230338B600E5278A /* CGPointTranslateTests.swift */,
|
||||
EE97254222C14B79004D3B3A /* NSAttributedString+HelpersTests.swift */,
|
||||
EE97253D22C130D8004D3B3A /* NSMutableAttributedString+AttributesTests.swift */,
|
||||
);
|
||||
@@ -632,15 +620,6 @@
|
||||
path = Styling;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EEBA153B2344845500B54ECB /* DownSnapshotTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EEBA153C2344845500B54ECB /* DownSnapshotTests.swift */,
|
||||
EEBA153E2344845500B54ECB /* Info.plist */,
|
||||
);
|
||||
path = DownSnapshotTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EEC752BE22C4AE1300EC729C /* AST */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -796,6 +775,7 @@
|
||||
8A569F3D1E6B3E50008BE2AC /* Headers */,
|
||||
14C5E33421877CDC00D5380C /* Resources */,
|
||||
14CEAC38218774BC00039EDF /* Replace Bundle for macOS Platform */,
|
||||
EED185ED26054F800051E616 /* Swiftlint */,
|
||||
);
|
||||
buildRules = (
|
||||
8AE66BE41F848C3900ED4C98 /* PBXBuildRule */,
|
||||
@@ -824,30 +804,13 @@
|
||||
8AFAEB021E6E32E900E09B68 /* PBXTargetDependency */,
|
||||
);
|
||||
name = DownTests;
|
||||
packageProductDependencies = (
|
||||
EE3E7E802604019300170A52 /* SnapshotTesting */,
|
||||
);
|
||||
productName = "DownTests-macOS";
|
||||
productReference = 8AFAEAFB1E6E32E900E09B68 /* DownTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
EEBA15392344845500B54ECB /* DownSnapshotTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = EEBA15412344845500B54ECB /* Build configuration list for PBXNativeTarget "DownSnapshotTests" */;
|
||||
buildPhases = (
|
||||
EEBA15362344845500B54ECB /* Sources */,
|
||||
EEBA15372344845500B54ECB /* Frameworks */,
|
||||
EEBA15382344845500B54ECB /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = DownSnapshotTests;
|
||||
packageProductDependencies = (
|
||||
EED7FED625E303000033E33A /* SnapshotTesting */,
|
||||
);
|
||||
productName = DownSnapshotTests;
|
||||
productReference = EEBA153A2344845500B54ECB /* DownSnapshotTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -868,10 +831,6 @@
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
EEBA15392344845500B54ECB = {
|
||||
CreatedOnToolsVersion = 11.0;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = D4201E7A1CFA5151008EEC6E /* Build configuration list for PBXProject "Down" */;
|
||||
@@ -892,7 +851,6 @@
|
||||
targets = (
|
||||
8A569F3F1E6B3E50008BE2AC /* Down */,
|
||||
8AFAEAFA1E6E32E900E09B68 /* DownTests */,
|
||||
EEBA15392344845500B54ECB /* DownSnapshotTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -915,13 +873,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
EEBA15382344845500B54ECB /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
@@ -944,6 +895,24 @@
|
||||
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nRESOURCE_PATH=$SRCROOT/Sources/Down/Resources\n\nFILENAME_IN_BUNDLE=DownView.bundle\n\nBUILD_APP_DIR=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Resources\n\necho \"$RESOURCE_PATH\"\necho \"$BUILD_APP_DIR\"\n\nif [ \"$PLATFORM_NAME\" == \"macosx\" ]; then\n echo $BUILD_APP_DIR\n rm -r \"$BUILD_APP_DIR/$FILENAME_IN_BUNDLE/\"\n cp -R \"$RESOURCE_PATH/DownView (macOS).bundle\" \"$BUILD_APP_DIR/$FILENAME_IN_BUNDLE/\"\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EED185ED26054F800051E616 /* Swiftlint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = Swiftlint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -1042,36 +1011,29 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EE3E7E54260400F900170A52 /* ThematicBreakSyleTests.swift in Sources */,
|
||||
EE3E7E4D260400F500170A52 /* BlockQuoteStyleTests.swift in Sources */,
|
||||
EE97253E22C130D8004D3B3A /* NSMutableAttributedString+AttributesTests.swift in Sources */,
|
||||
EE3E7E622604010000170A52 /* InlineStyleTests.swift in Sources */,
|
||||
EE3E7E702604010700170A52 /* StylerTestSuite.swift in Sources */,
|
||||
EE3E7E692604010300170A52 /* ListItemStyleTests.swift in Sources */,
|
||||
EE3E7E46260400F100170A52 /* CodeBlockStyleTests.swift in Sources */,
|
||||
EE97254322C14B79004D3B3A /* NSAttributedString+HelpersTests.swift in Sources */,
|
||||
8AFAEB091E6E331700E09B68 /* StringTests.swift in Sources */,
|
||||
EE3E7E5B260400FC00170A52 /* HeadingStyleTests.swift in Sources */,
|
||||
EE408A3D23033B6B00E5278A /* ListItemPrefixGeneratorTests.swift in Sources */,
|
||||
EE8F38CC22BFB2420056270E /* NodeTests.swift in Sources */,
|
||||
EE408A39230338B600E5278A /* CGPoint_TranslateTests.swift in Sources */,
|
||||
EE408A3B2303399B00E5278A /* CGRect_HelpersTests.swift in Sources */,
|
||||
EE3E7E3F260400EE00170A52 /* LinkStyleTests.swift in Sources */,
|
||||
EE3E7E38260400E800170A52 /* DownDebugLayoutManagerTests.swift in Sources */,
|
||||
EE408A39230338B600E5278A /* CGPointTranslateTests.swift in Sources */,
|
||||
EE408A3B2303399B00E5278A /* CGRectHelpersTests.swift in Sources */,
|
||||
EE3E7E88260401F000170A52 /* VisitorTests.swift in Sources */,
|
||||
8AFAEB071E6E331700E09B68 /* DownViewTests.swift in Sources */,
|
||||
8AFAEB061E6E331700E09B68 /* BindingTests.swift in Sources */,
|
||||
8AFAEB081E6E331700E09B68 /* NSAttributedStringTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
EEBA15362344845500B54ECB /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EEBA154B234484BF00B54ECB /* HeadingStyleTests.swift in Sources */,
|
||||
EEBA1549234484BF00B54ECB /* BlockQuoteStyleTests.swift in Sources */,
|
||||
EEBA1546234484BF00B54ECB /* DownDebugLayoutManagerTests.swift in Sources */,
|
||||
EEBA154D234484BF00B54ECB /* ListItemStyleTests.swift in Sources */,
|
||||
EEBA154C234484BF00B54ECB /* InlineStyleTests.swift in Sources */,
|
||||
EEBA154A234484BF00B54ECB /* ThematicBreakSyleTests.swift in Sources */,
|
||||
EEBA1548234484BF00B54ECB /* CodeBlockStyleTests.swift in Sources */,
|
||||
EEBA1547234484BF00B54ECB /* LinkStyleTests.swift in Sources */,
|
||||
EEBA154E234484BF00B54ECB /* StylerTestSuite.swift in Sources */,
|
||||
EEBA154F234484C300B54ECB /* VisitorTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
@@ -1137,9 +1099,11 @@
|
||||
"$(PROJECT_DIR)/Carthage/Build/iOS",
|
||||
);
|
||||
INFOPLIST_FILE = "Supporting Files/DownTests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.downMarkdown.DownTests;
|
||||
"TARGETED_DEVICE_FAMILY[sdk=appletvos*]" = 3;
|
||||
"TARGETED_DEVICE_FAMILY[sdk=appletvsimulator*]" = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 13.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1153,9 +1117,11 @@
|
||||
"$(PROJECT_DIR)/Carthage/Build/iOS",
|
||||
);
|
||||
INFOPLIST_FILE = "Supporting Files/DownTests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.downMarkdown.DownTests;
|
||||
"TARGETED_DEVICE_FAMILY[sdk=appletvos*]" = 3;
|
||||
"TARGETED_DEVICE_FAMILY[sdk=appletvsimulator*]" = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 13.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1281,64 +1247,6 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
EEBA153F2344845500B54ECB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Carthage/Build/iOS",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = DownSnapshotTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.downMarkdown.DownSnapshotTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
EEBA15402344845500B54ECB /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Carthage/Build/iOS",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = DownSnapshotTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.downMarkdown.DownSnapshotTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -1369,15 +1277,6 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
EEBA15412344845500B54ECB /* Build configuration list for PBXNativeTarget "DownSnapshotTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
EEBA153F2344845500B54ECB /* Debug */,
|
||||
EEBA15402344845500B54ECB /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
@@ -1392,7 +1291,7 @@
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
EED7FED625E303000033E33A /* SnapshotTesting */ = {
|
||||
EE3E7E802604019300170A52 /* SnapshotTesting */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = EED7FED525E303000033E33A /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
|
||||
productName = SnapshotTesting;
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "EEBA15392344845500B54ECB"
|
||||
BuildableName = "DownSnapshotTests.xctest"
|
||||
BlueprintName = "DownSnapshotTests"
|
||||
ReferencedContainer = "container:Down.xcodeproj">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "EEBA15392344845500B54ECB"
|
||||
BuildableName = "DownSnapshotTests.xctest"
|
||||
BlueprintName = "DownSnapshotTests"
|
||||
ReferencedContainer = "container:Down.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,7 +1,7 @@
|
||||
## Down
|
||||
[](https://travis-ci.com/johnxnguyen/Down)
|
||||
[](https://github.com/johnxnguyen/Down/blob/master/LICENSE)
|
||||
[]()
|
||||
[](https://cocoapods.org/pods/Down)
|
||||
[](https://swift.org)
|
||||
[](https://developer.apple.com/macos/)
|
||||
[](https://developer.apple.com/ios/)
|
||||
|
||||
@@ -10,7 +10,9 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class BaseNode: Node {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public let cmarkNode: CMarkNode
|
||||
|
||||
public private(set) lazy var children: [Node] = Array(childSequence)
|
||||
@@ -25,9 +27,11 @@ public class BaseNode: Node {
|
||||
}
|
||||
return depth
|
||||
}()
|
||||
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
init(cmarkNode: CMarkNode) {
|
||||
self.cmarkNode = cmarkNode
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class BlockQuote: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension BlockQuote: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Block Quote"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,13 +7,34 @@
|
||||
|
||||
import libcmark
|
||||
|
||||
/// Sequence of child nodes
|
||||
/// Sequence of child nodes.
|
||||
|
||||
public struct ChildSequence: Sequence {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
let node: CMarkNode
|
||||
|
||||
public struct Iterator: IteratorProtocol {
|
||||
// MARK: - Methods
|
||||
|
||||
public func makeIterator() -> Iterator {
|
||||
return Iterator(node: cmark_node_first_child(node))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Iterator
|
||||
|
||||
public extension ChildSequence {
|
||||
|
||||
struct Iterator: IteratorProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var node: CMarkNode?
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
public mutating func next() -> Node? {
|
||||
guard let node = node else { return nil }
|
||||
defer { self.node = cmark_node_next(node) }
|
||||
@@ -25,10 +46,7 @@ public struct ChildSequence: Sequence {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func makeIterator() -> Iterator {
|
||||
return Iterator(node: cmark_node_first_child(node))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,17 +9,21 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class Code: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The code content, if present.
|
||||
|
||||
public private(set) lazy var literal: String? = cmarkNode.literal
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension Code: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Code - \(literal ?? "nil")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,10 +9,13 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class CodeBlock: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The code content, if present.
|
||||
|
||||
public private(set) lazy var literal: String? = cmarkNode.literal
|
||||
|
||||
|
||||
/// The fence info is an optional string that trails the opening sequence of backticks.
|
||||
/// It can be used to provide some contextual information about the block, such as
|
||||
/// the name of a programming language.
|
||||
@@ -24,16 +27,18 @@ public class CodeBlock: BaseNode {
|
||||
/// '''
|
||||
/// ```
|
||||
///
|
||||
|
||||
public private(set) lazy var fenceInfo: String? = cmarkNode.fenceInfo
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension CodeBlock: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
let content = (literal ?? "nil").replacingOccurrences(of: "\n", with: "\\n")
|
||||
return "Code Block - fenceInfo: \(fenceInfo ?? "nil"), content: \(content)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,17 +9,21 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class CustomBlock: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properfies
|
||||
|
||||
/// The custom content, if present.
|
||||
|
||||
public private(set) lazy var literal: String? = cmarkNode.literal
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension CustomBlock: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Custom Block - \(literal ?? "nil")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,16 +9,20 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class CustomInline: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The custom content, if present.
|
||||
|
||||
public private(set) lazy var literal: String? = cmarkNode.literal
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension CustomInline: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Custom Inline - \(literal ?? "nil")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,26 +9,30 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class Document: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
deinit {
|
||||
// Frees the node and all its children.
|
||||
cmark_node_free(cmarkNode)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
/// Accepts the given visitor and return its result.
|
||||
|
||||
@discardableResult
|
||||
public func accept<T: Visitor>(_ visitor: T) -> T.Result {
|
||||
return visitor.visit(document: self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension Document: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Document"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class Emphasis: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension Emphasis: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Emphasis"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,16 +9,20 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class Heading: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The level of the heading, a value between 1 and 6.
|
||||
|
||||
public private(set) lazy var headingLevel: Int = cmarkNode.headingLevel
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension Heading: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Heading - L\(headingLevel)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,18 +9,22 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class HtmlBlock: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The html content, if present.
|
||||
|
||||
public private(set) lazy var literal: String? = cmarkNode.literal
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension HtmlBlock: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
let content = (literal ?? "nil").replacingOccurrences(of: "\n", with: "\\n")
|
||||
return "Html Block - content: \(content)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,17 +9,21 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class HtmlInline: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The html tag, if present.
|
||||
|
||||
public private(set) lazy var literal: String? = cmarkNode.literal
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension HtmlInline: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Html Inline - \(literal ?? "nil")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class Image: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The title of the image, if present.
|
||||
///
|
||||
/// In the example below, the first line is a reference link, with the reference at the
|
||||
@@ -21,9 +23,9 @@ public class Image: BaseNode {
|
||||
/// ...
|
||||
/// [<id>]: <url> "<title>"
|
||||
/// ```
|
||||
///
|
||||
|
||||
public private(set) lazy var title: String? = cmarkNode.title
|
||||
|
||||
|
||||
/// The url of the image, if present.
|
||||
///
|
||||
/// For example:
|
||||
@@ -31,16 +33,17 @@ public class Image: BaseNode {
|
||||
/// ```
|
||||
/// 
|
||||
/// ```
|
||||
///
|
||||
|
||||
public private(set) lazy var url: String? = cmarkNode.url
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension Image: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Image - title: \(title ?? "nil"), url: \(url ?? "nil"))"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class Item: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension Item: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Item"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class LineBreak: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension LineBreak: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Line Break"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class Link: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The title of the link, if present.
|
||||
///
|
||||
/// In the example below, the first line is a reference link, with the reference at the
|
||||
@@ -21,9 +23,9 @@ public class Link: BaseNode {
|
||||
/// ...
|
||||
/// [<id>]: <url> "<title>"
|
||||
/// ```
|
||||
///
|
||||
|
||||
public private(set) lazy var title: String? = cmarkNode.title
|
||||
|
||||
|
||||
/// The url of the link, if present.
|
||||
///
|
||||
/// For example:
|
||||
@@ -31,16 +33,17 @@ public class Link: BaseNode {
|
||||
/// ```
|
||||
/// [<text>](<url>)
|
||||
/// ```
|
||||
///
|
||||
|
||||
public private(set) lazy var url: String? = cmarkNode.url
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension Link: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Link - title: \(title ?? "nil"), url: \(url ?? "nil"))"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,17 +10,21 @@ import libcmark
|
||||
|
||||
public class List: BaseNode {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The type of the list, either bullet or ordered.
|
||||
|
||||
public lazy var listType: ListType = {
|
||||
guard let type = ListType(cmarkNode: cmarkNode) else {
|
||||
assertionFailure("Unsupported or missing list type. Defaulting to .bullet.")
|
||||
return .bullet
|
||||
}
|
||||
|
||||
|
||||
return type
|
||||
}()
|
||||
|
||||
|
||||
/// The number of items in the list.
|
||||
|
||||
public lazy var numberOfItems: Int = children.count
|
||||
|
||||
/// Whether the list is "tight".
|
||||
@@ -29,16 +33,36 @@ public class List: BaseNode {
|
||||
/// a hint to render the list with more (loose) or less (tight) spacing between items.
|
||||
|
||||
public lazy var isTight: Bool = cmark_node_get_list_tight(cmarkNode) == 1
|
||||
|
||||
/// The list delimiter.
|
||||
|
||||
public lazy var delimiter: Delimiter? = Delimiter(cmarkNode.listDelimiter)
|
||||
}
|
||||
|
||||
// MARK: - List Type
|
||||
|
||||
public extension List {
|
||||
|
||||
enum Delimiter {
|
||||
case period
|
||||
case paren
|
||||
|
||||
init?(_ cmark: cmark_delim_type) {
|
||||
switch cmark {
|
||||
case CMARK_NO_DELIM: return nil
|
||||
case CMARK_PERIOD_DELIM: self = .period
|
||||
case CMARK_PAREN_DELIM: self = .paren
|
||||
default: preconditionFailure("Invalid delim type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ListType: CustomDebugStringConvertible {
|
||||
case bullet
|
||||
case ordered(start: Int)
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .bullet: return "Bullet"
|
||||
@@ -46,6 +70,8 @@ public extension List {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
init?(cmarkNode: CMarkNode) {
|
||||
switch cmarkNode.listType {
|
||||
case CMARK_BULLET_LIST: self = .bullet
|
||||
@@ -53,14 +79,29 @@ public extension List {
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension List: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "List - type: \(listType), isTight: \(isTight)"
|
||||
var result = "List - type: \(listType), isTight: \(isTight)"
|
||||
if let delim = delimiter {
|
||||
result += ", delimiter: \(delim)"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension List.Delimiter: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .paren: return "paren"
|
||||
case .period: return "period"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,33 +10,43 @@ import libcmark
|
||||
|
||||
/// A node is a wrapper of a raw `CMarkNode` belonging to the abstract syntax tree
|
||||
/// generated by cmark.
|
||||
|
||||
public protocol Node {
|
||||
|
||||
/// The wrapped node.
|
||||
|
||||
var cmarkNode: CMarkNode { get }
|
||||
|
||||
|
||||
/// The wrapped child nodes.
|
||||
|
||||
var children: [Node] { get }
|
||||
|
||||
}
|
||||
|
||||
public extension Node {
|
||||
|
||||
/// True iff the node has a sibling that succeeds it.
|
||||
|
||||
var hasSuccessor: Bool {
|
||||
return cmark_node_next(cmarkNode) != nil
|
||||
}
|
||||
|
||||
/// Sequence of wrapped child nodes.
|
||||
|
||||
var childSequence: ChildSequence {
|
||||
return ChildSequence(node: cmarkNode)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Helper extensions
|
||||
|
||||
public typealias CMarkNode = UnsafeMutablePointer<cmark_node>
|
||||
|
||||
public extension UnsafeMutablePointer where Pointee == cmark_node {
|
||||
|
||||
public extension CMarkNode {
|
||||
|
||||
/// Wraps the cmark node referred to by this pointer.
|
||||
|
||||
func wrap() -> Node? {
|
||||
switch type {
|
||||
case CMARK_NODE_DOCUMENT: return Document(cmarkNode: self)
|
||||
@@ -70,42 +80,47 @@ public extension UnsafeMutablePointer where Pointee == cmark_node {
|
||||
var type: cmark_node_type {
|
||||
return cmark_node_get_type(self)
|
||||
}
|
||||
|
||||
|
||||
var literal: String? {
|
||||
return String(cString: cmark_node_get_literal(self))
|
||||
}
|
||||
|
||||
|
||||
var fenceInfo: String? {
|
||||
return String(cString: cmark_node_get_fence_info(self))
|
||||
}
|
||||
|
||||
|
||||
var headingLevel: Int {
|
||||
return Int(cmark_node_get_heading_level(self))
|
||||
}
|
||||
|
||||
|
||||
var listType: cmark_list_type {
|
||||
return cmark_node_get_list_type(self)
|
||||
}
|
||||
|
||||
|
||||
var listStart: Int {
|
||||
return Int(cmark_node_get_list_start(self))
|
||||
}
|
||||
|
||||
|
||||
var listDelimiter: cmark_delim_type {
|
||||
return cmark_node_get_list_delim(self)
|
||||
}
|
||||
|
||||
var url: String? {
|
||||
return String(cString: cmark_node_get_url(self))
|
||||
}
|
||||
|
||||
|
||||
var title: String? {
|
||||
return String(cString: cmark_node_get_title(self))
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
|
||||
|
||||
init?(cString: UnsafePointer<Int8>?) {
|
||||
guard let unwrapped = cString else { return nil }
|
||||
let result = String(cString: unwrapped)
|
||||
guard !result.isEmpty else { return nil }
|
||||
self = result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class Paragraph: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension Paragraph: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Paragraph"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class SoftBreak: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension SoftBreak: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Soft Break"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class Strong: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension Strong: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Strong"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,17 +9,21 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public class Text: BaseNode {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The text content, if present.
|
||||
|
||||
public private(set) lazy var literal: String? = cmarkNode.literal
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
||||
extension Text: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Text - \(literal ?? "nil")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ public class ThematicBreak: BaseNode {}
|
||||
// MARK: - Debug
|
||||
|
||||
extension ThematicBreak: CustomDebugStringConvertible {
|
||||
|
||||
|
||||
public var debugDescription: String {
|
||||
return "Thematic Break"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,10 +36,13 @@ public protocol ColorCollection {
|
||||
var thematicBreak: DownColor { get }
|
||||
var listItemPrefix: DownColor { get }
|
||||
var codeBlockBackground: DownColor { get }
|
||||
|
||||
}
|
||||
|
||||
public struct StaticColorCollection: ColorCollection {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var heading1: DownColor
|
||||
public var heading2: DownColor
|
||||
public var heading3: DownColor
|
||||
@@ -55,6 +58,8 @@ public struct StaticColorCollection: ColorCollection {
|
||||
public var listItemPrefix: DownColor
|
||||
public var codeBlockBackground: DownColor
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(
|
||||
heading1: DownColor = .black,
|
||||
heading2: DownColor = .black,
|
||||
@@ -86,6 +91,7 @@ public struct StaticColorCollection: ColorCollection {
|
||||
self.listItemPrefix = listItemPrefix
|
||||
self.codeBlockBackground = codeBlockBackground
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,10 +31,13 @@ public protocol FontCollection {
|
||||
var body: DownFont { get }
|
||||
var code: DownFont { get }
|
||||
var listItemPrefix: DownFont { get }
|
||||
|
||||
}
|
||||
|
||||
public struct StaticFontCollection: FontCollection {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var heading1: DownFont
|
||||
public var heading2: DownFont
|
||||
public var heading3: DownFont
|
||||
@@ -45,6 +48,8 @@ public struct StaticFontCollection: FontCollection {
|
||||
public var code: DownFont
|
||||
public var listItemPrefix: DownFont
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(
|
||||
heading1: DownFont = .boldSystemFont(ofSize: 28),
|
||||
heading2: DownFont = .boldSystemFont(ofSize: 24),
|
||||
@@ -66,6 +71,7 @@ public struct StaticFontCollection: FontCollection {
|
||||
self.code = code
|
||||
self.listItemPrefix = listItemPrefix
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,10 +28,13 @@ public protocol ParagraphStyleCollection {
|
||||
var heading6: NSParagraphStyle { get }
|
||||
var body: NSParagraphStyle { get }
|
||||
var code: NSParagraphStyle { get }
|
||||
|
||||
}
|
||||
|
||||
public struct StaticParagraphStyleCollection: ParagraphStyleCollection {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var heading1: NSParagraphStyle
|
||||
public var heading2: NSParagraphStyle
|
||||
public var heading3: NSParagraphStyle
|
||||
@@ -41,6 +44,8 @@ public struct StaticParagraphStyleCollection: ParagraphStyleCollection {
|
||||
public var body: NSParagraphStyle
|
||||
public var code: NSParagraphStyle
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init() {
|
||||
let headingStyle = NSMutableParagraphStyle()
|
||||
headingStyle.paragraphSpacing = 8
|
||||
@@ -63,6 +68,7 @@ public struct StaticParagraphStyleCollection: ParagraphStyleCollection {
|
||||
body = bodyStyle
|
||||
code = codeStyle
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,13 +20,17 @@ import AppKit
|
||||
|
||||
struct BlockBackgroundColorAttribute {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var color: DownColor
|
||||
var inset: CGFloat
|
||||
|
||||
}
|
||||
|
||||
extension NSAttributedString.Key {
|
||||
|
||||
static let blockBackgroundColor = NSAttributedString.Key("blockBackgroundColor")
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,6 +20,8 @@ import AppKit
|
||||
|
||||
struct QuoteStripeAttribute {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var color: DownColor
|
||||
var thickness: CGFloat
|
||||
var spacingAfter: CGFloat
|
||||
@@ -28,25 +30,35 @@ struct QuoteStripeAttribute {
|
||||
var layoutWidth: CGFloat {
|
||||
return thickness + spacingAfter
|
||||
}
|
||||
}
|
||||
|
||||
extension QuoteStripeAttribute {
|
||||
// MARK: - Life cycle
|
||||
|
||||
init(color: DownColor, thickness: CGFloat, spacingAfter: CGFloat, locations: [CGFloat]) {
|
||||
self.color = color
|
||||
self.thickness = thickness
|
||||
self.spacingAfter = spacingAfter
|
||||
self.locations = locations
|
||||
}
|
||||
|
||||
init(level: Int, color: DownColor, options: QuoteStripeOptions) {
|
||||
self.init(color: color, thickness: options.thickness, spacingAfter: options.spacingAfter, locations: [])
|
||||
locations = (0..<level).map { CGFloat($0) * layoutWidth }
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
func indented(by indentation: CGFloat) -> QuoteStripeAttribute {
|
||||
var copy = self
|
||||
copy.locations = locations.map { $0 + indentation }
|
||||
return copy
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension NSAttributedString.Key {
|
||||
|
||||
|
||||
static let quoteStripe = NSAttributedString.Key(rawValue: "quoteStripe")
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,13 +20,17 @@ import AppKit
|
||||
|
||||
struct ThematicBreakAttribute {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var thickness: CGFloat
|
||||
var color: DownColor
|
||||
|
||||
}
|
||||
|
||||
extension NSAttributedString.Key {
|
||||
|
||||
|
||||
static let thematicBreak = NSAttributedString.Key(rawValue: "thematicBreak")
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,7 @@ extension CGPoint {
|
||||
func translated(by point: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: x + point.x, y: y + point.y)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,6 +27,7 @@ extension CGRect {
|
||||
func translated(by point: CGPoint) -> CGRect {
|
||||
return CGRect(origin: origin.translated(by: point), size: size)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,6 +12,8 @@ extension NSAttributedString {
|
||||
|
||||
typealias Attributes = [NSAttributedString.Key: Any]
|
||||
|
||||
// MARK: - Ranges
|
||||
|
||||
var wholeRange: NSRange {
|
||||
return NSRange(location: 0, length: length)
|
||||
}
|
||||
@@ -32,11 +34,11 @@ extension NSAttributedString {
|
||||
return ranges(for: key, in: range, where: { $0 == nil })
|
||||
}
|
||||
|
||||
private func ranges(for key: Key, in range: NSRange, where p: (Any?) -> Bool) -> [NSRange] {
|
||||
private func ranges(for key: Key, in range: NSRange, where predicate: (Any?) -> Bool) -> [NSRange] {
|
||||
var ranges = [NSRange]()
|
||||
|
||||
enumerateAttribute(key, in: range, options: []) { value, attrRange, _ in
|
||||
if p(value) {
|
||||
if predicate(value) {
|
||||
ranges.append(attrRange)
|
||||
}
|
||||
}
|
||||
@@ -60,6 +62,8 @@ extension NSAttributedString {
|
||||
return result.filter { $0.length > 1 }
|
||||
}
|
||||
|
||||
// MARK: - Enumerate attributes
|
||||
|
||||
func enumerateAttributes<A>(for key: Key, block: (_ attr: A, _ range: NSRange) -> Void) {
|
||||
enumerateAttributes(for: key, in: wholeRange, block: block)
|
||||
}
|
||||
@@ -71,4 +75,5 @@ extension NSAttributedString {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-4
@@ -35,14 +35,14 @@ extension NSMutableAttributedString {
|
||||
addAttribute(key, value: value, range: range)
|
||||
}
|
||||
|
||||
func updateExistingAttributes<A>(for key: Key, using f: (A) -> A) {
|
||||
updateExistingAttributes(for: key, in: wholeRange, using: f)
|
||||
func updateExistingAttributes<A>(for key: Key, using transform: (A) -> A) {
|
||||
updateExistingAttributes(for: key, in: wholeRange, using: transform)
|
||||
}
|
||||
|
||||
func updateExistingAttributes<A>(for key: Key, in range: NSRange, using f: (A) -> A) {
|
||||
func updateExistingAttributes<A>(for key: Key, in range: NSRange, using transform: (A) -> A) {
|
||||
var existingValues = [(value: A, range: NSRange)]()
|
||||
enumerateAttributes(for: key, in: range) { existingValues.append(($0, $1)) }
|
||||
existingValues.forEach { addAttribute(key, value: f($0.0), range: $0.1) }
|
||||
existingValues.forEach { addAttribute(key, value: transform($0.0), range: $0.1) }
|
||||
}
|
||||
|
||||
func addAttributeInMissingRanges<A>(for key: Key, value: A) {
|
||||
@@ -54,4 +54,5 @@ extension NSMutableAttributedString {
|
||||
addAttribute(key, value: value, range: $0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ extension DownFont {
|
||||
private func contains(_ trait: DownFontDescriptor.SymbolicTraits) -> Bool {
|
||||
return fontDescriptor.symbolicTraits.contains(trait)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
@@ -75,6 +76,7 @@ private extension DownFontDescriptor.SymbolicTraits {
|
||||
static let strong = DownFontDescriptor.SymbolicTraits.traitBold
|
||||
static let emphasis = DownFontDescriptor.SymbolicTraits.traitItalic
|
||||
static let monoSpace = DownFontDescriptor.SymbolicTraits.traitMonoSpace
|
||||
|
||||
}
|
||||
|
||||
#elseif canImport(AppKit)
|
||||
@@ -84,6 +86,7 @@ private extension DownFontDescriptor.SymbolicTraits {
|
||||
static let strong = DownFontDescriptor.SymbolicTraits.bold
|
||||
static let emphasis = DownFontDescriptor.SymbolicTraits.italic
|
||||
static let monoSpace = DownFontDescriptor.SymbolicTraits.monoSpace
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,13 +20,17 @@ import AppKit
|
||||
|
||||
/// A convenient class used to format lists, such that list item prefixes
|
||||
/// are right aligned and list item content left aligns.
|
||||
|
||||
public class ListItemParagraphStyler {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var indentation: CGFloat {
|
||||
return largestPrefixWidth + options.spacingAfterPrefix
|
||||
}
|
||||
|
||||
/// The paragraph style intended for all paragraphs excluding the first.
|
||||
|
||||
public var trailingParagraphStyle: NSParagraphStyle {
|
||||
let contentIndentation = indentation
|
||||
let style = baseStyle
|
||||
@@ -42,18 +46,23 @@ public class ListItemParagraphStyler {
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.paragraphSpacingBefore = options.spacingAbove
|
||||
style.paragraphSpacing = options.spacingBelow
|
||||
style.alignment = options.alignment
|
||||
return style
|
||||
}
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(options: ListItemOptions, prefixFont: DownFont) {
|
||||
self.options = options
|
||||
self.largestPrefixWidth = prefixFont.widthOfNumberedPrefix(digits: options.maxPrefixDigits)
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
/// The paragraph style intended for the first paragraph of the list item.
|
||||
///
|
||||
/// - Parameter prefixWidth: the width (in points) of the list item prefix.
|
||||
|
||||
public func leadingParagraphStyle(prefixWidth: CGFloat) -> NSParagraphStyle {
|
||||
let contentIndentation = indentation
|
||||
let prefixIndentation: CGFloat = contentIndentation - options.spacingAfterPrefix - prefixWidth
|
||||
@@ -68,8 +77,9 @@ public class ListItemParagraphStyler {
|
||||
}
|
||||
|
||||
private func tabStop(at location: CGFloat) -> NSTextTab {
|
||||
return NSTextTab(textAlignment: .left, location: location, options: [:])
|
||||
return NSTextTab(textAlignment: options.alignment, location: location, options: [:])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
@@ -91,6 +101,7 @@ private extension DownFont {
|
||||
.size()
|
||||
.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Int {
|
||||
@@ -98,6 +109,7 @@ private extension Int {
|
||||
static var decimalDigits: [Int] {
|
||||
return Array(0...9)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,15 +31,18 @@ import AppKit
|
||||
/// of a `DownStyler`.
|
||||
///
|
||||
/// Insert this into a TextKit stack manually, or use the provided `DownDebugTextView`.
|
||||
|
||||
public class DownDebugLayoutManager: DownLayoutManager {
|
||||
|
||||
// MARK: - Drawing
|
||||
|
||||
override public func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
|
||||
super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)
|
||||
drawLineFragments(forGlyphRange: glyphsToShow, at: origin)
|
||||
}
|
||||
|
||||
private func drawLineFragments(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
|
||||
enumerateLineFragments(forGlyphRange: glyphsToShow) { rect, usedRect, textContainer, glyphRange, _ in
|
||||
enumerateLineFragments(forGlyphRange: glyphsToShow) { rect, usedRect, _, _, _ in
|
||||
[(usedRect, DownColor.blue), (rect, DownColor.red)].forEach { rectToDraw, color in
|
||||
let adjustedRect = rectToDraw.translated(by: origin)
|
||||
self.drawRect(adjustedRect, color: color.cgColor)
|
||||
@@ -55,6 +58,7 @@ public class DownDebugLayoutManager: DownLayoutManager {
|
||||
context.setStrokeColor(color)
|
||||
context.stroke(rect)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,8 +21,11 @@ import AppKit
|
||||
/// A layout manager capable of drawing the custom attributes set by the `DownStyler`.
|
||||
///
|
||||
/// Insert this into a TextKit stack manually, or use the provided `DownTextView`.
|
||||
|
||||
public class DownLayoutManager: NSLayoutManager {
|
||||
|
||||
// MARK: - Graphic context
|
||||
|
||||
#if canImport(UIKit)
|
||||
var context: CGContext? {
|
||||
return UIGraphicsGetCurrentContext()
|
||||
@@ -51,13 +54,15 @@ public class DownLayoutManager: NSLayoutManager {
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - Drawing
|
||||
|
||||
override public func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
|
||||
drawCustomBackgrounds(forGlyphRange: glyphsToShow, at: origin)
|
||||
super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)
|
||||
drawCustomAttributes(forGlyphRange: glyphsToShow, at: origin)
|
||||
}
|
||||
|
||||
private func drawCustomBackgrounds(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
|
||||
private func drawCustomBackgrounds(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
|
||||
guard let context = context else { return }
|
||||
push(context: context)
|
||||
defer { popContext() }
|
||||
@@ -66,17 +71,19 @@ public class DownLayoutManager: NSLayoutManager {
|
||||
|
||||
let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
|
||||
|
||||
textStorage.enumerateAttributes(for: .blockBackgroundColor, in: characterRange) { (attr: BlockBackgroundColorAttribute, blockRange) in
|
||||
|
||||
textStorage.enumerateAttributes(for: .blockBackgroundColor,
|
||||
in: characterRange) { (attr: BlockBackgroundColorAttribute, blockRange) in
|
||||
let inset = attr.inset
|
||||
|
||||
context.setFillColor(attr.color.cgColor)
|
||||
|
||||
let allBlockColorRanges = glyphRanges(for: .blockBackgroundColor, in: textStorage, inCharacterRange: blockRange)
|
||||
let blockColorGlyphRange = glyphRange(forCharacterRange: blockRange, actualCharacterRange: nil)
|
||||
let allBlockColorRanges = glyphRanges(for: .blockBackgroundColor,
|
||||
in: textStorage,
|
||||
inCharacterRange: blockRange)
|
||||
|
||||
enumerateLineFragments(forGlyphRange: blockColorGlyphRange) { lineRect, lineUsedRect, container, lineGlyphRange, _ in
|
||||
let glyphRange = self.glyphRange(forCharacterRange: blockRange, actualCharacterRange: nil)
|
||||
|
||||
enumerateLineFragments(forGlyphRange: glyphRange) { lineRect, lineUsedRect, container, lineGlyphRange, _ in
|
||||
let isLineStartOfBlock = allBlockColorRanges.contains {
|
||||
lineGlyphRange.overlapsStart(of: $0)
|
||||
}
|
||||
@@ -89,7 +96,7 @@ public class DownLayoutManager: NSLayoutManager {
|
||||
let maxX = lineRect.maxX
|
||||
let minY = isLineStartOfBlock ? lineUsedRect.minY - inset : lineRect.minY
|
||||
let maxY = isLineEndOfBlock ? lineUsedRect.maxY + inset : lineUsedRect.maxY
|
||||
let blockRect = CGRect(minX: minX, minY: minY, maxX: maxX, maxY: maxY).translated(by: origin)
|
||||
let blockRect = CGRect(minX: minX, minY: minY, maxX: maxX, maxY: maxY).translated(by: origin)
|
||||
|
||||
context.fill(blockRect)
|
||||
}
|
||||
@@ -107,7 +114,9 @@ public class DownLayoutManager: NSLayoutManager {
|
||||
push(context: context)
|
||||
defer { popContext() }
|
||||
|
||||
textStorage?.enumerateAttributes(for: .thematicBreak, in: characterRange) { (attr: ThematicBreakAttribute, range) in
|
||||
textStorage?.enumerateAttributes(for: .thematicBreak,
|
||||
in: characterRange) { (attr: ThematicBreakAttribute, range) in
|
||||
|
||||
let firstGlyphIndex = glyphIndexForCharacter(at: range.lowerBound)
|
||||
|
||||
let lineRect = lineFragmentRect(forGlyphAt: firstGlyphIndex, effectiveRange: nil)
|
||||
@@ -115,7 +124,10 @@ public class DownLayoutManager: NSLayoutManager {
|
||||
|
||||
let lineStart = usedRect.minX + fragmentPadding(forGlyphAt: firstGlyphIndex)
|
||||
|
||||
let boundingRect = CGRect(x: lineStart, y: lineRect.minY, width: lineRect.width - lineStart, height: lineRect.height)
|
||||
let width = lineRect.width - lineStart
|
||||
let height = lineRect.height
|
||||
|
||||
let boundingRect = CGRect(x: lineStart, y: lineRect.minY, width: width, height: height)
|
||||
let adjustedLineRect = boundingRect.translated(by: origin)
|
||||
|
||||
drawThematicBreak(with: context, in: adjustedLineRect, attr: attr)
|
||||
@@ -140,7 +152,9 @@ public class DownLayoutManager: NSLayoutManager {
|
||||
push(context: context)
|
||||
defer { popContext() }
|
||||
|
||||
textStorage?.enumerateAttributes(for: .quoteStripe, in: characterRange) { (attr: QuoteStripeAttribute, quoteRange) in
|
||||
textStorage?.enumerateAttributes(for: .quoteStripe,
|
||||
in: characterRange) { (attr: QuoteStripeAttribute, quoteRange) in
|
||||
|
||||
context.setFillColor(attr.color.cgColor)
|
||||
|
||||
let glyphRangeOfQuote = self.glyphRange(forCharacterRange: quoteRange, actualCharacterRange: nil)
|
||||
@@ -165,7 +179,10 @@ public class DownLayoutManager: NSLayoutManager {
|
||||
}
|
||||
}
|
||||
|
||||
private func glyphRanges(for key: NSAttributedString.Key, in storage: NSTextStorage, inCharacterRange range: NSRange) -> [NSRange] {
|
||||
private func glyphRanges(for key: NSAttributedString.Key,
|
||||
in storage: NSTextStorage,
|
||||
inCharacterRange range: NSRange) -> [NSRange] {
|
||||
|
||||
return storage
|
||||
.ranges(of: key, in: range)
|
||||
.map { self.glyphRange(forCharacterRange: $0, actualCharacterRange: nil) }
|
||||
@@ -184,6 +201,7 @@ private extension NSRange {
|
||||
func overlapsEnd(of range: NSRange) -> Bool {
|
||||
return lowerBound < range.upperBound && upperBound >= range.upperBound
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Array where Element == NSRange {
|
||||
@@ -207,6 +225,7 @@ private extension Array where Element == NSRange {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,11 +20,16 @@ import AppKit
|
||||
|
||||
public struct CodeBlockOptions {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var containerInset: CGFloat
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(containerInset: CGFloat = 8) {
|
||||
self.containerInset = containerInset
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,21 +20,29 @@ import AppKit
|
||||
|
||||
public struct ListItemOptions {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var maxPrefixDigits: UInt
|
||||
public var spacingAfterPrefix: CGFloat
|
||||
public var spacingAbove: CGFloat
|
||||
public var spacingBelow: CGFloat
|
||||
public var alignment: NSTextAlignment
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(maxPrefixDigits: UInt = 2,
|
||||
spacingAfterPrefix: CGFloat = 8,
|
||||
spacingAbove: CGFloat = 4,
|
||||
spacingBelow: CGFloat = 8
|
||||
) {
|
||||
spacingBelow: CGFloat = 8,
|
||||
alignment: NSTextAlignment = .natural) {
|
||||
|
||||
self.maxPrefixDigits = maxPrefixDigits
|
||||
self.spacingAfterPrefix = spacingAfterPrefix
|
||||
self.spacingAbove = spacingAbove
|
||||
self.spacingBelow = spacingBelow
|
||||
self.alignment = alignment
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,13 +20,18 @@ import AppKit
|
||||
|
||||
public struct QuoteStripeOptions {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var thickness: CGFloat
|
||||
public var spacingAfter: CGFloat
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(thickness: CGFloat = 2, spacingAfter: CGFloat = 8) {
|
||||
self.thickness = thickness
|
||||
self.spacingAfter = spacingAfter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,13 +20,18 @@ import AppKit
|
||||
|
||||
public struct ThematicBreakOptions {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var thickness: CGFloat
|
||||
public var indentation: CGFloat
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(thickness: CGFloat = 1, indentation: CGFloat = 0) {
|
||||
self.thickness = thickness
|
||||
self.indentation = indentation
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,6 +21,7 @@ import AppKit
|
||||
/// A default `Styler` implementation that supports a variety of configurable
|
||||
/// properties for font, text color and paragraph styling, as well as formatting
|
||||
/// of nested lists and quotes.
|
||||
|
||||
open class DownStyler: Styler {
|
||||
|
||||
// MARK: - Properties
|
||||
@@ -35,12 +36,12 @@ open class DownStyler: Styler {
|
||||
|
||||
private let itemParagraphStyler: ListItemParagraphStyler
|
||||
|
||||
private var listPrefixAttributes: [NSAttributedString.Key : Any] {[
|
||||
private var listPrefixAttributes: [NSAttributedString.Key: Any] {[
|
||||
.font: fonts.listItemPrefix,
|
||||
.foregroundColor: colors.listItemPrefix]
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(configuration: DownStylerConfiguration = DownStylerConfiguration()) {
|
||||
fonts = configuration.fonts
|
||||
@@ -49,7 +50,8 @@ open class DownStyler: Styler {
|
||||
quoteStripeOptions = configuration.quoteStripeOptions
|
||||
thematicBreakOptions = configuration.thematicBreakOptions
|
||||
codeBlockOptions = configuration.codeBlockOptions
|
||||
itemParagraphStyler = ListItemParagraphStyler(options: configuration.listItemOptions, prefixFont: fonts.listItemPrefix)
|
||||
itemParagraphStyler = ListItemParagraphStyler(options: configuration.listItemOptions,
|
||||
prefixFont: fonts.listItemPrefix)
|
||||
}
|
||||
|
||||
// MARK: - Styling
|
||||
@@ -59,7 +61,9 @@ open class DownStyler: Styler {
|
||||
}
|
||||
|
||||
open func style(blockQuote str: NSMutableAttributedString, nestDepth: Int) {
|
||||
let stripeAttribute = QuoteStripeAttribute(level: nestDepth + 1, color: colors.quoteStripe, options: quoteStripeOptions)
|
||||
let stripeAttribute = QuoteStripeAttribute(level: nestDepth + 1,
|
||||
color: colors.quoteStripe,
|
||||
options: quoteStripeOptions)
|
||||
|
||||
str.updateExistingAttributes(for: .paragraphStyle) { (style: NSParagraphStyle) in
|
||||
style.indented(by: stripeAttribute.layoutWidth)
|
||||
@@ -82,7 +86,7 @@ open class DownStyler: Styler {
|
||||
|
||||
guard let leadingParagraphRange = paragraphRanges.first else { return }
|
||||
|
||||
indentListItemLeadingParagraph(in: str, prefixLength: prefixLength, inRange: leadingParagraphRange)
|
||||
indentListItemLeadingParagraph(in: str, prefixLength: prefixLength, in: leadingParagraphRange)
|
||||
|
||||
paragraphRanges.dropFirst().forEach {
|
||||
indentListItemTrailingParagraph(in: str, inRange: $0)
|
||||
@@ -111,15 +115,15 @@ open class DownStyler: Styler {
|
||||
str.updateExistingAttributes(for: .font) { (currentFont: DownFont) in
|
||||
var newFont = font
|
||||
|
||||
if (currentFont.isMonospace) {
|
||||
if currentFont.isMonospace {
|
||||
newFont = newFont.monospace
|
||||
}
|
||||
|
||||
if (currentFont.isEmphasized) {
|
||||
|
||||
if currentFont.isEmphasized {
|
||||
newFont = newFont.emphasis
|
||||
}
|
||||
|
||||
if (currentFont.isStrong) {
|
||||
if currentFont.isStrong {
|
||||
newFont = newFont.strong
|
||||
}
|
||||
|
||||
@@ -229,7 +233,10 @@ open class DownStyler: Styler {
|
||||
}
|
||||
}
|
||||
|
||||
private func indentListItemLeadingParagraph(in str: NSMutableAttributedString, prefixLength: Int, inRange range: NSRange) {
|
||||
private func indentListItemLeadingParagraph(in str: NSMutableAttributedString,
|
||||
prefixLength: Int,
|
||||
in range: NSRange) {
|
||||
|
||||
str.updateExistingAttributes(for: .paragraphStyle, in: range) { (existingStyle: NSParagraphStyle) in
|
||||
existingStyle.indented(by: itemParagraphStyler.indentation)
|
||||
}
|
||||
@@ -265,7 +272,7 @@ open class DownStyler: Styler {
|
||||
private extension NSParagraphStyle {
|
||||
|
||||
func indented(by indentation: CGFloat) -> NSParagraphStyle {
|
||||
let result = mutableCopy() as! NSMutableParagraphStyle
|
||||
guard let result = mutableCopy() as? NSMutableParagraphStyle else { return self }
|
||||
result.firstLineHeadIndent += indentation
|
||||
result.headIndent += indentation
|
||||
|
||||
@@ -277,7 +284,7 @@ private extension NSParagraphStyle {
|
||||
}
|
||||
|
||||
func inset(by amount: CGFloat) -> NSParagraphStyle {
|
||||
let result = mutableCopy() as! NSMutableParagraphStyle
|
||||
guard let result = mutableCopy() as? NSMutableParagraphStyle else { return self }
|
||||
result.paragraphSpacingBefore += amount
|
||||
result.paragraphSpacing += amount
|
||||
result.firstLineHeadIndent += amount
|
||||
@@ -285,6 +292,7 @@ private extension NSParagraphStyle {
|
||||
result.tailIndent = -amount
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension NSAttributedString {
|
||||
@@ -292,8 +300,9 @@ private extension NSAttributedString {
|
||||
func prefix(with length: Int) -> NSAttributedString {
|
||||
guard length <= self.length else { return self }
|
||||
guard length > 0 else { return NSAttributedString() }
|
||||
return attributedSubstring(from: NSMakeRange(0, length))
|
||||
return attributedSubstring(from: NSRange(location: 0, length: length))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,17 +9,22 @@
|
||||
#if !os(watchOS) && !os(Linux)
|
||||
|
||||
/// A configuration object used to initialze the `DownStyler`.
|
||||
|
||||
public struct DownStylerConfiguration {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var fonts: FontCollection
|
||||
public var colors: ColorCollection
|
||||
public var paragraphStyles: ParagraphStyleCollection
|
||||
|
||||
|
||||
public var listItemOptions: ListItemOptions
|
||||
public var quoteStripeOptions: QuoteStripeOptions
|
||||
public var thematicBreakOptions: ThematicBreakOptions
|
||||
public var codeBlockOptions: CodeBlockOptions
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(fonts: FontCollection = StaticFontCollection(),
|
||||
colors: ColorCollection = StaticColorCollection(),
|
||||
paragraphStyles: ParagraphStyleCollection = StaticParagraphStyleCollection(),
|
||||
@@ -36,6 +41,7 @@ public struct DownStylerConfiguration {
|
||||
self.thematicBreakOptions = thematicBreakOptions
|
||||
self.codeBlockOptions = codeBlockOptions
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,147 +18,172 @@ import Foundation
|
||||
///
|
||||
/// A styler is used in conjunction with an instance of `AttributedStringVisitor` in order
|
||||
/// to generate an NSAttributedString from an abstract syntax tree.
|
||||
|
||||
public protocol Styler {
|
||||
|
||||
/// Styles the content of the document in the given string.
|
||||
///
|
||||
/// - Parameter str: the document content.
|
||||
func style(document str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the document content.
|
||||
|
||||
func style(document str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the block quote contained in the given string.
|
||||
///
|
||||
/// - Parameter str: the quote content.
|
||||
/// - Parameter nestDepth: the zero indexed nesting depth of the block quote node.
|
||||
func style(blockQuote str: NSMutableAttributedString, nestDepth: Int)
|
||||
/// - Parameters:
|
||||
/// - str: the quote content.
|
||||
/// - nestDepth: the zero indexed nesting depth of the block quote node.
|
||||
|
||||
func style(blockQuote str: NSMutableAttributedString, nestDepth: Int)
|
||||
|
||||
/// Styles the content of the list contained in the given string.
|
||||
///
|
||||
/// - Parameter str: the list content.
|
||||
/// - Parameter nestDepth: the zero indexed nesting depth of the list node.
|
||||
func style(list str: NSMutableAttributedString, nestDepth: Int)
|
||||
/// - Parameters:
|
||||
/// - str: the list content.
|
||||
/// - nestDepth: the zero indexed nesting depth of the list node.
|
||||
|
||||
func style(list str: NSMutableAttributedString, nestDepth: Int)
|
||||
|
||||
/// Styles the number or bullet list item prefix.
|
||||
///
|
||||
/// - Parameter str: the list item prefix.
|
||||
func style(listItemPrefix str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the list item prefix.
|
||||
|
||||
func style(listItemPrefix str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the list item contained in the given string, including the
|
||||
/// number or bullet prefix.
|
||||
///
|
||||
/// - Parameter str: the item content.
|
||||
/// - Parameter prefixLength: the character length of the number or bullet prefix.
|
||||
func style(item str: NSMutableAttributedString, prefixLength: Int)
|
||||
/// - Parameters:
|
||||
/// - str: the item content.
|
||||
/// - prefixLength: the character length of the number or bullet prefix.
|
||||
|
||||
func style(item str: NSMutableAttributedString, prefixLength: Int)
|
||||
|
||||
/// Styles the content of the code block in the given string.
|
||||
///
|
||||
/// An example use case for `fenceInfo` is to specify a programming language name,
|
||||
/// which could be used to support syntax highlighting.
|
||||
///
|
||||
/// - Parameter str: the code content.
|
||||
/// - Parameter fenceInfo: the string that trails the initial ``` ticks.
|
||||
func style(codeBlock str: NSMutableAttributedString, fenceInfo: String?)
|
||||
/// - Parameters:
|
||||
/// - str: the code content.
|
||||
/// - fenceInfo: the string that trails the initial \`\`\` ticks.
|
||||
|
||||
func style(codeBlock str: NSMutableAttributedString, fenceInfo: String?)
|
||||
|
||||
/// Styles the content of the html block contained in the given string.
|
||||
///
|
||||
/// - Parameter str: the html content.
|
||||
func style(htmlBlock str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the html content.
|
||||
|
||||
func style(htmlBlock str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the custom block contained in the given string.
|
||||
///
|
||||
/// - Parameter str: the content.
|
||||
func style(customBlock str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the content.
|
||||
|
||||
func style(customBlock str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the paragraph in the given string.
|
||||
///
|
||||
/// - Parameter str: the paragraph content.
|
||||
func style(paragraph str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the paragraph content.
|
||||
|
||||
func style(paragraph str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the heading in the given string.
|
||||
///
|
||||
/// - Parameter str: the heading content.
|
||||
/// - Parameter level: the heading level [1, 6]
|
||||
func style(heading str: NSMutableAttributedString, level: Int)
|
||||
/// - Parameters:
|
||||
/// - str: the heading content.
|
||||
/// - level: the heading level [1, 6]
|
||||
|
||||
func style(heading str: NSMutableAttributedString, level: Int)
|
||||
|
||||
/// Styles the content of the thematic break in the given string.
|
||||
///
|
||||
/// - Parameter str: the thematic break.
|
||||
func style(thematicBreak str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the thematic break.
|
||||
|
||||
func style(thematicBreak str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the inline text node in the given string.
|
||||
///
|
||||
/// The text nodes are always the leaves of the AST, thus they
|
||||
/// contain the base style upon which other nodes can work with.
|
||||
///
|
||||
/// - Parameter str: the text content.
|
||||
func style(text str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the text content.
|
||||
|
||||
func style(text str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the soft break in the given string.
|
||||
///
|
||||
/// - Parameter str: the soft break.
|
||||
func style(softBreak str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the soft break.
|
||||
|
||||
func style(softBreak str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the line break in the given string.
|
||||
///
|
||||
/// - Parameter str: the line break.
|
||||
func style(lineBreak str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the line break.
|
||||
|
||||
func style(lineBreak str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the inline code in the given string.
|
||||
///
|
||||
/// - Parameter str: the code content.
|
||||
func style(code str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the code content.
|
||||
|
||||
func style(code str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the inline html tags in the given string.
|
||||
///
|
||||
/// Note, the content does not include text between matching tags.
|
||||
///
|
||||
/// - Parameter str: the html content.
|
||||
func style(htmlInline str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the html content.
|
||||
|
||||
func style(htmlInline str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the inline custom node in the given string.
|
||||
///
|
||||
/// - Parameter str: the custom content.
|
||||
func style(customInline str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the custom content.
|
||||
|
||||
func style(customInline str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the inline emphasis node in the given string.
|
||||
///
|
||||
/// - Parameter str: the ephasized content.
|
||||
func style(emphasis str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the ephasized content.
|
||||
|
||||
func style(emphasis str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the inline strong node in the given string.
|
||||
///
|
||||
/// - Parameter str: the strong content.
|
||||
func style(strong str: NSMutableAttributedString)
|
||||
/// - Parameters:
|
||||
/// - str: the strong content.
|
||||
|
||||
func style(strong str: NSMutableAttributedString)
|
||||
|
||||
/// Styles the content of the inline link node in the given string.
|
||||
///
|
||||
/// - Parameter str: the link content.
|
||||
/// - Parameter title: the link title.
|
||||
/// - Parameter url: the linked url.
|
||||
/// - Parameters:
|
||||
/// - str: the link content.
|
||||
/// - title: the link title.
|
||||
/// - url: the linked url.
|
||||
|
||||
func style(link str: NSMutableAttributedString, title: String?, url: String?)
|
||||
|
||||
/// Styles the content of the inline image node in the given string.
|
||||
///
|
||||
/// - Parameter str: the link content.
|
||||
/// - Parameter title: the link title.
|
||||
/// - Parameter url: the linked url.
|
||||
/// - Parameters:
|
||||
/// - str: the link content.
|
||||
/// - title: the link title.
|
||||
/// - url: the linked url.
|
||||
|
||||
func style(image str: NSMutableAttributedString, title: String?, url: String?)
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,11 @@ import AppKit
|
||||
/// A text view capable of parsing and rendering markdown via the AST, as well as line fragments.
|
||||
///
|
||||
/// See `DownDebugLayoutManager`.
|
||||
|
||||
public class DownDebugTextView: DownTextView {
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(frame: CGRect, styler: Styler = DownStyler()) {
|
||||
super.init(frame: frame, styler: styler, layoutManager: DownDebugLayoutManager())
|
||||
}
|
||||
@@ -30,6 +33,7 @@ public class DownDebugTextView: DownTextView {
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,11 +21,16 @@ public typealias TextView = NSTextView
|
||||
#endif
|
||||
|
||||
/// A text view capable of parsing and rendering markdown via the AST.
|
||||
|
||||
open class DownTextView: TextView {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
open var styler: Styler
|
||||
open var styler: Styler {
|
||||
didSet {
|
||||
try? render()
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
|
||||
@@ -40,15 +45,14 @@ open class DownTextView: TextView {
|
||||
|
||||
open override var string: String {
|
||||
didSet {
|
||||
guard oldValue != string else { return }
|
||||
guard oldValue != string else { return }
|
||||
try? render()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
// MARK: - Life cycle
|
||||
|
||||
public convenience init(frame: CGRect, styler: Styler = DownStyler()) {
|
||||
self.init(frame: frame, styler: styler, layoutManager: DownLayoutManager())
|
||||
@@ -90,6 +94,7 @@ open class DownTextView: TextView {
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,10 +13,16 @@ import Foundation
|
||||
/// tree produced by a markdown string. It traverses the tree to construct substrings
|
||||
/// represented at each node and uses an instance of `Styler` to apply the visual attributes.
|
||||
/// These substrings are joined together to produce the final result.
|
||||
|
||||
public typealias ListPrefixGeneratorBuilder = (List) -> ListItemPrefixGenerator
|
||||
|
||||
public class AttributedStringVisitor {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let styler: Styler
|
||||
private let options: DownOptions
|
||||
private let listPrefixGeneratorBuilder: ListPrefixGeneratorBuilder
|
||||
private var listPrefixGenerators = [ListItemPrefixGenerator]()
|
||||
|
||||
/// Creates a new instance with the given styler and options.
|
||||
@@ -24,153 +30,163 @@ public class AttributedStringVisitor {
|
||||
/// - parameters:
|
||||
/// - styler: used to style the markdown elements.
|
||||
/// - options: may be used to modify rendering.
|
||||
public init(styler: Styler, options: DownOptions = .default) {
|
||||
/// - listPrefixGeneratorBuilder: may be used to modify list prefixes.
|
||||
|
||||
public init(
|
||||
styler: Styler,
|
||||
options: DownOptions = .default,
|
||||
listPrefixGeneratorBuilder: @escaping ListPrefixGeneratorBuilder = { StaticListItemPrefixGenerator(list: $0) }
|
||||
) {
|
||||
self.styler = styler
|
||||
self.options = options
|
||||
self.listPrefixGeneratorBuilder = listPrefixGeneratorBuilder
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AttributedStringVisitor: Visitor {
|
||||
|
||||
public typealias Result = NSMutableAttributedString
|
||||
|
||||
|
||||
public func visit(document node: Document) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
styler.style(document: s)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
styler.style(document: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(blockQuote node: BlockQuote) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
if node.hasSuccessor { s.append(.paragraphSeparator) }
|
||||
styler.style(blockQuote: s, nestDepth: node.nestDepth)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
if node.hasSuccessor { result.append(.paragraphSeparator) }
|
||||
styler.style(blockQuote: result, nestDepth: node.nestDepth)
|
||||
return result
|
||||
}
|
||||
|
||||
public func visit(list node: List) -> NSMutableAttributedString {
|
||||
listPrefixGenerators.append(ListItemPrefixGenerator(list: node))
|
||||
|
||||
listPrefixGenerators.append(listPrefixGeneratorBuilder(node))
|
||||
defer { listPrefixGenerators.removeLast() }
|
||||
|
||||
let items = visitChildren(of: node)
|
||||
|
||||
let s = items.joined
|
||||
if node.hasSuccessor { s.append(.paragraphSeparator) }
|
||||
styler.style(list: s, nestDepth: node.nestDepth)
|
||||
return s
|
||||
let result = items.joined
|
||||
if node.hasSuccessor { result.append(.paragraphSeparator) }
|
||||
styler.style(list: result, nestDepth: node.nestDepth)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(item node: Item) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
let result = visitChildren(of: node).joined
|
||||
|
||||
let prefix = listPrefixGenerators.last?.next() ?? "•"
|
||||
let attributedPrefix = "\(prefix)\t".attributed
|
||||
styler.style(listItemPrefix: attributedPrefix)
|
||||
s.insert(attributedPrefix, at: 0)
|
||||
result.insert(attributedPrefix, at: 0)
|
||||
|
||||
if node.hasSuccessor { s.append(.paragraphSeparator) }
|
||||
styler.style(item: s, prefixLength: (prefix as NSString).length)
|
||||
return s
|
||||
if node.hasSuccessor { result.append(.paragraphSeparator) }
|
||||
styler.style(item: result, prefixLength: (prefix as NSString).length)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(codeBlock node: CodeBlock) -> NSMutableAttributedString {
|
||||
guard let literal = node.literal else { return .empty }
|
||||
let s = literal.replacingNewlinesWithLineSeparators().attributed
|
||||
if node.hasSuccessor { s.append(.paragraphSeparator) }
|
||||
styler.style(codeBlock: s, fenceInfo: node.fenceInfo)
|
||||
return s
|
||||
let result = literal.replacingNewlinesWithLineSeparators().attributed
|
||||
if node.hasSuccessor { result.append(.paragraphSeparator) }
|
||||
styler.style(codeBlock: result, fenceInfo: node.fenceInfo)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(htmlBlock node: HtmlBlock) -> NSMutableAttributedString {
|
||||
guard let literal = node.literal else { return .empty }
|
||||
let s = literal.replacingNewlinesWithLineSeparators().attributed
|
||||
if node.hasSuccessor { s.append(.paragraphSeparator) }
|
||||
styler.style(htmlBlock: s)
|
||||
return s
|
||||
let result = literal.replacingNewlinesWithLineSeparators().attributed
|
||||
if node.hasSuccessor { result.append(.paragraphSeparator) }
|
||||
styler.style(htmlBlock: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(customBlock node: CustomBlock) -> NSMutableAttributedString {
|
||||
guard let s = node.literal?.attributed else { return .empty }
|
||||
styler.style(customBlock: s)
|
||||
return s
|
||||
guard let result = node.literal?.attributed else { return .empty }
|
||||
styler.style(customBlock: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(paragraph node: Paragraph) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
if node.hasSuccessor { s.append(.paragraphSeparator) }
|
||||
styler.style(paragraph: s)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
if node.hasSuccessor { result.append(.paragraphSeparator) }
|
||||
styler.style(paragraph: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(heading node: Heading) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
if node.hasSuccessor { s.append(.paragraphSeparator) }
|
||||
styler.style(heading: s, level: node.headingLevel)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
if node.hasSuccessor { result.append(.paragraphSeparator) }
|
||||
styler.style(heading: result, level: node.headingLevel)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(thematicBreak node: ThematicBreak) -> NSMutableAttributedString {
|
||||
let s = "\(String.zeroWidthSpace)\n".attributed
|
||||
styler.style(thematicBreak: s)
|
||||
return s
|
||||
let result = "\(String.zeroWidthSpace)\n".attributed
|
||||
styler.style(thematicBreak: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(text node: Text) -> NSMutableAttributedString {
|
||||
guard let s = node.literal?.attributed else { return .empty }
|
||||
styler.style(text: s)
|
||||
return s
|
||||
guard let result = node.literal?.attributed else { return .empty }
|
||||
styler.style(text: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(softBreak node: SoftBreak) -> NSMutableAttributedString {
|
||||
let s = (options.contains(.hardBreaks) ? String.lineSeparator : " ").attributed
|
||||
styler.style(softBreak: s)
|
||||
return s
|
||||
let result = (options.contains(.hardBreaks) ? String.lineSeparator : " ").attributed
|
||||
styler.style(softBreak: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(lineBreak node: LineBreak) -> NSMutableAttributedString {
|
||||
let s = String.lineSeparator.attributed
|
||||
styler.style(lineBreak: s)
|
||||
return s
|
||||
let result = String.lineSeparator.attributed
|
||||
styler.style(lineBreak: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(code node: Code) -> NSMutableAttributedString {
|
||||
guard let s = node.literal?.attributed else { return .empty }
|
||||
styler.style(code: s)
|
||||
return s
|
||||
guard let result = node.literal?.attributed else { return .empty }
|
||||
styler.style(code: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(htmlInline node: HtmlInline) -> NSMutableAttributedString {
|
||||
guard let s = node.literal?.attributed else { return .empty }
|
||||
styler.style(htmlInline: s)
|
||||
return s
|
||||
guard let result = node.literal?.attributed else { return .empty }
|
||||
styler.style(htmlInline: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(customInline node: CustomInline) -> NSMutableAttributedString {
|
||||
guard let s = node.literal?.attributed else { return .empty }
|
||||
styler.style(customInline: s)
|
||||
return s
|
||||
guard let result = node.literal?.attributed else { return .empty }
|
||||
styler.style(customInline: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(emphasis node: Emphasis) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
styler.style(emphasis: s)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
styler.style(emphasis: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(strong node: Strong) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
styler.style(strong: s)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
styler.style(strong: result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(link node: Link) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
styler.style(link: s, title: node.title, url: node.url)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
styler.style(link: result, title: node.title, url: node.url)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
public func visit(image node: Image) -> NSMutableAttributedString {
|
||||
let s = visitChildren(of: node).joined
|
||||
styler.style(image: s, title: node.title, url: node.url)
|
||||
return s
|
||||
let result = visitChildren(of: node).joined
|
||||
styler.style(image: result, title: node.title, url: node.url)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +197,7 @@ private extension Sequence where Iterator.Element == NSMutableAttributedString {
|
||||
var joined: NSMutableAttributedString {
|
||||
return reduce(into: NSMutableAttributedString()) { $0.append($1) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension NSMutableAttributedString {
|
||||
@@ -188,6 +205,7 @@ private extension NSMutableAttributedString {
|
||||
static var empty: NSMutableAttributedString {
|
||||
return "".attributed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension NSAttributedString {
|
||||
@@ -195,6 +213,7 @@ private extension NSAttributedString {
|
||||
static var paragraphSeparator: NSAttributedString {
|
||||
return String.paragraphSeparator.attributed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension String {
|
||||
@@ -204,11 +223,13 @@ private extension String {
|
||||
}
|
||||
|
||||
// This codepoint marks the end of a paragraph and the start of the next.
|
||||
|
||||
static var paragraphSeparator: String {
|
||||
return "\u{2029}"
|
||||
}
|
||||
|
||||
// This code point allows line breaking, without starting a new paragraph.
|
||||
|
||||
static var lineSeparator: String {
|
||||
return "\u{2028}"
|
||||
}
|
||||
@@ -222,5 +243,7 @@ private extension String {
|
||||
let lines = trimmed.components(separatedBy: .newlines)
|
||||
return lines.joined(separator: .lineSeparator)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // !os(Linux)
|
||||
|
||||
@@ -9,16 +9,23 @@ import Foundation
|
||||
|
||||
/// This visitor will generate the debug description of an entire abstract syntax tree,
|
||||
/// indicating relationships between nodes with indentation.
|
||||
|
||||
public class DebugVisitor: Visitor {
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var depth = 0
|
||||
|
||||
private var indent: String {
|
||||
return String(repeating: " ", count: depth)
|
||||
}
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init() {}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func report(_ node: Node) -> String {
|
||||
return "\(indent)\(node is Document ? "" : "↳ ")\(String(reflecting: node))\n"
|
||||
}
|
||||
@@ -114,5 +121,5 @@ public class DebugVisitor: Visitor {
|
||||
public func visit(image node: Image) -> String {
|
||||
return reportWithChildren(node)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,28 +8,47 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class ListItemPrefixGenerator {
|
||||
/// A ListItemPrefixGenerator is an object used to generate list item prefix.
|
||||
public protocol ListItemPrefixGenerator {
|
||||
init(listType: List.ListType, numberOfItems: Int, nestDepth: Int)
|
||||
func next() -> String?
|
||||
}
|
||||
|
||||
public extension ListItemPrefixGenerator {
|
||||
init(list: List) {
|
||||
self.init(listType: list.listType, numberOfItems: list.numberOfItems, nestDepth: list.nestDepth)
|
||||
}
|
||||
}
|
||||
|
||||
/// Default implementation of `ListItemPrefixGenerator`.
|
||||
/// Generating the following symbol based on `List.ListType`:
|
||||
/// - List.ListType is bullet => "•"
|
||||
/// - List.ListType is ordered => "X." (where is the item number)
|
||||
public class StaticListItemPrefixGenerator: ListItemPrefixGenerator {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var prefixes: IndexingIterator<[String]>
|
||||
|
||||
convenience init(list: List) {
|
||||
self.init(listType: list.listType, numberOfItems: list.numberOfItems)
|
||||
}
|
||||
// MARK: - Life cycle
|
||||
|
||||
init(listType: List.ListType, numberOfItems: Int) {
|
||||
switch listType {
|
||||
case .bullet:
|
||||
prefixes = [String](repeating: "•", count: numberOfItems)
|
||||
.makeIterator()
|
||||
required public init(listType: List.ListType, numberOfItems: Int, nestDepth: Int) {
|
||||
switch listType {
|
||||
case .bullet:
|
||||
prefixes = [String](repeating: "•", count: numberOfItems)
|
||||
.makeIterator()
|
||||
|
||||
case .ordered(let start):
|
||||
prefixes = (start..<(start + numberOfItems))
|
||||
.map { "\($0)." }
|
||||
.makeIterator()
|
||||
case .ordered(let start):
|
||||
prefixes = (start..<(start + numberOfItems))
|
||||
.map { "\($0)." }
|
||||
.makeIterator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func next() -> String? {
|
||||
// MARK: - Methods
|
||||
|
||||
public func next() -> String? {
|
||||
prefixes.next()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ import Foundation
|
||||
/// each node of the tree and produces some result for that node. A visitor is "accepted" by
|
||||
/// the root node (of type `Document`), which will start the traversal by first invoking
|
||||
/// `visit(document:)`.
|
||||
|
||||
public protocol Visitor {
|
||||
|
||||
associatedtype Result
|
||||
|
||||
func visit(document node: Document) -> Result
|
||||
func visit(blockQuote node: BlockQuote) -> Result
|
||||
func visit(list node: List) -> Result
|
||||
@@ -34,9 +37,11 @@ public protocol Visitor {
|
||||
func visit(link node: Link) -> Result
|
||||
func visit(image node: Image) -> Result
|
||||
func visitChildren(of node: Node) -> [Result]
|
||||
|
||||
}
|
||||
|
||||
extension Visitor {
|
||||
|
||||
public func visitChildren(of node: Node) -> [Result] {
|
||||
return node.childSequence.compactMap { child in
|
||||
switch child {
|
||||
@@ -66,4 +71,5 @@ extension Visitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ public struct Down: DownASTRenderable, DownHTMLRenderable, DownXMLRenderable,
|
||||
/// A string containing CommonMark Markdown
|
||||
public var markdownString: String
|
||||
|
||||
/// Initializes the container with a CommonMark Markdown string which can then be rendered depending on protocol conformance
|
||||
/// Initializes the container with a CommonMark Markdown string which can then be
|
||||
/// rendered depending on protocol conformance.
|
||||
///
|
||||
/// - Parameter markdownString: A string containing CommonMark Markdown
|
||||
public init(markdownString: String) {
|
||||
|
||||
@@ -9,19 +9,28 @@
|
||||
import Foundation
|
||||
|
||||
public enum DownErrors: Error {
|
||||
/// Thrown when there was an issue converting the Markdown into an abstract syntax tree
|
||||
|
||||
/// Thrown when there was an issue converting the Markdown into an abstract syntax tree.
|
||||
|
||||
case markdownToASTError
|
||||
|
||||
/// Thrown when the abstract syntax tree could not be rendered into another format
|
||||
/// Thrown when the abstract syntax tree could not be rendered into another format.
|
||||
|
||||
case astRenderingError
|
||||
|
||||
/// Thrown when an HTML string cannot be converted into an `NSData` representation
|
||||
/// Thrown when an HTML string cannot be converted into an `NSData` representation.
|
||||
|
||||
case htmlDataConversionError
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
/// Thrown when a custom template bundle has a non-standard bundle format.
|
||||
///
|
||||
/// Specifically, the file URL of the bundle’s subdirectory containing resource files could not be found (i.e. the bundle's `resourceURL` property is nil).
|
||||
/// Specifically, the file URL of the bundle’s subdirectory containing resource files could
|
||||
/// not be found (i.e. the bundle's `resourceURL` property is nil).
|
||||
|
||||
case nonStandardBundleFormatError
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -10,18 +10,27 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public struct DownOptions: OptionSet {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public let rawValue: Int32
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
public init(rawValue: Int32) { self.rawValue = rawValue }
|
||||
|
||||
/// Default options
|
||||
/// Default options.
|
||||
|
||||
public static let `default` = DownOptions(rawValue: CMARK_OPT_DEFAULT)
|
||||
|
||||
// MARK: - Rendering Options
|
||||
|
||||
/// Include a `data-sourcepos` attribute on all block elements
|
||||
/// Include a `data-sourcepos` attribute on all block elements.
|
||||
|
||||
public static let sourcePos = DownOptions(rawValue: CMARK_OPT_SOURCEPOS)
|
||||
|
||||
/// Render `softbreak` elements as hard line breaks.
|
||||
|
||||
public static let hardBreaks = DownOptions(rawValue: CMARK_OPT_HARDBREAKS)
|
||||
|
||||
/// Suppress raw HTML and unsafe links (`javascript:`, `vbscript:`,
|
||||
@@ -32,8 +41,9 @@ public struct DownOptions: OptionSet {
|
||||
///
|
||||
/// Note: this is the default option as of cmark v0.29.0. Use `unsafe`
|
||||
/// to disable this behavior.
|
||||
|
||||
public static let safe = DownOptions(rawValue: CMARK_OPT_SAFE)
|
||||
|
||||
|
||||
/// Render raw HTML and unsafe links (`javascript:`, `vbscript:`,
|
||||
/// `file:`, and `data:`, except for `image/png`, `image/gif`,
|
||||
/// `image/jpeg`, or `image/webp` mime types). By default,
|
||||
@@ -41,23 +51,28 @@ public struct DownOptions: OptionSet {
|
||||
/// links are replaced by empty strings.
|
||||
///
|
||||
/// Note: `safe` is the default as of cmark v0.29.0
|
||||
|
||||
public static let unsafe = DownOptions(rawValue: CMARK_OPT_UNSAFE)
|
||||
|
||||
// MARK: - Parsing Options
|
||||
|
||||
/// Normalize tree by consolidating adjacent text nodes.
|
||||
|
||||
public static let normalize = DownOptions(rawValue: CMARK_OPT_NORMALIZE)
|
||||
|
||||
/// Validate UTF-8 in the input before parsing, replacing illegal
|
||||
/// sequences with the replacement character U+FFFD.
|
||||
|
||||
public static let validateUTF8 = DownOptions(rawValue: CMARK_OPT_VALIDATE_UTF8)
|
||||
|
||||
/// Convert straight quotes to curly, --- to em dashes, -- to en dashes.
|
||||
|
||||
public static let smart = DownOptions(rawValue: CMARK_OPT_SMART)
|
||||
|
||||
|
||||
// MARK: - Combo Options
|
||||
|
||||
|
||||
/// Combines 'unsafe' and 'smart' to render raw HTML and produce smart typography.
|
||||
|
||||
public static let smartUnsafe = DownOptions(rawValue: CMARK_OPT_SMART + CMARK_OPT_UNSAFE)
|
||||
|
||||
}
|
||||
|
||||
@@ -9,18 +9,25 @@
|
||||
#if !os(Linux)
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
import AppKit
|
||||
|
||||
#else
|
||||
|
||||
import UIKit
|
||||
|
||||
#endif
|
||||
|
||||
extension NSAttributedString {
|
||||
|
||||
/// Instantiates an attributed string with the given HTML string
|
||||
///
|
||||
/// - Parameter htmlString: An HTML string
|
||||
/// - Throws: `HTMLDataConversionError` or an instantiation error
|
||||
/// - Parameters:
|
||||
/// - htmlString: An HTML string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `HTMLDataConversionError` or an instantiation error.
|
||||
|
||||
convenience init(htmlString: String) throws {
|
||||
guard let data = htmlString.data(using: String.Encoding.utf8) else {
|
||||
throw DownErrors.htmlDataConversionError
|
||||
@@ -30,8 +37,10 @@ extension NSAttributedString {
|
||||
.documentType: NSAttributedString.DocumentType.html,
|
||||
.characterEncoding: NSNumber(value: String.Encoding.utf8.rawValue)
|
||||
]
|
||||
|
||||
try self.init(data: data, options: options, documentAttributes: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // !os(Linux)
|
||||
|
||||
@@ -11,11 +11,16 @@ import libcmark
|
||||
|
||||
extension String {
|
||||
|
||||
/// Generates an HTML string from the contents of the string (self), which should contain CommonMark Markdown
|
||||
/// Generates an HTML string from the contents of the string (self), which should contain CommonMark Markdown.
|
||||
///
|
||||
/// - Parameter options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: HTML string
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - Returns:
|
||||
/// An HTML string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toHTML(_ options: DownOptions = .default) throws -> String {
|
||||
let ast = try DownASTRenderer.stringToAST(self, options: options)
|
||||
let html = try DownHTMLRenderer.astToHTML(ast, options: options)
|
||||
|
||||
@@ -10,24 +10,39 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public protocol DownASTRenderable: DownRenderable {
|
||||
func toAST(_ options: DownOptions) throws -> UnsafeMutablePointer<cmark_node>
|
||||
|
||||
func toAST(_ options: DownOptions) throws -> CMarkNode
|
||||
|
||||
}
|
||||
|
||||
extension DownASTRenderable {
|
||||
/// Generates an abstract syntax tree from the `markdownString` property
|
||||
|
||||
/// Generates an abstract syntax tree from the `markdownString` property.
|
||||
///
|
||||
/// - Parameter options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: An abstract syntax tree representation of the Markdown input
|
||||
/// - Throws: `MarkdownToASTError` if conversion fails
|
||||
public func toAST(_ options: DownOptions = .default) throws -> UnsafeMutablePointer<cmark_node> {
|
||||
/// - Parametera:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
///
|
||||
/// - Returns:
|
||||
/// An abstract syntax tree representation of the Markdown input.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `MarkdownToASTError` if conversion fails.
|
||||
|
||||
public func toAST(_ options: DownOptions = .default) throws -> CMarkNode {
|
||||
return try DownASTRenderer.stringToAST(markdownString, options: options)
|
||||
}
|
||||
|
||||
/// Parses the `markdownString` property into an abstract syntax tree and returns the root `Document` node.
|
||||
///
|
||||
/// - Parameter options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: The root Document node for the abstract syntax tree representation of the Markdown input
|
||||
/// - Throws: `MarkdownToASTError` if conversion fails
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
///
|
||||
/// - Returns:
|
||||
/// The root Document node for the abstract syntax tree representation of the Markdown input.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `MarkdownToASTError` if conversion fails.
|
||||
|
||||
public func toDocument(_ options: DownOptions = .default) throws -> Document {
|
||||
let tree = try toAST(options)
|
||||
|
||||
@@ -37,20 +52,27 @@ extension DownASTRenderable {
|
||||
|
||||
return Document(cmarkNode: tree)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct DownASTRenderer {
|
||||
/// Generates an abstract syntax tree from the given CommonMark Markdown string
|
||||
|
||||
/// Generates an abstract syntax tree from the given CommonMark Markdown string.
|
||||
///
|
||||
/// **Important:** It is the caller's responsibility to call `cmark_node_free(ast)` on the returned value
|
||||
/// **Important:** It is the caller's responsibility to call `cmark_node_free(ast)` on the returned value.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - string: A string containing CommonMark Markdown
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: An abstract syntax tree representation of the Markdown input
|
||||
/// - Throws: `MarkdownToASTError` if conversion fails
|
||||
public static func stringToAST(_ string: String, options: DownOptions = .default) throws -> UnsafeMutablePointer<cmark_node> {
|
||||
var tree: UnsafeMutablePointer<cmark_node>?
|
||||
/// - string: A string containing CommonMark Markdown.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
///
|
||||
/// - Returns:
|
||||
/// An abstract syntax tree representation of the Markdown input.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `MarkdownToASTError` if conversion fails.
|
||||
public static func stringToAST(_ string: String, options: DownOptions = .default) throws -> CMarkNode {
|
||||
var tree: CMarkNode?
|
||||
|
||||
string.withCString {
|
||||
let stringLength = Int(strlen($0))
|
||||
tree = cmark_parse_document($0, stringLength, options.rawValue)
|
||||
@@ -59,6 +81,8 @@ public struct DownASTRenderer {
|
||||
guard let ast = tree else {
|
||||
throw DownErrors.markdownToASTError
|
||||
}
|
||||
|
||||
return ast
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,16 +7,20 @@
|
||||
//
|
||||
|
||||
#if !os(Linux)
|
||||
|
||||
import Foundation
|
||||
import libcmark
|
||||
|
||||
public protocol DownAttributedStringRenderable: DownHTMLRenderable, DownASTRenderable {
|
||||
|
||||
func toAttributedString(_ options: DownOptions, stylesheet: String?) throws -> NSAttributedString
|
||||
func toAttributedString(_ options: DownOptions, styler: Styler) throws -> NSAttributedString
|
||||
|
||||
}
|
||||
|
||||
extension DownAttributedStringRenderable {
|
||||
/// Generates an `NSAttributedString` from the `markdownString` property
|
||||
|
||||
/// Generates an `NSAttributedString` from the `markdownString` property.
|
||||
///
|
||||
/// **Note:** The attributed string is constructed and rendered via WebKit from html generated from the
|
||||
/// abstract syntax tree. This process is not background safe and must be executed on the main
|
||||
@@ -24,31 +28,46 @@ extension DownAttributedStringRenderable {
|
||||
/// use the `toAttributedString(options: styler:)` method below.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - stylesheet: a `String` to use as the CSS stylesheet when rendering, defaulting to a style that uses the `NSAttributedString` default font
|
||||
/// - Returns: An `NSAttributedString`
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
public func toAttributedString(_ options: DownOptions = .default, stylesheet: String? = nil) throws -> NSAttributedString {
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - stylesheet: a `String` to use as the CSS stylesheet when rendering, defaulting
|
||||
/// to a style that uses the `NSAttributedString` default font.
|
||||
///
|
||||
/// - Returns:
|
||||
/// An `NSAttributedString`.
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toAttributedString(_ options: DownOptions = .default,
|
||||
stylesheet: String? = nil) throws -> NSAttributedString {
|
||||
|
||||
let html = try self.toHTML(options)
|
||||
let defaultStylesheet = "* {font-family: Helvetica } code, pre { font-family: Menlo }"
|
||||
return try NSAttributedString(htmlString: "<style>" + (stylesheet ?? defaultStylesheet) + "</style>" + html)
|
||||
}
|
||||
|
||||
/// Generates an `NSAttributedString` from the `markdownString` property
|
||||
|
||||
/// Generates an `NSAttributedString` from the `markdownString` property.
|
||||
///
|
||||
/// **Note:** The attributed string is constructed directly by traversing the abstract syntax tree. It is
|
||||
/// much faster than the `toAttributedString(options: stylesheet)` method and it can be also be
|
||||
/// rendered in a background thread.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering
|
||||
/// - styler: a class/struct conforming to `Styler` to use when rendering the various elements of the attributed string
|
||||
/// - Returns: An `NSAttributedString`
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
/// - options: `DownOptions` to modify parsing or rendering.
|
||||
/// - styler: a class/struct conforming to `Styler` to use when rendering the various
|
||||
/// elements of the attributed string
|
||||
///
|
||||
/// - Returns:
|
||||
/// An `NSAttributedString`.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toAttributedString(_ options: DownOptions = .default, styler: Styler) throws -> NSAttributedString {
|
||||
let document = try self.toDocument(options)
|
||||
let visitor = AttributedStringVisitor(styler: styler, options: options)
|
||||
return document.accept(visitor)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // !os(Linux)
|
||||
|
||||
@@ -10,48 +10,68 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public protocol DownCommonMarkRenderable: DownRenderable {
|
||||
|
||||
func toCommonMark(_ options: DownOptions, width: Int32) throws -> String
|
||||
|
||||
}
|
||||
|
||||
extension DownCommonMarkRenderable {
|
||||
/// Generates a CommonMark Markdown string from the `markdownString` property
|
||||
|
||||
/// Generates a CommonMark Markdown string from the `markdownString` property.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - width: The width to break on, defaulting to 0
|
||||
/// - Returns: CommonMark Markdown string
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - width: The width to break on, defaulting to 0.
|
||||
///
|
||||
/// - Returns:
|
||||
/// A CommonMark Markdown string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toCommonMark(_ options: DownOptions = .default, width: Int32 = 0) throws -> String {
|
||||
let ast = try DownASTRenderer.stringToAST(markdownString, options: options)
|
||||
let commonMark = try DownCommonMarkRenderer.astToCommonMark(ast, options: options, width: width)
|
||||
cmark_node_free(ast)
|
||||
return commonMark
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct DownCommonMarkRenderer {
|
||||
/// Generates a CommonMark Markdown string from the given abstract syntax tree
|
||||
|
||||
/// Generates a CommonMark Markdown string from the given abstract syntax tree.
|
||||
///
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - width: The width to break on, defaulting to 0
|
||||
/// - Returns: CommonMark Markdown string
|
||||
/// - Throws: `ASTRenderingError` if the AST could not be converted
|
||||
public static func astToCommonMark(_ ast: UnsafeMutablePointer<cmark_node>,
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - width: The width to break on, defaulting to 0.
|
||||
///
|
||||
/// - Returns:
|
||||
/// A CommonMark Markdown string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `ASTRenderingError` if the AST could not be converted.
|
||||
|
||||
public static func astToCommonMark(_ ast: CMarkNode,
|
||||
options: DownOptions = .default,
|
||||
width: Int32 = 0) throws -> String {
|
||||
|
||||
guard let cCommonMarkString = cmark_render_commonmark(ast, options.rawValue, width) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
defer { free(cCommonMarkString) }
|
||||
|
||||
|
||||
defer {
|
||||
free(cCommonMarkString)
|
||||
}
|
||||
|
||||
guard let commonMarkString = String(cString: cCommonMarkString, encoding: String.Encoding.utf8) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
|
||||
|
||||
return commonMarkString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,48 +10,68 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public protocol DownGroffRenderable: DownRenderable {
|
||||
|
||||
func toGroff(_ options: DownOptions, width: Int32) throws -> String
|
||||
|
||||
}
|
||||
|
||||
extension DownGroffRenderable {
|
||||
/// Generates a groff man string from the `markdownString` property
|
||||
|
||||
/// Generates a groff man string from the `markdownString` property.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - width: The width to break on, defaulting to 0
|
||||
/// - Returns: groff man string
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - width: The width to break on, defaulting to 0.
|
||||
///
|
||||
/// - Returns:
|
||||
/// A groff man string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toGroff(_ options: DownOptions = .default, width: Int32 = 0) throws -> String {
|
||||
let ast = try DownASTRenderer.stringToAST(markdownString, options: options)
|
||||
let groff = try DownGroffRenderer.astToGroff(ast, options: options, width: width)
|
||||
cmark_node_free(ast)
|
||||
return groff
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct DownGroffRenderer {
|
||||
/// Generates a groff man string from the given abstract syntax tree
|
||||
|
||||
/// Generates a groff man string from the given abstract syntax tree.
|
||||
///
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - width: The width to break on, defaulting to 0
|
||||
/// - Returns: groff man string
|
||||
/// - Throws: `ASTRenderingError` if the AST could not be converted
|
||||
public static func astToGroff(_ ast: UnsafeMutablePointer<cmark_node>,
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - width: The width to break on, defaulting to 0.
|
||||
///
|
||||
/// - Returns:
|
||||
/// A groff man string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `ASTRenderingError` if the AST could not be converted.
|
||||
|
||||
public static func astToGroff(_ ast: CMarkNode,
|
||||
options: DownOptions = .default,
|
||||
width: Int32 = 0) throws -> String {
|
||||
|
||||
guard let cGroffString = cmark_render_man(ast, options.rawValue, width) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
defer { free(cGroffString) }
|
||||
|
||||
|
||||
defer {
|
||||
free(cGroffString)
|
||||
}
|
||||
|
||||
guard let groffString = String(cString: cGroffString, encoding: String.Encoding.utf8) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
|
||||
return groffString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,40 +10,60 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public protocol DownHTMLRenderable: DownRenderable {
|
||||
|
||||
func toHTML(_ options: DownOptions) throws -> String
|
||||
|
||||
}
|
||||
|
||||
extension DownHTMLRenderable {
|
||||
/// Generates an HTML string from the `markdownString` property
|
||||
|
||||
/// Generates an HTML string from the `markdownString` property.
|
||||
///
|
||||
/// - Parameter options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: HTML string
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
///
|
||||
/// - Returns:
|
||||
/// An HTML string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toHTML(_ options: DownOptions = .default) throws -> String {
|
||||
return try markdownString.toHTML(options)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct DownHTMLRenderer {
|
||||
/// Generates an HTML string from the given abstract syntax tree
|
||||
|
||||
/// Generates an HTML string from the given abstract syntax tree.
|
||||
///
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: HTML string
|
||||
/// - Throws: `ASTRenderingError` if the AST could not be converted
|
||||
public static func astToHTML(_ ast: UnsafeMutablePointer<cmark_node>, options: DownOptions = .default) throws -> String {
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
///
|
||||
/// - Returns:
|
||||
/// An HTML string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `ASTRenderingError` if the AST could not be converted.
|
||||
|
||||
public static func astToHTML(_ ast: CMarkNode, options: DownOptions = .default) throws -> String {
|
||||
guard let cHTMLString = cmark_render_html(ast, options.rawValue) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
defer { free(cHTMLString) }
|
||||
|
||||
|
||||
defer {
|
||||
free(cHTMLString)
|
||||
}
|
||||
|
||||
guard let htmlString = String(cString: cHTMLString, encoding: String.Encoding.utf8) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
|
||||
return htmlString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,48 +10,68 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public protocol DownLaTeXRenderable: DownRenderable {
|
||||
|
||||
func toLaTeX(_ options: DownOptions, width: Int32) throws -> String
|
||||
|
||||
}
|
||||
|
||||
extension DownLaTeXRenderable {
|
||||
/// Generates a LaTeX string from the `markdownString` property
|
||||
|
||||
/// Generates a LaTeX string from the `markdownString` property.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - width: The width to break on, defaulting to 0
|
||||
/// - Returns: LaTeX string
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - width: The width to break on, defaulting to 0.
|
||||
///
|
||||
/// - Returns:
|
||||
/// A LaTeX string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toLaTeX(_ options: DownOptions = .default, width: Int32 = 0) throws -> String {
|
||||
let ast = try DownASTRenderer.stringToAST(markdownString, options: options)
|
||||
let latex = try DownLaTeXRenderer.astToLaTeX(ast, options: options, width: width)
|
||||
cmark_node_free(ast)
|
||||
return latex
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct DownLaTeXRenderer {
|
||||
/// Generates a LaTeX string from the given abstract syntax tree
|
||||
|
||||
/// Generates a LaTeX string from the given abstract syntax tree.
|
||||
///
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - width: The width to break on, defaulting to 0
|
||||
/// - Returns: LaTeX string
|
||||
/// - Throws: `ASTRenderingError` if the AST could not be converted
|
||||
public static func astToLaTeX(_ ast: UnsafeMutablePointer<cmark_node>,
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - width: The width to break on, defaulting to 0.
|
||||
///
|
||||
/// - Returns:
|
||||
/// A LaTeX string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `ASTRenderingError` if the AST could not be converted.
|
||||
|
||||
public static func astToLaTeX(_ ast: CMarkNode,
|
||||
options: DownOptions = .default,
|
||||
width: Int32 = 0) throws -> String {
|
||||
|
||||
guard let cLatexString = cmark_render_latex(ast, options.rawValue, width) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
defer { free(cLatexString) }
|
||||
|
||||
|
||||
defer {
|
||||
free(cLatexString)
|
||||
}
|
||||
|
||||
guard let latexString = String(cString: cLatexString, encoding: String.Encoding.utf8) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
|
||||
|
||||
return latexString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
import Foundation
|
||||
|
||||
public protocol DownRenderable {
|
||||
/// A string containing CommonMark Markdown
|
||||
|
||||
/// A string containing CommonMark Markdown.
|
||||
|
||||
var markdownString: String { get set }
|
||||
|
||||
}
|
||||
|
||||
@@ -10,43 +10,63 @@ import Foundation
|
||||
import libcmark
|
||||
|
||||
public protocol DownXMLRenderable: DownRenderable {
|
||||
|
||||
func toXML(_ options: DownOptions) throws -> String
|
||||
|
||||
}
|
||||
|
||||
extension DownXMLRenderable {
|
||||
/// Generates an XML string from the `markdownString` property
|
||||
|
||||
/// Generates an XML string from the `markdownString` property.
|
||||
///
|
||||
/// - Parameter options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: XML string
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
/// - Parameters:
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
///
|
||||
/// - Returns:
|
||||
/// An XML string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func toXML(_ options: DownOptions = .default) throws -> String {
|
||||
let ast = try DownASTRenderer.stringToAST(markdownString, options: options)
|
||||
let xml = try DownXMLRenderer.astToXML(ast, options: options)
|
||||
cmark_node_free(ast)
|
||||
return xml
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct DownXMLRenderer {
|
||||
|
||||
/// Generates an XML string from the given abstract syntax tree
|
||||
///
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns
|
||||
/// **Note:** caller is responsible for calling `cmark_node_free(ast)` after this returns.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - Returns: XML string
|
||||
/// - Throws: `ASTRenderingError` if the AST could not be converted
|
||||
public static func astToXML(_ ast: UnsafeMutablePointer<cmark_node>, options: DownOptions = .default) throws -> String {
|
||||
/// - ast: The `cmark_node` representing the abstract syntax tree.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
///
|
||||
/// - Returns:
|
||||
/// An XML string.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `ASTRenderingError` if the AST could not be converted.
|
||||
|
||||
public static func astToXML(_ ast: CMarkNode, options: DownOptions = .default) throws -> String {
|
||||
guard let cXMLString = cmark_render_xml(ast, options.rawValue) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
defer { free(cXMLString) }
|
||||
|
||||
|
||||
defer {
|
||||
free(cXMLString)
|
||||
}
|
||||
|
||||
guard let xmlString = String(cString: cXMLString, encoding: String.Encoding.utf8) else {
|
||||
throw DownErrors.astRenderingError
|
||||
}
|
||||
|
||||
return xmlString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ extension Foundation.Bundle {
|
||||
Bundle(for: BundleFinder.self).resourceURL,
|
||||
|
||||
// For command-line tools.
|
||||
Bundle.main.bundleURL,
|
||||
Bundle.main.bundleURL
|
||||
]
|
||||
|
||||
for candidate in candidates {
|
||||
|
||||
@@ -7,30 +7,48 @@
|
||||
//
|
||||
|
||||
#if !os(Linux)
|
||||
|
||||
#if os(tvOS) || os(watchOS)
|
||||
// Sorry, not available for tvOS nor watchOS
|
||||
|
||||
// Sorry, not available for tvOS nor watchOS
|
||||
|
||||
#else
|
||||
|
||||
import WebKit
|
||||
|
||||
// MARK: - Public API
|
||||
|
||||
public typealias DownViewClosure = () -> ()
|
||||
public typealias DownViewClosure = () -> Void
|
||||
|
||||
open class DownView: WKWebView {
|
||||
|
||||
/// Initializes a web view with the results of rendering a CommonMark Markdown string
|
||||
// MARK: - Life cycle
|
||||
|
||||
/// Initializes a web view with the results of rendering a CommonMark Markdown string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - frame: The frame size of the web view
|
||||
/// - markdownString: A string containing CommonMark Markdown
|
||||
/// - openLinksInBrowser: Whether or not to open links using an external browser
|
||||
/// - templateBundle: Optional custom template bundle. Leaving this as `nil` will use the bundle included with Down.
|
||||
/// - configuration: Optional custom web view configuration.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - didLoadSuccessfully: Optional callback for when the web content has loaded successfully
|
||||
/// - writableBundle: Whether or not the bundle folder is writable.
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
public init(frame: CGRect, markdownString: String, openLinksInBrowser: Bool = true, templateBundle: Bundle? = nil, writableBundle: Bool = false, configuration: WKWebViewConfiguration? = nil, options: DownOptions = .default, didLoadSuccessfully: DownViewClosure? = nil) throws {
|
||||
/// - frame: The frame size of the web view
|
||||
/// - markdownString: A string containing CommonMark Markdown
|
||||
/// - openLinksInBrowser: Whether or not to open links using an external browser
|
||||
/// - templateBundle: Optional custom template bundle. Leaving this as `nil` will use the bundle included
|
||||
/// with Down.
|
||||
/// - configuration: Optional custom web view configuration.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - didLoadSuccessfully: Optional callback for when the web content has loaded successfully
|
||||
/// - writableBundle: Whether or not the bundle folder is writable.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public init(frame: CGRect,
|
||||
markdownString: String,
|
||||
openLinksInBrowser: Bool = true,
|
||||
templateBundle: Bundle? = nil,
|
||||
writableBundle: Bool = false,
|
||||
configuration: WKWebViewConfiguration? = nil,
|
||||
options: DownOptions = .default,
|
||||
didLoadSuccessfully: DownViewClosure? = nil) throws {
|
||||
|
||||
self.options = options
|
||||
self.didLoadSuccessfully = didLoadSuccessfully
|
||||
self.writableBundle = writableBundle
|
||||
@@ -46,7 +64,7 @@ open class DownView: WKWebView {
|
||||
super.init(frame: frame, configuration: configuration ?? WKWebViewConfiguration())
|
||||
|
||||
#if os(macOS)
|
||||
setupMacEnvironment()
|
||||
setupMacEnvironment()
|
||||
#endif
|
||||
|
||||
if openLinksInBrowser || didLoadSuccessfully != nil { navigationDelegate = self }
|
||||
@@ -62,22 +80,29 @@ open class DownView: WKWebView {
|
||||
clearTemporaryDirectory()
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - API
|
||||
|
||||
/// Renders the given CommonMark Markdown string into HTML and updates the DownView while keeping the style intact
|
||||
|
||||
/// Renders the given CommonMark Markdown string into HTML and updates the DownView while keeping the style intact.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - markdownString: A string containing CommonMark Markdown
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`
|
||||
/// - didLoadSuccessfully: Optional callback for when the web content has loaded successfully
|
||||
/// - Throws: `DownErrors` depending on the scenario
|
||||
public func update(markdownString: String, options: DownOptions? = nil, didLoadSuccessfully: DownViewClosure? = nil) throws {
|
||||
/// - markdownString: A string containing CommonMark Markdown.
|
||||
/// - options: `DownOptions` to modify parsing or rendering, defaulting to `.default`.
|
||||
/// - didLoadSuccessfully: Optional callback for when the web content has loaded successfully.
|
||||
///
|
||||
/// - Throws:
|
||||
/// `DownErrors` depending on the scenario.
|
||||
|
||||
public func update(markdownString: String,
|
||||
options: DownOptions? = nil,
|
||||
didLoadSuccessfully: DownViewClosure? = nil) throws {
|
||||
|
||||
// Note: As the init method sets this initially, we only overwrite them if
|
||||
// a non-nil value is passed in
|
||||
// a non-nil value is passed in.
|
||||
if let options = options {
|
||||
self.options = options
|
||||
}
|
||||
|
||||
if let didLoadSuccessfully = didLoadSuccessfully {
|
||||
self.didLoadSuccessfully = didLoadSuccessfully
|
||||
}
|
||||
@@ -96,15 +121,11 @@ open class DownView: WKWebView {
|
||||
}()
|
||||
|
||||
#if os(macOS)
|
||||
private lazy var temporaryDirectoryURL: URL = {
|
||||
return try! FileManager.default.url(for: .itemReplacementDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: URL(fileURLWithPath: NSTemporaryDirectory()),
|
||||
create: true).appendingPathComponent("Down", isDirectory: true)
|
||||
}()
|
||||
private var temporaryDirectoryURL: URL?
|
||||
#endif
|
||||
|
||||
|
||||
private var didLoadSuccessfully: DownViewClosure?
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Private API
|
||||
@@ -116,15 +137,15 @@ private extension DownView {
|
||||
let pageHTMLString = try htmlFromTemplate(htmlString)
|
||||
|
||||
#if os(iOS)
|
||||
if writableBundle {
|
||||
let newIndexUrl = try writeTempIndexFile(pageHTMLString: pageHTMLString)
|
||||
loadFileURL(newIndexUrl, allowingReadAccessTo: newIndexUrl.deletingLastPathComponent())
|
||||
} else {
|
||||
loadHTMLString(pageHTMLString, baseURL: baseURL)
|
||||
}
|
||||
if writableBundle {
|
||||
let newIndexUrl = try writeTempIndexFile(pageHTMLString: pageHTMLString)
|
||||
loadFileURL(newIndexUrl, allowingReadAccessTo: newIndexUrl.deletingLastPathComponent())
|
||||
} else {
|
||||
loadHTMLString(pageHTMLString, baseURL: baseURL)
|
||||
}
|
||||
#elseif os(macOS)
|
||||
let indexURL = try createTemporaryBundle(pageHTMLString: pageHTMLString)
|
||||
loadFileURL(indexURL, allowingReadAccessTo: indexURL.deletingLastPathComponent())
|
||||
let indexURL = try createTemporaryBundle(pageHTMLString: pageHTMLString)
|
||||
loadFileURL(indexURL, allowingReadAccessTo: indexURL.deletingLastPathComponent())
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -143,8 +164,19 @@ private extension DownView {
|
||||
|
||||
#if os(macOS)
|
||||
func createTemporaryBundle(pageHTMLString: String) throws -> URL {
|
||||
guard let bundleResourceURL = bundle.resourceURL
|
||||
else { throw DownErrors.nonStandardBundleFormatError }
|
||||
guard let bundleResourceURL = bundle.resourceURL else {
|
||||
throw DownErrors.nonStandardBundleFormatError
|
||||
}
|
||||
|
||||
let fileManager = FileManager.default
|
||||
|
||||
let temporaryDirectoryURL = try fileManager.url(for: .itemReplacementDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: URL(fileURLWithPath: NSTemporaryDirectory()),
|
||||
create: true).appendingPathComponent("Down", isDirectory: true)
|
||||
|
||||
self.temporaryDirectoryURL = temporaryDirectoryURL
|
||||
|
||||
let indexURL = temporaryDirectoryURL.appendingPathComponent("index.html", isDirectory: false)
|
||||
|
||||
// If updating markdown contents, no need to re-copy bundle.
|
||||
@@ -168,7 +200,9 @@ private extension DownView {
|
||||
|
||||
@objc
|
||||
func clearTemporaryDirectory() {
|
||||
try? FileManager.default.removeItem(at: temporaryDirectoryURL)
|
||||
if let temporaryDirectoryURL = temporaryDirectoryURL {
|
||||
try? FileManager.default.removeItem(at: temporaryDirectoryURL)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -177,11 +211,18 @@ private extension DownView {
|
||||
// MARK: - WKNavigationDelegate
|
||||
|
||||
extension DownView: WKNavigationDelegate {
|
||||
public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
|
||||
|
||||
public func webView(_ webView: WKWebView,
|
||||
decidePolicyFor navigationResponse: WKNavigationResponse,
|
||||
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
|
||||
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
public func webView(_ webView: WKWebView,
|
||||
decidePolicyFor navigationAction: WKNavigationAction,
|
||||
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
|
||||
guard let url = navigationAction.request.url else { return decisionHandler(.allow) }
|
||||
|
||||
switch navigationAction.navigationType {
|
||||
@@ -208,18 +249,21 @@ extension DownView: WKNavigationDelegate {
|
||||
NSWorkspace.shared.open(url)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
didLoadSuccessfully?()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private extension WKNavigationDelegate {
|
||||
|
||||
/// A wrapper for `UIApplication.shared.openURL` so that an empty default
|
||||
/// implementation is available in app extensions
|
||||
func openURL(url: URL) {}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // !os(Linux)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
SUPPORTED_PLATFORMS = macosx iphonesimulator iphoneos appletvos appletvsimulator
|
||||
VALID_ARCHS[sdk=macosx*] = i386 x86_64 arm64
|
||||
VALID_ARCHS[sdk=iphoneos*] = arm64 armv7 armv7s
|
||||
VALID_ARCHS[sdk=iphonesimulator*] = i386 x86_64
|
||||
VALID_ARCHS[sdk=iphonesimulator*] = i386 x86_64 arm64
|
||||
VALID_ARCHS[sdk=appletv*] = arm64
|
||||
VALID_ARCHS[sdk=appletvsimulator*] = x86_64
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ import XCTest
|
||||
|
||||
class ListItemPrefixGeneratorTests: XCTestCase {
|
||||
|
||||
func testNumberPrefixGeneration() {
|
||||
func testNumberStaticPrefixGeneration() {
|
||||
// Given
|
||||
let sut = ListItemPrefixGenerator(listType: .ordered(start: 3), numberOfItems: 3)
|
||||
let sut = StaticListItemPrefixGenerator(listType: .ordered(start: 3), numberOfItems: 3, nestDepth: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual("3.", sut.next())
|
||||
@@ -22,9 +22,9 @@ class ListItemPrefixGeneratorTests: XCTestCase {
|
||||
XCTAssertNil(sut.next())
|
||||
}
|
||||
|
||||
func testBulletPrefixGeneration() {
|
||||
func testBulletStaticPrefixGeneration() {
|
||||
// Given
|
||||
let sut = ListItemPrefixGenerator(listType: .bullet, numberOfItems: 3)
|
||||
let sut = StaticListItemPrefixGenerator(listType: .bullet, numberOfItems: 3, nestDepth: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual("•", sut.next())
|
||||
@@ -32,4 +32,5 @@ class ListItemPrefixGeneratorTests: XCTestCase {
|
||||
XCTAssertEqual("•", sut.next())
|
||||
XCTAssertNil(sut.next())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class NodeTests: XCTestCase {
|
||||
// Then
|
||||
XCTAssertEqual(sut.listNestDepthResults, [0, 1, 2])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
@@ -41,11 +42,11 @@ extension NodeTests {
|
||||
let document = try Down(markdownString: markdown).toDocument()
|
||||
document.accept(visitor)
|
||||
} catch {
|
||||
XCTFail()
|
||||
XCTFail("Failed to generate document.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class NodeVisitor: DebugVisitor {
|
||||
|
||||
@@ -55,4 +56,5 @@ private class NodeVisitor: DebugVisitor {
|
||||
listNestDepthResults.append(node.nestDepth)
|
||||
return super.visit(list: node)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@ import SnapshotTesting
|
||||
|
||||
class VisitorTests: XCTestCase {
|
||||
|
||||
func result(for markdown: String) -> String {
|
||||
func result(for markdown: String) throws -> String {
|
||||
let down = Down(markdownString: markdown)
|
||||
return try! down.toAttributedString(styler: EmptyStyler()).string
|
||||
return try down.toAttributedString(styler: EmptyStyler()).string
|
||||
}
|
||||
|
||||
func debugResult(for markdown: String) -> String {
|
||||
func debugResult(for markdown: String) throws -> String {
|
||||
let down = Down(markdownString: markdown)
|
||||
let document = try! down.toDocument()
|
||||
let document = try down.toDocument()
|
||||
return document.accept(DebugVisitor())
|
||||
}
|
||||
|
||||
func testBlockQuote() {
|
||||
func testBlockQuote() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.
|
||||
@@ -35,11 +35,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testList() {
|
||||
func testList() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.
|
||||
@@ -54,11 +54,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testCodeBlock() {
|
||||
func testCodeBlock() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.
|
||||
@@ -72,11 +72,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testHtmlBlock() {
|
||||
func testHtmlBlock() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.
|
||||
@@ -89,11 +89,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testParagraph() {
|
||||
func testParagraph() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.
|
||||
@@ -104,11 +104,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testHeading() {
|
||||
func testHeading() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.
|
||||
@@ -119,11 +119,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testThematicBreak() {
|
||||
func testThematicBreak() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.
|
||||
@@ -134,11 +134,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testSoftBreak() {
|
||||
func testSoftBreak() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text
|
||||
@@ -146,11 +146,11 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testLineBreak() {
|
||||
func testLineBreak() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text text.\\
|
||||
@@ -158,35 +158,36 @@ class VisitorTests: XCTestCase {
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testInline() {
|
||||
func testInline() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text **strong _emphasis `code` <html>_**
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
|
||||
func testLink() {
|
||||
func testLink() throws {
|
||||
// Given
|
||||
let markdown = """
|
||||
Text [link](www.example.com) text 
|
||||
"""
|
||||
|
||||
// Then
|
||||
assertSnapshot(matching: result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: debugResult(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try result(for: markdown), as: .lines)
|
||||
assertSnapshot(matching: try debugResult(for: markdown), as: .lines)
|
||||
}
|
||||
}
|
||||
|
||||
private class EmptyStyler: Styler {
|
||||
var listPrefixAttributes: [NSAttributedString.Key : Any] = [:]
|
||||
|
||||
var listPrefixAttributes: [NSAttributedString.Key: Any] = [:]
|
||||
func style(document str: NSMutableAttributedString) {}
|
||||
func style(blockQuote str: NSMutableAttributedString, nestDepth: Int) {}
|
||||
func style(list str: NSMutableAttributedString, nestDepth: Int) {}
|
||||
@@ -208,4 +209,5 @@ private class EmptyStyler: Styler {
|
||||
func style(strong str: NSMutableAttributedString) {}
|
||||
func style(link str: NSMutableAttributedString, title: String?, url: String?) {}
|
||||
func style(image str: NSMutableAttributedString, title: String?, url: String?) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Document
|
||||
↳ Paragraph
|
||||
↳ Text - Text text.
|
||||
↳ List - type: Ordered (start: 3), isTight: true
|
||||
↳ List - type: Ordered (start: 3), isTight: true, delimiter: period
|
||||
↳ Item
|
||||
↳ Paragraph
|
||||
↳ Text - One
|
||||
|
||||
@@ -7,45 +7,40 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SnapshotTesting
|
||||
@testable import Down
|
||||
|
||||
class BindingTests: XCTestCase {
|
||||
|
||||
let down = Down(markdownString: "## [Down](https://github.com/iwasrobbed/Down)")
|
||||
let down = Down(markdownString: "## [Down](https://github.com/johnxnnguyen/Down)")
|
||||
|
||||
func testASTBindingsWork() {
|
||||
let ast = try? down.toAST()
|
||||
XCTAssertNotNil(ast)
|
||||
func testASTBindingsWork() throws {
|
||||
_ = try down.toAST()
|
||||
}
|
||||
|
||||
func testHTMLBindingsWork() {
|
||||
let html = try? down.toHTML()
|
||||
XCTAssertNotNil(html)
|
||||
XCTAssertTrue(html == "<h2><a href=\"https://github.com/iwasrobbed/Down\">Down</a></h2>\n")
|
||||
func testHTMLBindingsWork() throws {
|
||||
let html = try down.toHTML()
|
||||
assertSnapshot(matching: html, as: .lines)
|
||||
}
|
||||
|
||||
func testXMLBindingsWork() {
|
||||
let xml = try? down.toXML()
|
||||
XCTAssertNotNil(xml)
|
||||
XCTAssertTrue(xml == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n<document xmlns=\"http://commonmark.org/xml/1.0\">\n <heading level=\"2\">\n <link destination=\"https://github.com/iwasrobbed/Down\" title=\"\">\n <text xml:space=\"preserve\">Down</text>\n </link>\n </heading>\n</document>\n")
|
||||
func testXMLBindingsWork() throws {
|
||||
let xml = try down.toXML()
|
||||
assertSnapshot(matching: xml, as: .lines)
|
||||
}
|
||||
|
||||
func testGroffBindingsWork() {
|
||||
let man = try? down.toGroff()
|
||||
XCTAssertNotNil(man)
|
||||
XCTAssertTrue(man == ".SS\nDown (https://github.com/iwasrobbed/Down)\n")
|
||||
func testGroffBindingsWork() throws {
|
||||
let man = try down.toGroff()
|
||||
assertSnapshot(matching: man, as: .lines)
|
||||
}
|
||||
|
||||
func testLaTeXBindngsWork() {
|
||||
let latex = try? down.toLaTeX()
|
||||
XCTAssertNotNil(latex)
|
||||
XCTAssertTrue(latex == "\\subsection{\\href{https://github.com/iwasrobbed/Down}{Down}}\n")
|
||||
func testLaTeXBindngsWork() throws {
|
||||
let latex = try down.toLaTeX()
|
||||
assertSnapshot(matching: latex, as: .lines)
|
||||
}
|
||||
|
||||
func testCommonMarkBindngsWork() {
|
||||
let commonMark = try? down.toCommonMark()
|
||||
XCTAssertNotNil(commonMark)
|
||||
XCTAssertTrue(commonMark == "## [Down](https://github.com/iwasrobbed/Down)\n")
|
||||
func testCommonMarkBindngsWork() throws {
|
||||
let commonMark = try down.toCommonMark()
|
||||
assertSnapshot(matching: commonMark, as: .lines)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -18,22 +18,26 @@ class DownViewTests: XCTestCase {
|
||||
func testInstantiation() {
|
||||
let expect1 = expectation(description: "DownView sets the html and validates the html is correct")
|
||||
var downView: DownView?
|
||||
downView = try? DownView(frame: .zero, markdownString: "## [Down](https://github.com/iwasrobbed/Down)", didLoadSuccessfully: {
|
||||
|
||||
downView = try? DownView(frame: .zero,
|
||||
markdownString: "## [Down](https://github.com/iwasrobbed/Down)",
|
||||
didLoadSuccessfully: {
|
||||
|
||||
self._pageContents(for: downView!) { htmlString in
|
||||
XCTAssertTrue(htmlString!.contains("css/down.min.css"))
|
||||
XCTAssertTrue(htmlString!.contains("https://github.com/iwasrobbed/Down"))
|
||||
|
||||
|
||||
expect1.fulfill()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10) { (error: Error?) in
|
||||
if let error = error {
|
||||
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func testUpdatingMarkdown() {
|
||||
let expect1 = expectation(description: "DownView sets the html and validates the html is correct")
|
||||
var downView: DownView?
|
||||
@@ -41,27 +45,27 @@ class DownViewTests: XCTestCase {
|
||||
self._pageContents(for: downView!) { htmlString in
|
||||
XCTAssertTrue(htmlString!.contains("css/down.min.css"))
|
||||
XCTAssertTrue(htmlString!.contains("https://github.com/iwasrobbed/Down"))
|
||||
|
||||
|
||||
expect1.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10) { (error: Error?) in
|
||||
if let error = error {
|
||||
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let expect2 = expectation(description: "DownView sets the html and validates the html is correct")
|
||||
try? downView?.update(markdownString: "## [Google](https://google.com)") {
|
||||
try? downView?.update(markdownString: "## [Google](https://google.com)") {
|
||||
self._pageContents(for: downView!) { htmlString in
|
||||
XCTAssertTrue(htmlString!.contains("css/down.min.css"))
|
||||
XCTAssertTrue(htmlString!.contains("https://google.com"))
|
||||
|
||||
|
||||
expect2.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
waitForExpectations(timeout: 10) { (error: Error?) in
|
||||
if let error = error {
|
||||
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
|
||||
@@ -80,7 +84,11 @@ class DownViewTests: XCTestCase {
|
||||
}
|
||||
|
||||
var downView: DownView?
|
||||
downView = try? DownView(frame: .zero, markdownString: "## [Down](https://github.com/iwasrobbed/Down)", templateBundle: templateBundle, didLoadSuccessfully: {
|
||||
downView = try? DownView(frame: .zero,
|
||||
markdownString: "## [Down](https://github.com/iwasrobbed/Down)",
|
||||
templateBundle: templateBundle,
|
||||
didLoadSuccessfully: {
|
||||
|
||||
self._pageContents(for: downView!) { htmlString in
|
||||
XCTAssertTrue(htmlString!.contains("css/down.min.css"))
|
||||
XCTAssertTrue(htmlString!.contains("https://github.com/iwasrobbed/Down"))
|
||||
@@ -98,7 +106,9 @@ class DownViewTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInstantiationWithCustomWritableTemplateBundle() {
|
||||
let expect1 = expectation(description: "DownView accepts and loads custom bundle files from a user writable location")
|
||||
let expect1 = expectation(
|
||||
description: "DownView accepts and loads custom bundle files from a user writable location"
|
||||
)
|
||||
|
||||
guard
|
||||
let bundle = Bundle(for: type(of: self)).url(forResource: "TestDownView", withExtension: "bundle"),
|
||||
@@ -108,13 +118,20 @@ class DownViewTests: XCTestCase {
|
||||
return
|
||||
}
|
||||
|
||||
let markdownString = """
|
||||
```swift
|
||||
let x = 1
|
||||
```
|
||||
"""
|
||||
let markdown = """
|
||||
```swift
|
||||
let x = 1
|
||||
```
|
||||
"""
|
||||
|
||||
var downView: DownView?
|
||||
downView = try? DownView(frame: .zero, markdownString: markdownString, templateBundle: templateBundle, writableBundle: true, didLoadSuccessfully: {
|
||||
|
||||
downView = try? DownView(frame: .zero,
|
||||
markdownString: markdown,
|
||||
templateBundle: templateBundle,
|
||||
writableBundle: true,
|
||||
didLoadSuccessfully: {
|
||||
|
||||
self._pageContents(for: downView!) { htmlString in
|
||||
XCTAssertTrue(htmlString!.contains("css/down.min.css"))
|
||||
XCTAssertTrue(htmlString!.contains("hljs-keyword"))
|
||||
@@ -132,21 +149,21 @@ let x = 1
|
||||
}
|
||||
|
||||
func testDownOptions() {
|
||||
let markdownString = "## [Down](https://github.com/iwasrobbed/Down)\n\n<strong>I'm strong!</strong>"
|
||||
let markdown = "## [Down](https://github.com/iwasrobbed/Down)\n\n<strong>I'm strong!</strong>"
|
||||
let renderedHTML = "<strong>I'm strong!</strong>"
|
||||
|
||||
// Set this view to initially be HTML safe
|
||||
let safeExpect = expectation(description: "DownView default init strips unsafe HTML")
|
||||
let toggleSafeExpect = expectation(description: "DownView update to unsafe does not strip unsafe HTML")
|
||||
var safeDownView: DownView?
|
||||
safeDownView = try? DownView(frame: .zero, markdownString: markdownString, didLoadSuccessfully: {
|
||||
safeDownView = try? DownView(frame: .zero, markdownString: markdown, didLoadSuccessfully: {
|
||||
self._pageContents(for: safeDownView!) { htmlString in
|
||||
XCTAssertTrue(safeDownView?.options == .default)
|
||||
XCTAssertFalse(htmlString!.contains(renderedHTML))
|
||||
safeExpect.fulfill()
|
||||
|
||||
// Then change it to HTML unsafe options and ensure it's changed
|
||||
try? safeDownView?.update(markdownString: markdownString, options: .unsafe, didLoadSuccessfully: {
|
||||
try? safeDownView?.update(markdownString: markdown, options: .unsafe, didLoadSuccessfully: {
|
||||
XCTAssertTrue(safeDownView?.options == .unsafe)
|
||||
self._pageContents(for: safeDownView!) { htmlString in
|
||||
XCTAssertTrue(htmlString!.contains(renderedHTML))
|
||||
@@ -160,14 +177,15 @@ let x = 1
|
||||
let unsafeExpect = expectation(description: "DownView unsafe init does not strip unsafe HTML")
|
||||
let toggleUnsafeExpect = expectation(description: "DownView update to safe strips unsafe HTML")
|
||||
var unsafeDownView: DownView?
|
||||
unsafeDownView = try? DownView(frame: .zero, markdownString: markdownString, options: .unsafe, didLoadSuccessfully: {
|
||||
|
||||
unsafeDownView = try? DownView(frame: .zero, markdownString: markdown, options: .unsafe, didLoadSuccessfully: {
|
||||
self._pageContents(for: unsafeDownView!) { htmlString in
|
||||
XCTAssertTrue(unsafeDownView?.options == .unsafe)
|
||||
XCTAssertTrue(htmlString!.contains(renderedHTML))
|
||||
unsafeExpect.fulfill()
|
||||
|
||||
// And then toggle it to be HTML safe and ensure it's changed
|
||||
try? unsafeDownView?.update(markdownString: markdownString, options: .default, didLoadSuccessfully: {
|
||||
try? unsafeDownView?.update(markdownString: markdown, options: .default, didLoadSuccessfully: {
|
||||
XCTAssertTrue(unsafeDownView?.options == .default)
|
||||
self._pageContents(for: unsafeDownView!) { htmlString in
|
||||
XCTAssertFalse(htmlString!.contains(renderedHTML))
|
||||
@@ -222,7 +240,11 @@ let x = 1
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.setURLSchemeHandler(mockURLSchemeHandler, forURLScheme: mockURLScheme)
|
||||
|
||||
downView = try? DownView(frame: .zero, markdownString: "[Link](\(mockURL.absoluteString))", openLinksInBrowser: true, configuration: configuration, didLoadSuccessfully: didLoadSuccessfully)
|
||||
downView = try? DownView(frame: .zero,
|
||||
markdownString: "[Link](\(mockURL.absoluteString))",
|
||||
openLinksInBrowser: true,
|
||||
configuration: configuration,
|
||||
didLoadSuccessfully: didLoadSuccessfully)
|
||||
|
||||
waitForExpectations(timeout: 10) { (error: Error?) in
|
||||
if let error = error {
|
||||
@@ -230,15 +252,17 @@ let x = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension DownViewTests {
|
||||
|
||||
func _pageContents(for downView: DownView, completion: @escaping (_ htmlString: String?) -> ()) {
|
||||
|
||||
func _pageContents(for downView: DownView, completion: @escaping (_ htmlString: String?) -> Void) {
|
||||
downView.evaluateJavaScript("document.documentElement.outerHTML.toString()") { (html: Any?, _) in
|
||||
completion(html as? String)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,7 +12,8 @@ import XCTest
|
||||
class NSAttributedStringTests: XCTestCase {
|
||||
|
||||
func testAttributedStringBindingsWork() {
|
||||
let attributedString = try? Down(markdownString: "## [Down](https://github.com/iwasrobbed/Down)").toAttributedString()
|
||||
let markdown = "## [Down](https://github.com/johnxnguyen/Down)"
|
||||
let attributedString = try? Down(markdownString: markdown).toAttributedString()
|
||||
XCTAssertNotNil(attributedString)
|
||||
XCTAssertTrue(attributedString!.string == "Down\n")
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class BlockQuoteStyleTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
///
|
||||
/// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
/// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
///
|
||||
// # Important
|
||||
//
|
||||
// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
|
||||
// MARK: - Alignment
|
||||
|
||||
@@ -204,5 +205,7 @@ class BlockQuoteStyleTests: StylerTestSuite {
|
||||
// Then
|
||||
assertStyle(for: markdown, width: .wide)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class CodeBlockStyleTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
@@ -50,4 +52,7 @@ class CodeBlockStyleTests: StylerTestSuite {
|
||||
// Then
|
||||
assertStyle(for: markdown, width: .wide)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class DownDebugLayoutManagerTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
@@ -40,3 +42,5 @@ class DownDebugLayoutManagerTests: StylerTestSuite {
|
||||
assertStyle(for: markdown, width: .wide, showLineFragments: true)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class HeadingStyleTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
///
|
||||
/// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
/// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
///
|
||||
// # Important
|
||||
//
|
||||
// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
|
||||
// MARK: - Heading Levels
|
||||
|
||||
@@ -103,4 +104,7 @@ class HeadingStyleTests: StylerTestSuite {
|
||||
// Then
|
||||
assertStyle(for: markdown, width: .wide)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+2
-1
@@ -9,7 +9,7 @@
|
||||
import XCTest
|
||||
@testable import Down
|
||||
|
||||
class CGPoint_TranslateTests: XCTestCase {
|
||||
class CGPointTranslateTests: XCTestCase {
|
||||
|
||||
func testPointTranslation() {
|
||||
// Given
|
||||
@@ -21,4 +21,5 @@ class CGPoint_TranslateTests: XCTestCase {
|
||||
// Then
|
||||
XCTAssertEqual(CGPoint(x: 4, y: 6), result)
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -9,7 +9,7 @@
|
||||
import XCTest
|
||||
@testable import Down
|
||||
|
||||
class CGRect_HelpersTests: XCTestCase {
|
||||
class CGRectHelpersTests: XCTestCase {
|
||||
|
||||
func testRectInitializationWithBoundaries() {
|
||||
// When
|
||||
@@ -29,4 +29,5 @@ class CGRect_HelpersTests: XCTestCase {
|
||||
// Then
|
||||
XCTAssertEqual(CGRect(x: 6, y: 8, width: 3, height: 4), result)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
import XCTest
|
||||
@testable import Down
|
||||
|
||||
class NSAttributedString_HelpersTests: XCTestCase {
|
||||
class NSAttributedStringHelpersTests: XCTestCase {
|
||||
|
||||
let dummyKey = NSAttributedString.Key(rawValue: "key")
|
||||
let dummyValue = "value"
|
||||
@@ -91,8 +91,7 @@ class NSAttributedString_HelpersTests: XCTestCase {
|
||||
XCTAssertEqual(result[1], NSRange(location: 19, length: 4)) // "you "
|
||||
}
|
||||
|
||||
// MARK: - Missing Attribute Ranges
|
||||
|
||||
// MARK: - Missing Attribute Ranges
|
||||
|
||||
func testRangesMissingAttribute_None() {
|
||||
// Given
|
||||
@@ -165,7 +164,7 @@ class NSAttributedString_HelpersTests: XCTestCase {
|
||||
|
||||
func testParagraphRanges() {
|
||||
// Given
|
||||
let sut = NSAttributedString(string:"Hello\nhello\nworld")
|
||||
let sut = NSAttributedString(string: "Hello\nhello\nworld")
|
||||
|
||||
// When
|
||||
let result = sut.paragraphRanges()
|
||||
@@ -180,7 +179,7 @@ class NSAttributedString_HelpersTests: XCTestCase {
|
||||
func testParagraphRangesOfStringThatHasParagraphSeparators() {
|
||||
// Given
|
||||
let separator = "\u{2029}"
|
||||
let sut = NSAttributedString(string:"Hello\(separator)hello\(separator)world")
|
||||
let sut = NSAttributedString(string: "Hello\(separator)hello\(separator)world")
|
||||
|
||||
// When
|
||||
let result = sut.paragraphRanges()
|
||||
@@ -194,7 +193,7 @@ class NSAttributedString_HelpersTests: XCTestCase {
|
||||
|
||||
func testParagraphRangesOfStringWithLargeBreaks() {
|
||||
// Given
|
||||
let sut = NSAttributedString(string:"Hello\n\nhello\n\n\nworld")
|
||||
let sut = NSAttributedString(string: "Hello\n\nhello\n\n\nworld")
|
||||
|
||||
// When
|
||||
let result = sut.paragraphRanges()
|
||||
@@ -206,28 +205,29 @@ class NSAttributedString_HelpersTests: XCTestCase {
|
||||
XCTAssertEqual(result[2], NSRange(location: 15, length: 5)) // "world
|
||||
}
|
||||
|
||||
// MARK: - Enumeration
|
||||
// MARK: - Enumeration
|
||||
|
||||
func testEnumerationOfAttributes() {
|
||||
// Given
|
||||
let sut = NSMutableAttributedString()
|
||||
sut.append(make("Hello ", attributed: true))
|
||||
sut.append(make("world ", attributed: true))
|
||||
sut.append(make("how do "))
|
||||
sut.append(make("you ", attributed: true))
|
||||
sut.append(make("do?"))
|
||||
func testEnumerationOfAttributes() {
|
||||
// Given
|
||||
let sut = NSMutableAttributedString()
|
||||
sut.append(make("Hello ", attributed: true))
|
||||
sut.append(make("world ", attributed: true))
|
||||
sut.append(make("how do "))
|
||||
sut.append(make("you ", attributed: true))
|
||||
sut.append(make("do?"))
|
||||
|
||||
// When
|
||||
var result = [(String, NSRange)]()
|
||||
sut.enumerateAttributes(for: dummyKey) { (attr: String, range) in
|
||||
result.append((attr, range))
|
||||
// When
|
||||
var result = [(String, NSRange)]()
|
||||
sut.enumerateAttributes(for: dummyKey) { (attr: String, range) in
|
||||
result.append((attr, range))
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertEqual(result[0].0, "value")
|
||||
XCTAssertEqual(result[0].1, NSRange(location: 0, length: 12)) // "Hello world "
|
||||
XCTAssertEqual(result[1].0, "value")
|
||||
XCTAssertEqual(result[1].1, NSRange(location: 19, length: 4)) // "you "
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertEqual(result[0].0, "value")
|
||||
XCTAssertEqual(result[0].1, NSRange(location: 0, length: 12)) // "Hello world "
|
||||
XCTAssertEqual(result[1].0, "value")
|
||||
XCTAssertEqual(result[1].1, NSRange(location: 19, length: 4)) // "you "
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import XCTest
|
||||
@testable import Down
|
||||
|
||||
class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
class NSMutableAttributedStringAttributesTests: XCTestCase {
|
||||
|
||||
private let key1 = NSAttributedString.Key("dummyKey1")
|
||||
private let key2 = NSAttributedString.Key("dummyKey2")
|
||||
@@ -31,7 +31,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
|
||||
attributeRanges = sut.ranges(of: key2)
|
||||
XCTAssertEqual(attributeRanges, [sut.wholeRange])
|
||||
XCTAssertTrue(value(for: key2, inRange: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
XCTAssertTrue(value(for: key2, in: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
}
|
||||
|
||||
func testAddingAttributes() {
|
||||
@@ -44,7 +44,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
// Then
|
||||
let attributeRanges = sut.ranges(of: key1)
|
||||
XCTAssertEqual(attributeRanges, [sut.wholeRange])
|
||||
XCTAssertTrue(value(for: key1, inRange: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
XCTAssertTrue(value(for: key1, in: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
}
|
||||
|
||||
func testAddingAttribute() {
|
||||
@@ -57,7 +57,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
// Then
|
||||
let attributeRanges = sut.ranges(of: key1)
|
||||
XCTAssertEqual(attributeRanges, [sut.wholeRange])
|
||||
XCTAssertTrue(value(for: key1, inRange: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
XCTAssertTrue(value(for: key1, in: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
}
|
||||
|
||||
func testRemovingAttribute() {
|
||||
@@ -90,7 +90,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
let attributeRanges = sut.ranges(of: .foregroundColor)
|
||||
XCTAssertEqual(attributeRanges.count, 1)
|
||||
XCTAssertEqual(attributeRanges.first, NSRange(location: 0, length: 12))
|
||||
XCTAssertTrue(value(for: .foregroundColor, inRange: attributeRanges.first!, isEqualTo: DownColor.yellow, sut: sut))
|
||||
XCTAssertTrue(value(for: .foregroundColor, in: attributeRanges.first!, isEqualTo: DownColor.yellow, sut: sut))
|
||||
}
|
||||
|
||||
func testUpdatingAttribute() {
|
||||
@@ -105,7 +105,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
// Then
|
||||
let attributeRanges = sut.ranges(of: key1)
|
||||
XCTAssertEqual(attributeRanges, [sut.wholeRange])
|
||||
XCTAssertTrue(value(for: key1, inRange: attributeRanges.first!, isEqualTo: dummyValue.uppercased(), sut: sut))
|
||||
XCTAssertTrue(value(for: key1, in: attributeRanges.first!, isEqualTo: dummyValue.uppercased(), sut: sut))
|
||||
}
|
||||
|
||||
func testUpdatingAttributeInRange() {
|
||||
@@ -115,7 +115,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
let rangeOfSecondWord = NSRange(location: 6, length: 5)
|
||||
|
||||
// When
|
||||
sut.updateExistingAttributes(for: key1, in: rangeOfFirstWord) { (value: String) in
|
||||
sut.updateExistingAttributes(for: key1, in: rangeOfFirstWord) { _ in
|
||||
"some new value"
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
let attributeRanges = sut.ranges(of: key1)
|
||||
XCTAssertEqual(attributeRanges.count, 2)
|
||||
XCTAssertEqual(attributeRanges, [rangeOfFirstWord, rangeOfSecondWord])
|
||||
XCTAssertTrue(value(for: key1, inRange: attributeRanges.first!, isEqualTo: "some new value", sut: sut))
|
||||
XCTAssertTrue(value(for: key1, in: attributeRanges.first!, isEqualTo: "some new value", sut: sut))
|
||||
}
|
||||
|
||||
func testUpdatingAttributeThatDidNotExistInRangeDoesNothing() {
|
||||
@@ -132,7 +132,7 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
let rangeOfFirstWord = NSRange(location: 0, length: 6)
|
||||
|
||||
// When
|
||||
sut.updateExistingAttributes(for: key1, in: rangeOfFirstWord) { (value: String) in
|
||||
sut.updateExistingAttributes(for: key1, in: rangeOfFirstWord) { _ in
|
||||
"some new value"
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
let attributeRanges = sut.ranges(of: key1)
|
||||
XCTAssertEqual(attributeRanges.count, 2)
|
||||
XCTAssertEqual(attributeRanges, [rangeOfFirstWord, rangeOfSecondWord])
|
||||
XCTAssertTrue(value(for: key1, inRange: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
XCTAssertTrue(value(for: key1, inRange: attributeRanges.last!, isEqualTo: "some new value", sut: sut))
|
||||
XCTAssertTrue(value(for: key1, in: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
XCTAssertTrue(value(for: key1, in: attributeRanges.last!, isEqualTo: "some new value", sut: sut))
|
||||
}
|
||||
|
||||
func testAdddingAttributeInMissingRangesDoesNothingIfNoMissingRanges() {
|
||||
@@ -171,19 +171,25 @@ class NSMutableAttributedString_AttributesTests: XCTestCase {
|
||||
let attributeRanges = sut.ranges(of: key1)
|
||||
XCTAssertEqual(attributeRanges.count, 1)
|
||||
XCTAssertEqual(attributeRanges.first!, sut.wholeRange)
|
||||
XCTAssertTrue(value(for: key1, inRange: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
XCTAssertTrue(value(for: key1, in: attributeRanges.first!, isEqualTo: dummyValue, sut: sut))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension NSMutableAttributedString_AttributesTests {
|
||||
private extension NSMutableAttributedStringAttributesTests {
|
||||
|
||||
func countAttribute(_ name: NSAttributedString.Key, in str: NSAttributedString) -> Int {
|
||||
str.ranges(of: name).count
|
||||
}
|
||||
|
||||
func value<A: Equatable>(for name: NSAttributedString.Key, inRange: NSRange, isEqualTo aValue: A, sut: NSMutableAttributedString) -> Bool {
|
||||
func value<A: Equatable>(for name: NSAttributedString.Key,
|
||||
in range: NSRange,
|
||||
isEqualTo aValue: A,
|
||||
sut: NSMutableAttributedString) -> Bool {
|
||||
|
||||
var effectiveRange = NSRange()
|
||||
let value = sut.attribute(name, at: inRange.location, effectiveRange: &effectiveRange) as? A
|
||||
return value == aValue && effectiveRange == inRange
|
||||
let value = sut.attribute(name, at: range.location, effectiveRange: &effectiveRange) as? A
|
||||
return value == aValue && effectiveRange == range
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class InlineStyleTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
///
|
||||
/// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
/// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
///
|
||||
// # Important
|
||||
//
|
||||
// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
|
||||
// MARK: - Simple
|
||||
|
||||
@@ -91,4 +92,7 @@ class InlineStyleTests: StylerTestSuite {
|
||||
// Then
|
||||
assertStyle(for: markdown, width: .wide)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class LinkStyleTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
@@ -17,7 +19,7 @@ class LinkStyleTests: StylerTestSuite {
|
||||
func testThat_Link_IsStyled() {
|
||||
// Given
|
||||
let markdown = """
|
||||
Praesent facilisis [pellentesque](www.example.com) ipsum at pulvinar. Sed consectetur augue vel mattis hendrerit.
|
||||
Praesent facilisis [pellentesque](www.example.com) ipsum at pulvinar. Sed consectetur augue.
|
||||
"""
|
||||
|
||||
// Then
|
||||
@@ -33,4 +35,7 @@ class LinkStyleTests: StylerTestSuite {
|
||||
// Then
|
||||
assertStyle(for: markdown, width: .narrow)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class ListItemStyleTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
///
|
||||
/// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
/// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
///
|
||||
// # Important
|
||||
//
|
||||
// Snapshot tests must be run on the same simulator used to record the reference snapshots, otherwise
|
||||
// the comparison may fail. These tests were recorded on the **iPhone 12** simulator.
|
||||
|
||||
// MARK: - Prefix Alignment
|
||||
|
||||
@@ -262,4 +263,7 @@ class ListItemStyleTests: StylerTestSuite {
|
||||
// Then
|
||||
assertStyle(for: markdown, width: .narrow)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
import XCTest
|
||||
import SnapshotTesting
|
||||
@testable import Down
|
||||
@@ -18,7 +20,7 @@ class StylerTestSuite: XCTestCase {
|
||||
|
||||
var textContainerInset: UIEdgeInsets!
|
||||
|
||||
// MARK: - Lifecycle
|
||||
// MARK: - Life cycle
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
@@ -42,33 +44,52 @@ class StylerTestSuite: XCTestCase {
|
||||
testName: String = #function,
|
||||
line: UInt = #line) {
|
||||
|
||||
let view = self.view(for: markdown, width: width, configuration: configuration, showLineFragments: showLineFragments)
|
||||
let maybeView = try? self.view(for: markdown,
|
||||
width: width,
|
||||
configuration: configuration,
|
||||
showLineFragments: showLineFragments)
|
||||
|
||||
let failure = verifySnapshot(matching: view, as: .image, record: recording, file: file, testName: testName, line: line)
|
||||
guard let view = maybeView else {
|
||||
return XCTFail("Failed to generate markdown view.", file: file, line: line)
|
||||
}
|
||||
|
||||
let failure = verifySnapshot(matching: view,
|
||||
as: .image,
|
||||
record: recording,
|
||||
file: file,
|
||||
testName: testName,
|
||||
line: line)
|
||||
|
||||
guard let message = failure else { return }
|
||||
|
||||
XCTFail(message, file: file, line: line)
|
||||
}
|
||||
|
||||
func view(for markdown: String, width: Width, configuration: DownStylerConfiguration?, showLineFragments: Bool = false) -> DownTextView {
|
||||
func view(for markdown: String,
|
||||
width: Width,
|
||||
configuration: DownStylerConfiguration?,
|
||||
showLineFragments: Bool = false) throws -> DownTextView {
|
||||
|
||||
// To make the snapshots the same size of the text content, we set a huge height then resize the view
|
||||
// to the content size.
|
||||
let frame = CGRect(x: 0, y: 0, width: width.rawValue, height: 5000)
|
||||
let textView = showLineFragments ? DownDebugTextView(frame: frame) : DownTextView(frame: frame)
|
||||
textView.textContainerInset = textContainerInset
|
||||
textView.attributedText = attributedString(for: markdown, configuration: configuration)
|
||||
textView.attributedText = try attributedString(for: markdown, configuration: configuration)
|
||||
textView.layoutIfNeeded()
|
||||
textView.resizeToContentSize()
|
||||
return textView
|
||||
}
|
||||
|
||||
private func attributedString(for markdown: String, configuration: DownStylerConfiguration?) -> NSAttributedString {
|
||||
private func attributedString(for markdown: String,
|
||||
configuration: DownStylerConfiguration?) throws -> NSAttributedString {
|
||||
|
||||
let down = Down(markdownString: markdown)
|
||||
let styler = DownStyler(configuration: configuration ?? .testConfiguration)
|
||||
return try! down.toAttributedString(styler: styler)
|
||||
return try down.toAttributedString(styler: styler)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StylerTestSuite {
|
||||
|
||||
@@ -76,14 +97,15 @@ extension StylerTestSuite {
|
||||
case narrow = 300
|
||||
case wide = 600
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension DownTextView {
|
||||
|
||||
func resizeToContentSize() {
|
||||
frame = .init(origin: frame.origin, size: .init(width: contentSize.width, height: contentSize.height))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension DownStylerConfiguration {
|
||||
@@ -160,4 +182,7 @@ private extension DownStylerConfiguration {
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
// Copyright © 2016-2019 Down. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
class ThematicBreakSyleTests: StylerTestSuite {
|
||||
|
||||
/// # Important
|
||||
@@ -64,4 +66,7 @@ class ThematicBreakSyleTests: StylerTestSuite {
|
||||
// Then
|
||||
assertStyle(for: markdown, width: .wide, configuration: configuration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1 @@
|
||||
## [Down](https://github.com/johnxnnguyen/Down)
|
||||
@@ -0,0 +1,2 @@
|
||||
.SS
|
||||
Down (https://github.com/johnxnnguyen/Down)
|
||||
@@ -0,0 +1 @@
|
||||
<h2><a href="https://github.com/johnxnnguyen/Down">Down</a></h2>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user