Updates readme

This commit is contained in:
Simon Fairbairn 2020-04-11 16:02:15 +12:00
parent 4e2f5c234e
commit 79467395b5
8 changed files with 149 additions and 54 deletions

View File

@ -92,6 +92,12 @@ label.attributedText = md.attributedString()
[Links](http://voyagetravelapps.com/)
![Images](<Name of asset in bundle>)
[Referenced Links][1]
![Referenced Images][2]
[1]: http://voyagetravelapps.com/
[2]: <Name of asset in bundle>
> 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 )

View File

@ -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

View File

@ -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),

View File

@ -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<Int>] ) {
@ -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)

View File

@ -59,7 +59,7 @@
<EnvironmentVariable
key = "SwiftyScannerScanner"
value = ""
isEnabled = "YES">
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "SwiftyScannerScannerPerformanceLogging"

View File

@ -11,12 +11,14 @@ import XCTest
class SwiftyMarkdownStylingTests: SwiftyMarkdownCharacterTests {
func testIsolatedCase() {
challenge = TokenTest(input: "a ```b`", output: "a ```b`", tokens : [
Token(type: .string, inputString: "a ```b`", characterStyles: [])
func off_testIsolatedCase() {
challenge = TokenTest(input: "*\\***\\****b*\\***\\****\\", output: "***b***\\", tokens : [
Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.italic]),
Token(type: .string, inputString: "*b**", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
Token(type: .string, inputString: "\\", characterStyles: [])
])
results = self.attempt(challenge, rules: [.backticks])
results = self.attempt(challenge)
if results.stringTokens.count == challenge.tokens.count {
for (idx, token) in results.stringTokens.enumerated() {
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
@ -508,12 +510,10 @@ class SwiftyMarkdownStylingTests: SwiftyMarkdownCharacterTests {
}
func testForExtremeEscapeCombinations() {
challenge = TokenTest(input: "Before *\\***\\****A bold string*\\***\\****\\ After", output: "Before ***A bold string***\\ After", tokens : [
Token(type: .string, inputString: "Before ", characterStyles: []),
Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.italic]),
Token(type: .string, inputString: "**", characterStyles: [CharacterStyle.bold]),
Token(type: .string, inputString: "A bold string**", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
Token(type: .string, inputString: "\\ After", characterStyles: [])
challenge = TokenTest(input: "\\****b\\****", output: "*b*", tokens : [
Token(type: .string, inputString: "*", characterStyles: []),
Token(type: .string, inputString: "b*", characterStyles: [CharacterStyle.bold, CharacterStyle.italic])
])
results = self.attempt(challenge)
if results.stringTokens.count == challenge.tokens.count {
@ -526,6 +526,42 @@ class SwiftyMarkdownStylingTests: SwiftyMarkdownCharacterTests {
}
XCTAssertEqual(results.foundStyles, results.expectedStyles)
XCTAssertEqual(results.attributedString.string, challenge.output)
challenge = TokenTest(input: "**\\**b*\\***", output: "*b*", tokens : [
Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.bold]),
Token(type: .string, inputString: "b", characterStyles: [CharacterStyle.italic, CharacterStyle.bold]),
Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.bold]),
])
results = self.attempt(challenge)
if results.stringTokens.count == challenge.tokens.count {
for (idx, token) in results.stringTokens.enumerated() {
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
}
} else {
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
}
XCTAssertEqual(results.foundStyles, results.expectedStyles)
XCTAssertEqual(results.attributedString.string, challenge.output)
// challenge = TokenTest(input: "Before *\\***\\****A bold string*\\***\\****\\ After", output: "Before ***A bold string***\\ After", tokens : [
// Token(type: .string, inputString: "Before ", characterStyles: []),
// Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.italic]),
// Token(type: .string, inputString: "**", characterStyles: [CharacterStyle.bold]),
// Token(type: .string, inputString: "A bold string**", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
// Token(type: .string, inputString: "\\ After", characterStyles: [])
// ])
// results = self.attempt(challenge)
// if results.stringTokens.count == challenge.tokens.count {
// for (idx, token) in results.stringTokens.enumerated() {
// XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
// XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
// }
// } else {
// XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
// }
// XCTAssertEqual(results.foundStyles, results.expectedStyles)
// XCTAssertEqual(results.attributedString.string, challenge.output)
}
func testThatExtraCharactersAreHandles() {

View File

@ -585,11 +585,10 @@ class SwiftyMarkdownLinkTests: SwiftyMarkdownCharacterTests {
}
XCTAssertEqual(results.attributedString.string, challenge.output)
XCTAssertEqual(results.foundStyles, results.expectedStyles)
var links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.image) ?? false) })
if links.count == 1 {
XCTAssertEqual(links[0].metadataStrings.first, "imageName")
if results.images.count == 1 {
XCTAssertEqual(results.images[0].metadataStrings.first, "imageName")
} else {
XCTFail("Incorrect link count. Expecting 1, found \(links.count)")
XCTFail("Incorrect link count. Expecting 1, found \(results.images.count)")
}
challenge = TokenTest(input: "An [![Image](imageName)](https://www.neverendingvoyage.com/)", output: "An ", tokens: [
@ -607,17 +606,37 @@ class SwiftyMarkdownLinkTests: SwiftyMarkdownCharacterTests {
}
XCTAssertEqual(results.attributedString.string, challenge.output)
XCTAssertEqual(results.foundStyles, results.expectedStyles)
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.image) ?? false) })
if links.count == 1 {
XCTAssertEqual(links[0].metadataStrings.first, "imageName")
if results.images.count == 1 {
XCTAssertEqual(results.images[0].metadataStrings.first, "imageName")
} else {
XCTFail("Incorrect link count. Expecting 1, found \(links.count)")
XCTFail("Incorrect link count. Expecting 1, found \(results.images.count)")
}
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
if links.count == 1 {
XCTAssertEqual(links[0].metadataStrings.last, "https://www.neverendingvoyage.com/")
if results.links.count == 1 {
XCTAssertEqual(results.links[0].metadataStrings.last, "https://www.neverendingvoyage.com/")
} else {
XCTFail("Incorrect link count. Expecting 1, found \(links.count)")
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
}
}
func testForReferencedImages() {
challenge = TokenTest(input: "A ![referenced image][image]\n[image]: imageName", output: "A ", tokens: [
Token(type: .string, inputString: "A ", characterStyles: []),
Token(type: .string, inputString: "referenced image", characterStyles: [CharacterStyle.image])
])
results = self.attempt(challenge)
if results.stringTokens.count == challenge.tokens.count {
for (idx, token) in results.stringTokens.enumerated() {
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
}
} else {
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
}
XCTAssertEqual(results.foundStyles, results.expectedStyles)
if results.images.count == 1 {
XCTAssertEqual(results.images[0].metadataStrings.first, "imageName")
} else {
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
}
}

View File

@ -14,6 +14,7 @@ struct ChallengeReturn {
let tokens : [Token]
let stringTokens : [Token]
let links : [Token]
let images : [Token]
let attributedString : NSAttributedString
let foundStyles : [[CharacterStyle]]
let expectedStyles : [[CharacterStyle]]
@ -26,7 +27,8 @@ enum Rule {
case images
case links
case referencedLinks
case strikethroughs
case referencedImages
case tildes
func asCharacterRule() -> 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)
}
}