Fix hashtag parsing
Changelog-Fixed: No longer parse hashtags in urls Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -132,6 +132,19 @@ func is_hashtag_char(_ c: Character) -> Bool {
|
|||||||
return c.isLetter || c.isNumber
|
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? {
|
func parse_hashtag(_ p: Parser) -> String? {
|
||||||
let start = p.pos
|
let start = p.pos
|
||||||
|
|
||||||
@@ -139,6 +152,13 @@ func parse_hashtag(_ p: Parser) -> String? {
|
|||||||
return nil
|
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 {
|
guard let str = parse_while(p, match: is_hashtag_char) else {
|
||||||
p.pos = start
|
p.pos = start
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -12,25 +12,25 @@ enum PostBlock {
|
|||||||
case ref(ReferencedId)
|
case ref(ReferencedId)
|
||||||
case hashtag(String)
|
case hashtag(String)
|
||||||
|
|
||||||
var is_text: Bool {
|
var is_text: String? {
|
||||||
if case .text = self {
|
if case .text(let txt) = self {
|
||||||
return true
|
return txt
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_hashtag: Bool {
|
var is_hashtag: String? {
|
||||||
if case .hashtag = self {
|
if case .hashtag(let ht) = self {
|
||||||
return true
|
return ht
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_ref: Bool {
|
var is_ref: ReferencedId? {
|
||||||
if case .ref = self {
|
if case .ref(let ref) = self {
|
||||||
return true
|
return ref
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,46 @@ class ReplyTests: XCTestCase {
|
|||||||
XCTAssertEqual(ref.is_mention!.ref.ref_id, "event_id")
|
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 {
|
func testRootReplyWithMention() throws {
|
||||||
let content = "this is #[1] a mention"
|
let content = "this is #[1] a mention"
|
||||||
let tags = [["e", "thread_id"], ["e", "mentioned_id"]]
|
let tags = [["e", "thread_id"], ["e", "mentioned_id"]]
|
||||||
@@ -83,7 +123,7 @@ class ReplyTests: XCTestCase {
|
|||||||
//let tags: [[String]] = []
|
//let tags: [[String]] = []
|
||||||
let blocks = parse_post_blocks(content: content)
|
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)
|
XCTAssertEqual(mentions.count, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,9 +261,9 @@ class ReplyTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertNotNil(parsed)
|
XCTAssertNotNil(parsed)
|
||||||
XCTAssertEqual(parsed.count, 3)
|
XCTAssertEqual(parsed.count, 3)
|
||||||
XCTAssertTrue(parsed[0].is_text)
|
XCTAssertEqual(parsed[0].is_text, "this is a nostr:")
|
||||||
XCTAssertTrue(parsed[1].is_ref)
|
XCTAssertTrue(parsed[1].is_ref != nil)
|
||||||
XCTAssertTrue(parsed[2].is_text)
|
XCTAssertEqual(parsed[2].is_text, ":\(id) event mention")
|
||||||
|
|
||||||
guard case .ref(let ref) = parsed[1] else {
|
guard case .ref(let ref) = parsed[1] else {
|
||||||
XCTAssertTrue(false)
|
XCTAssertTrue(false)
|
||||||
@@ -268,9 +308,9 @@ class ReplyTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertNotNil(parsed)
|
XCTAssertNotNil(parsed)
|
||||||
XCTAssertEqual(parsed.count, 3)
|
XCTAssertEqual(parsed.count, 3)
|
||||||
XCTAssertTrue(parsed[0].is_text)
|
XCTAssertEqual(parsed[0].is_text, "this is a ")
|
||||||
XCTAssertTrue(parsed[1].is_ref)
|
XCTAssertNotNil(parsed[1].is_ref)
|
||||||
XCTAssertTrue(parsed[2].is_text)
|
XCTAssertEqual(parsed[2].is_text, " event mention")
|
||||||
|
|
||||||
guard case .ref(let ref) = parsed[1] else {
|
guard case .ref(let ref) = parsed[1] else {
|
||||||
XCTAssertTrue(false)
|
XCTAssertTrue(false)
|
||||||
@@ -299,9 +339,9 @@ class ReplyTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertNotNil(parsed)
|
XCTAssertNotNil(parsed)
|
||||||
XCTAssertEqual(parsed.count, 3)
|
XCTAssertEqual(parsed.count, 3)
|
||||||
XCTAssertTrue(parsed[0].is_text)
|
XCTAssertEqual(parsed[0].is_text, "this is a ")
|
||||||
XCTAssertTrue(parsed[1].is_ref)
|
XCTAssertNotNil(parsed[1].is_ref)
|
||||||
XCTAssertTrue(parsed[2].is_text)
|
XCTAssertEqual(parsed[2].is_text, " event mention")
|
||||||
|
|
||||||
guard case .ref(let ref) = parsed[1] else {
|
guard case .ref(let ref) = parsed[1] else {
|
||||||
XCTAssertTrue(false)
|
XCTAssertTrue(false)
|
||||||
@@ -330,9 +370,9 @@ class ReplyTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertNotNil(parsed)
|
XCTAssertNotNil(parsed)
|
||||||
XCTAssertEqual(parsed.count, 3)
|
XCTAssertEqual(parsed.count, 3)
|
||||||
XCTAssertTrue(parsed[0].is_text)
|
XCTAssertEqual(parsed[0].is_text, "this is a ")
|
||||||
XCTAssertTrue(parsed[1].is_ref)
|
XCTAssertNotNil(parsed[1].is_ref)
|
||||||
XCTAssertTrue(parsed[2].is_text)
|
XCTAssertEqual(parsed[2].is_text, " event mention")
|
||||||
|
|
||||||
guard case .ref(let ref) = parsed[1] else {
|
guard case .ref(let ref) = parsed[1] else {
|
||||||
XCTAssertTrue(false)
|
XCTAssertTrue(false)
|
||||||
@@ -361,9 +401,9 @@ class ReplyTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertNotNil(parsed)
|
XCTAssertNotNil(parsed)
|
||||||
XCTAssertEqual(parsed.count, 3)
|
XCTAssertEqual(parsed.count, 3)
|
||||||
XCTAssertTrue(parsed[0].is_text)
|
XCTAssertEqual(parsed[0].is_text, "this is a ")
|
||||||
XCTAssertTrue(parsed[1].is_ref)
|
XCTAssertNotNil(parsed[1].is_ref)
|
||||||
XCTAssertTrue(parsed[2].is_text)
|
XCTAssertEqual(parsed[2].is_text, " mention")
|
||||||
|
|
||||||
guard case .ref(let ref) = parsed[1] else {
|
guard case .ref(let ref) = parsed[1] else {
|
||||||
XCTAssertTrue(false)
|
XCTAssertTrue(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user