From 79467395b576a4da9a86e99a90120258e8d88d98 Mon Sep 17 00:00:00 2001 From: Simon Fairbairn Date: Sat, 11 Apr 2020 16:02:15 +1200 Subject: [PATCH] Updates readme --- README.md | 53 ++++++++++++++--- Sources/SwiftyMarkdown/CharacterRule.swift | 8 +-- Sources/SwiftyMarkdown/SwiftyMarkdown.swift | 5 ++ .../SwiftyScannerNonRepeating.swift | 21 +++---- .../xcschemes/SwiftyMarkdown-Package.xcscheme | 2 +- .../SwiftyMarkdownCharacterTests.swift | 58 +++++++++++++++---- .../SwiftyMarkdownLinkTests.swift | 43 ++++++++++---- .../XCTest+SwiftyMarkdown.swift | 13 +++-- 8 files changed, 149 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 89ed15a..418f524 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,12 @@ label.attributedText = md.attributedString() [Links](http://voyagetravelapps.com/) ![Images]() + [Referenced Links][1] + ![Referenced Images][2] + + [1]: http://voyagetravelapps.com/ + [2]: + > Blockquotes - Bulleted @@ -292,13 +298,46 @@ enum CharacterStyle : CharacterStyling { } static public var characterRules = [ - CharacterRule(openTag: "[", intermediateTag: "](", closingTag: ")", escapeCharacter: "\\", styles: [1 : [CharacterStyle.link]], maxTags: 1), - CharacterRule(openTag: "`", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.code]], maxTags: 1), - CharacterRule(openTag: "*", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3), - CharacterRule(openTag: "_", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3) + CharacterRule(primaryTag: CharacterRuleTag(tag: "[", type: .open), otherTags: [ + CharacterRuleTag(tag: "]", type: .close), + CharacterRuleTag(tag: "[", type: .metadataOpen), + CharacterRuleTag(tag: "]", type: .metadataClose) + ], styles: [1 : CharacterStyle.link], metadataLookup: true, definesBoundary: true), + CharacterRule(primaryTag: CharacterRuleTag(tag: "`", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.code], shouldCancelRemainingTags: true, balancedTags: true), + CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2), + CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2) ] ``` +These Character Rules are defined by SwiftyMarkdown: + + public struct CharacterRule : CustomStringConvertible { + + public let primaryTag : CharacterRuleTag + public let tags : [CharacterRuleTag] + public let escapeCharacters : [Character] + public let styles : [Int : CharacterStyling] + public let minTags : Int + public let maxTags : Int + public var metadataLookup : Bool = false + public var definesBoundary = false + public var shouldCancelRemainingRules = false + public var balancedTags = false + } + +1. `primaryTag`: Each rule must have at least one tag and it can be one of `repeating`, `open`, `close`, `metadataOpen`, or `metadataClose`. `repeating` tags are tags that have identical open and close characters (and often have more than 1 style depending on how many are in a group). For example, the `*` tag used in Markdown. +2. `tags`: An array of other tags that the rule can look for. This is where you would put the `close` tag for a custom rule, for example. +3. `escapeCharacters`: The characters that appear prior to any of the tag characters that tell the scanner to ignore the tag. +4. `styles`: The styles that should be applied to every character between the opening and closing tags. +5. `minTags`: The minimum number of repeating characters to be considered a successful match. For example, setting the `primaryTag` to `*` and the `minTag` to 2 would mean that `**foo**` would be a successful match wheras `*bar*` would not. +6. `maxTags`: The maximum number of repeating characters to be considered a successful match. +7. `metadataLookup`: Used for Markdown reference links. Tells the scanner to try to look up the metadata from this dictionary, rather than from the inline result. +8. `definesBoundary`: In order for open and close tags to be successful, the `boundaryCount` for a given location in the string needs to be the same. Setting this property to `true` means that this rule will increase the `boundaryCount` for every character between its opening and closing tags. For example, the `[` rule defines a boundary. After it is applied, the string `*foo[bar*]` becomes `*foobar*` with a boundaryCount `00001111`. Applying the `*` rule results in the output `*foobar*` because the opening `*` tag and the closing `*` tag now have different `boundaryCount` values. It's basically a way to fix the `**[should not be bold**](url)` problem in Markdown. +9. `shouldCancelRemainingTags`: A successful match will mark every character between the opening and closing tags as complete, thereby preventing any further rules from being applied to those characters. +10. `balancedTags`: This flag requires that the opening and closing tags be of exactly equal length. E.g. If this is set to true, `**foo*` would result in `**foo*`. If it was false, the output would be `*foo`. + + + #### Rule Subsets If you want to only support a small subset of Markdown, it's now easy to do. @@ -308,8 +347,8 @@ This example would only process strings with `*` and `_` characters, ignoring li SwiftyMarkdown.lineRules = [] SwiftyMarkdown.characterRules = [ - CharacterRule(openTag: "*", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3), - CharacterRule(openTag: "_", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3) + CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2), + CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2) ] ``` @@ -330,7 +369,7 @@ enum Characters : CharacterStyling { } let characterRules = [ - CharacterRule(openTag: "%", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.elf]], maxTags: 1) + CharacterRule(primaryTag: CharacterRuleTag(tag: "%", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.elf]) ] let processor = SwiftyTokeniser( with : characterRules ) diff --git a/Sources/SwiftyMarkdown/CharacterRule.swift b/Sources/SwiftyMarkdown/CharacterRule.swift index 1b953ec..ab02fbc 100644 --- a/Sources/SwiftyMarkdown/CharacterRule.swift +++ b/Sources/SwiftyMarkdown/CharacterRule.swift @@ -53,27 +53,25 @@ public struct CharacterRule : CustomStringConvertible { return self.primaryTag.type == .repeating } public var definesBoundary = false - public var shouldCancelRemainingTags = false + public var shouldCancelRemainingRules = false public var balancedTags = false public var description: String { return "Character Rule with Open tag: \(self.primaryTag.tag) and current styles : \(self.styles) " } - - public func tag( for type : CharacterRuleTagType ) -> CharacterRuleTag? { return self.tags.filter({ $0.type == type }).first ?? nil } - public init(primaryTag: CharacterRuleTag, otherTags: [CharacterRuleTag], escapeCharacters : [Character] = ["\\"], styles: [Int : CharacterStyling] = [:], minTags : Int = 1, maxTags : Int = 1, metadataLookup : Bool = false, definesBoundary : Bool = false, shouldCancelRemainingTags : Bool = false, balancedTags : Bool = false) { + public init(primaryTag: CharacterRuleTag, otherTags: [CharacterRuleTag], escapeCharacters : [Character] = ["\\"], styles: [Int : CharacterStyling] = [:], minTags : Int = 1, maxTags : Int = 1, metadataLookup : Bool = false, definesBoundary : Bool = false, shouldCancelRemainingRules : Bool = false, balancedTags : Bool = false) { self.primaryTag = primaryTag self.tags = otherTags self.escapeCharacters = escapeCharacters self.styles = styles self.metadataLookup = metadataLookup self.definesBoundary = definesBoundary - self.shouldCancelRemainingTags = shouldCancelRemainingTags + self.shouldCancelRemainingRules = shouldCancelRemainingRules self.minTags = maxTags < minTags ? maxTags : minTags self.maxTags = minTags > maxTags ? minTags : maxTags self.balancedTags = balancedTags diff --git a/Sources/SwiftyMarkdown/SwiftyMarkdown.swift b/Sources/SwiftyMarkdown/SwiftyMarkdown.swift index 7498e87..6749f29 100644 --- a/Sources/SwiftyMarkdown/SwiftyMarkdown.swift +++ b/Sources/SwiftyMarkdown/SwiftyMarkdown.swift @@ -168,6 +168,11 @@ If that is not set, then the system default will be used. ] static public var characterRules = [ + CharacterRule(primaryTag: CharacterRuleTag(tag: "![", type: .open), otherTags: [ + CharacterRuleTag(tag: "]", type: .close), + CharacterRuleTag(tag: "[", type: .metadataOpen), + CharacterRuleTag(tag: "]", type: .metadataClose) + ], styles: [1 : CharacterStyle.image], metadataLookup: true, definesBoundary: true), CharacterRule(primaryTag: CharacterRuleTag(tag: "![", type: .open), otherTags: [ CharacterRuleTag(tag: "]", type: .close), CharacterRuleTag(tag: "(", type: .metadataOpen), diff --git a/Sources/SwiftyMarkdown/SwiftyScannerNonRepeating.swift b/Sources/SwiftyMarkdown/SwiftyScannerNonRepeating.swift index cc42240..8d8170a 100644 --- a/Sources/SwiftyMarkdown/SwiftyScannerNonRepeating.swift +++ b/Sources/SwiftyMarkdown/SwiftyScannerNonRepeating.swift @@ -42,15 +42,19 @@ class SwiftyScannerNonRepeating { let metadata : [String : String] var pointer : Int = 0 + var spaceAndNewLine = CharacterSet.whitespacesAndNewlines var tagGroups : [TagGroup] = [] var isMetadataOpen = false + var enableLog = (ProcessInfo.processInfo.environment["SwiftyScannerScanner"] != nil) let currentPerfomanceLog = PerformanceLog(with: "SwiftyScannerScannerPerformanceLogging", identifier: "Scanner", log: OSLog.swiftyScannerPerformance) let log = PerformanceLog(with: "SwiftyScannerScanner", identifier: "Scanner", log: OSLog.swiftyScannerScanner) + + enum Position { case forward(Int) case backward(Int) @@ -278,7 +282,7 @@ class SwiftyScannerNonRepeating { if self.rule.definesBoundary { self.elements[idx].boundaryCount += 1 } - if self.rule.shouldCancelRemainingTags { + if self.rule.shouldCancelRemainingRules { self.elements[idx].boundaryCount = 1000 } } @@ -303,6 +307,7 @@ class SwiftyScannerNonRepeating { if shouldRemove { self.tagGroups.removeAll(where: { $0.groupID == id }) } + self.isMetadataOpen = false } func emptyRanges( _ ranges : inout [ClosedRange] ) { @@ -423,11 +428,6 @@ class SwiftyScannerNonRepeating { } } - var spaceAndNewLine = CharacterSet.whitespacesAndNewlines - - - - func scanRepeatingTags() { var groupID = "" @@ -485,9 +485,6 @@ class SwiftyScannerNonRepeating { } } - - - if !validTagGroup { if self.enableLog { os_log("Tag has whitespace on both sides", log: .swiftyScannerScanner, type: .info) @@ -497,7 +494,6 @@ class SwiftyScannerNonRepeating { } if let idx = tagGroups.firstIndex(where: { $0.groupID == groupID }) { - if tagType == .either { if tagGroups[idx].count == count { self.tagGroups[idx].tagRanges.append(openRange) @@ -518,9 +514,6 @@ class SwiftyScannerNonRepeating { } continue } - - - } var tagGroup = TagGroup(tagRanges: [openRange]) groupID = tagGroup.groupID @@ -528,7 +521,7 @@ class SwiftyScannerNonRepeating { tagGroup.count = count if self.enableLog { - os_log("New open tag found. Starting new Group with ID %@", log: OSLog.swiftyScannerScanner, type:.info , groupID) + os_log("New open tag found with characters %@. Starting new Group with ID %@", log: OSLog.swiftyScannerScanner, type:.info, self.elements[openRange].map( { String($0.character) }).joined(), groupID) } self.tagGroups.append(tagGroup) diff --git a/SwiftyMarkdown.xcodeproj/xcshareddata/xcschemes/SwiftyMarkdown-Package.xcscheme b/SwiftyMarkdown.xcodeproj/xcshareddata/xcschemes/SwiftyMarkdown-Package.xcscheme index 7c6d732..c2e7577 100644 --- a/SwiftyMarkdown.xcodeproj/xcshareddata/xcschemes/SwiftyMarkdown-Package.xcscheme +++ b/SwiftyMarkdown.xcodeproj/xcshareddata/xcschemes/SwiftyMarkdown-Package.xcscheme @@ -59,7 +59,7 @@ + isEnabled = "NO"> CharacterRule { switch self { @@ -36,14 +38,16 @@ enum Rule { return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "[" && !$0.metadataLookup }).first! case .backticks: return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "`" }).first! - case .strikethroughs: + case .tildes: return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "~" }).first! case .asterisks: return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "*" }).first! case .underscores: return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "_" }).first! case .referencedLinks: - return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "[" && $0.metadataLookup }).first! + return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "[" && $0.metadataLookup }).first! + case .referencedImages: + return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "![" && $0.metadataLookup }).first! } } } @@ -71,8 +75,9 @@ class SwiftyMarkdownCharacterTests : XCTestCase { let expectedStyles = challenge.tokens.compactMap({ $0.characterStyles as? [CharacterStyle] }) let linkTokens = tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) }) + let imageTokens = tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.image) ?? false) }) - return ChallengeReturn(tokens: tokens, stringTokens: stringTokens, links : linkTokens, attributedString: attributedString, foundStyles: existentTokenStyles, expectedStyles : expectedStyles) + return ChallengeReturn(tokens: tokens, stringTokens: stringTokens, links : linkTokens, images: imageTokens, attributedString: attributedString, foundStyles: existentTokenStyles, expectedStyles : expectedStyles) } }