commit e6db7369cdc72135df9943fc6541488eb57ca129
parent 49ff8824acb4410239c37b76ac630fe50b9f4f48
Author: William Casarin <jb55@jb55.com>
Date: Thu, 28 Jul 2022 13:10:00 -0700
Fix hashtag parsing
Changelog-Fixed: No longer parse hashtags in urls
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
3 files changed, 88 insertions(+), 28 deletions(-)
diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift
@@ -132,6 +132,19 @@ func is_hashtag_char(_ c: Character) -> Bool {
return c.isLetter || c.isNumber
}
+func prev_char(_ p: Parser, n: Int) -> Character? {
+ if p.pos - n < 0 {
+ return nil
+ }
+
+ let ind = p.str.index(p.str.startIndex, offsetBy: p.pos - n)
+ return p.str[ind]
+}
+
+func is_punctuation(_ c: Character) -> Bool {
+ return c.isWhitespace || c.isPunctuation
+}
+
func parse_hashtag(_ p: Parser) -> String? {
let start = p.pos
@@ -139,6 +152,13 @@ func parse_hashtag(_ p: Parser) -> String? {
return nil
}
+ if let prev = prev_char(p, n: 2) {
+ // we don't allow adjacent hashtags
+ if !is_punctuation(prev) {
+ return nil
+ }
+ }
+
guard let str = parse_while(p, match: is_hashtag_char) else {
p.pos = start
return nil
diff --git a/damus/Models/PostBlock.swift b/damus/Models/PostBlock.swift
@@ -12,25 +12,25 @@ enum PostBlock {
case ref(ReferencedId)
case hashtag(String)
- var is_text: Bool {
- if case .text = self {
- return true
+ var is_text: String? {
+ if case .text(let txt) = self {
+ return txt
}
- return false
+ return nil
}
- var is_hashtag: Bool {
- if case .hashtag = self {
- return true
+ var is_hashtag: String? {
+ if case .hashtag(let ht) = self {
+ return ht
}
- return false
+ return nil
}
- var is_ref: Bool {
- if case .ref = self {
- return true
+ var is_ref: ReferencedId? {
+ if case .ref(let ref) = self {
+ return ref
}
- return false
+ return nil
}
}
diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift
@@ -35,6 +35,46 @@ class ReplyTests: XCTestCase {
XCTAssertEqual(ref.is_mention!.ref.ref_id, "event_id")
}
+ func testUrlAnchorsAreNotHashtags() {
+ let content = "this is my link: https://jb55.com/index.html#buybitcoin this is not a hashtag!"
+ let blocks = parse_post_blocks(content: content)
+
+ XCTAssertEqual(blocks.count, 1)
+ XCTAssertEqual(blocks[0].is_text != nil, true)
+ }
+
+ func testHashtagsInQuote() {
+ let content = "This is my \"#awesome post\""
+ let blocks = parse_post_blocks(content: content)
+
+ XCTAssertEqual(blocks.count, 3)
+ XCTAssertEqual(blocks[0].is_text, "This is my \"")
+ XCTAssertEqual(blocks[1].is_hashtag, "awesome")
+ XCTAssertEqual(blocks[2].is_text, " post\"")
+ }
+
+ func testHashtagAtStartWorks() {
+ let content = "#hashtag"
+ let blocks = parse_post_blocks(content: content)
+ XCTAssertEqual(blocks.count, 3)
+ XCTAssertEqual(blocks[1].is_hashtag, "hashtag")
+ }
+
+ func testGroupOfHashtags() {
+ let content = "#hashtag#what#nope"
+ let blocks = parse_post_blocks(content: content)
+ XCTAssertEqual(blocks.count, 3)
+ XCTAssertEqual(blocks[1].is_hashtag, "hashtag")
+ XCTAssertEqual(blocks[2].is_text, "#what#nope")
+
+ switch blocks[1] {
+ case .hashtag(let htag):
+ XCTAssertEqual(htag, "hashtag")
+ default:
+ break
+ }
+ }
+
func testRootReplyWithMention() throws {
let content = "this is #[1] a mention"
let tags = [["e", "thread_id"], ["e", "mentioned_id"]]
@@ -83,7 +123,7 @@ class ReplyTests: XCTestCase {
//let tags: [[String]] = []
let blocks = parse_post_blocks(content: content)
- let mentions = blocks.filter { $0.is_ref }
+ let mentions = blocks.filter { $0.is_ref != nil }
XCTAssertEqual(mentions.count, 10)
}
@@ -221,9 +261,9 @@ class ReplyTests: XCTestCase {
XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3)
- XCTAssertTrue(parsed[0].is_text)
- XCTAssertTrue(parsed[1].is_ref)
- XCTAssertTrue(parsed[2].is_text)
+ XCTAssertEqual(parsed[0].is_text, "this is a nostr:")
+ XCTAssertTrue(parsed[1].is_ref != nil)
+ XCTAssertEqual(parsed[2].is_text, ":\(id) event mention")
guard case .ref(let ref) = parsed[1] else {
XCTAssertTrue(false)
@@ -268,9 +308,9 @@ class ReplyTests: XCTestCase {
XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3)
- XCTAssertTrue(parsed[0].is_text)
- XCTAssertTrue(parsed[1].is_ref)
- XCTAssertTrue(parsed[2].is_text)
+ XCTAssertEqual(parsed[0].is_text, "this is a ")
+ XCTAssertNotNil(parsed[1].is_ref)
+ XCTAssertEqual(parsed[2].is_text, " event mention")
guard case .ref(let ref) = parsed[1] else {
XCTAssertTrue(false)
@@ -299,9 +339,9 @@ class ReplyTests: XCTestCase {
XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3)
- XCTAssertTrue(parsed[0].is_text)
- XCTAssertTrue(parsed[1].is_ref)
- XCTAssertTrue(parsed[2].is_text)
+ XCTAssertEqual(parsed[0].is_text, "this is a ")
+ XCTAssertNotNil(parsed[1].is_ref)
+ XCTAssertEqual(parsed[2].is_text, " event mention")
guard case .ref(let ref) = parsed[1] else {
XCTAssertTrue(false)
@@ -330,9 +370,9 @@ class ReplyTests: XCTestCase {
XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3)
- XCTAssertTrue(parsed[0].is_text)
- XCTAssertTrue(parsed[1].is_ref)
- XCTAssertTrue(parsed[2].is_text)
+ XCTAssertEqual(parsed[0].is_text, "this is a ")
+ XCTAssertNotNil(parsed[1].is_ref)
+ XCTAssertEqual(parsed[2].is_text, " event mention")
guard case .ref(let ref) = parsed[1] else {
XCTAssertTrue(false)
@@ -361,9 +401,9 @@ class ReplyTests: XCTestCase {
XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3)
- XCTAssertTrue(parsed[0].is_text)
- XCTAssertTrue(parsed[1].is_ref)
- XCTAssertTrue(parsed[2].is_text)
+ XCTAssertEqual(parsed[0].is_text, "this is a ")
+ XCTAssertNotNil(parsed[1].is_ref)
+ XCTAssertEqual(parsed[2].is_text, " mention")
guard case .ref(let ref) = parsed[1] else {
XCTAssertTrue(false)