From e6db7369cdc72135df9943fc6541488eb57ca129 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Thu, 28 Jul 2022 13:10:00 -0700 Subject: [PATCH] Fix hashtag parsing Changelog-Fixed: No longer parse hashtags in urls Signed-off-by: William Casarin --- damus/Models/Mentions.swift | 20 ++++++++++ damus/Models/PostBlock.swift | 24 ++++++------ damusTests/ReplyTests.swift | 72 ++++++++++++++++++++++++++++-------- 3 files changed, 88 insertions(+), 28 deletions(-) diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift index 1524761..fc426ea 100644 --- 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 index 0ff0e9d..8f9118a 100644 --- 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 index f310132..cc415cd 100644 --- 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)