Rewrite note parsing in C
This eliminates any parsing choppyness Fixes: #32 Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
5
damus-c/damus-Bridging-Header.h
Normal file
5
damus-c/damus-Bridging-Header.h
Normal file
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#include "damus.h"
|
||||
257
damus-c/damus.c
Normal file
257
damus-c/damus.c
Normal file
@@ -0,0 +1,257 @@
|
||||
//
|
||||
// damus.c
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2022-10-17.
|
||||
//
|
||||
|
||||
#include "damus.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef unsigned char u8;
|
||||
|
||||
struct cursor {
|
||||
const u8 *p;
|
||||
const u8 *start;
|
||||
const u8 *end;
|
||||
};
|
||||
|
||||
static inline int is_whitespace(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
|
||||
}
|
||||
|
||||
static void make_cursor(struct cursor *c, const u8 *content, size_t len)
|
||||
{
|
||||
c->start = content;
|
||||
c->end = content + len;
|
||||
c->p = content;
|
||||
}
|
||||
|
||||
static int consume_until_whitespace(struct cursor *cur, int or_end) {
|
||||
char c;
|
||||
|
||||
while (cur->p < cur->end) {
|
||||
c = *cur->p;
|
||||
|
||||
if (is_whitespace(c))
|
||||
return 1;
|
||||
|
||||
cur->p++;
|
||||
}
|
||||
|
||||
return or_end;
|
||||
}
|
||||
|
||||
static int parse_char(struct cursor *cur, char c) {
|
||||
if (cur->p >= cur->end)
|
||||
return 0;
|
||||
|
||||
if (*cur->p == c) {
|
||||
cur->p++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int peek_char(struct cursor *cur, int ind) {
|
||||
if ((cur->p + ind < cur->start) || (cur->p + ind >= cur->end))
|
||||
return -1;
|
||||
|
||||
return *(cur->p + ind);
|
||||
}
|
||||
|
||||
static int parse_digit(struct cursor *cur, int *digit) {
|
||||
int c;
|
||||
if ((c = peek_char(cur, 0)) == -1)
|
||||
return 0;
|
||||
|
||||
c -= '0';
|
||||
|
||||
if (c >= 0 && c <= 9) {
|
||||
*digit = c;
|
||||
cur->p++;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_str(struct cursor *cur, const char *str) {
|
||||
unsigned long len = strlen(str);
|
||||
|
||||
if (cur->p + len >= cur->end)
|
||||
return 0;
|
||||
|
||||
if (!memcmp(cur->p, str, len)) {
|
||||
cur->p += len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_mention(struct cursor *cur, struct block *block) {
|
||||
int d1, d2, d3, ind;
|
||||
const u8 *start = cur->p;
|
||||
|
||||
if (!parse_str(cur, "#["))
|
||||
return 0;
|
||||
|
||||
if (!parse_digit(cur, &d1)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ind = d1;
|
||||
|
||||
if (parse_digit(cur, &d2))
|
||||
ind = (d1 * 10) + d2;
|
||||
|
||||
if (parse_digit(cur, &d3))
|
||||
ind = (d1 * 100) + (d2 * 10) + d3;
|
||||
|
||||
if (!parse_char(cur, ']')) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
block->type = BLOCK_MENTION;
|
||||
block->block.mention = ind;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_hashtag(struct cursor *cur, struct block *block) {
|
||||
int c;
|
||||
const u8 *start = cur->p;
|
||||
|
||||
if (!parse_char(cur, '#'))
|
||||
return 0;
|
||||
|
||||
c = peek_char(cur, 0);
|
||||
if (c == -1 || is_whitespace(c) || c == '#') {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
consume_until_whitespace(cur, 1);
|
||||
|
||||
block->type = BLOCK_HASHTAG;
|
||||
block->block.str.start = (const char*)(start + 1);
|
||||
block->block.str.end = (const char*)cur->p;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int add_block(struct blocks *blocks, struct block block)
|
||||
{
|
||||
if (blocks->num_blocks + 1 >= MAX_BLOCKS)
|
||||
return 0;
|
||||
|
||||
blocks->blocks[blocks->num_blocks++] = block;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end)
|
||||
{
|
||||
struct block b;
|
||||
|
||||
b.type = BLOCK_TEXT;
|
||||
b.block.str.start = (const char*)start;
|
||||
b.block.str.end = (const char*)end;
|
||||
|
||||
return add_block(blocks, b);
|
||||
}
|
||||
|
||||
static int parse_url(struct cursor *cur, struct block *block) {
|
||||
const u8 *start = cur->p;
|
||||
|
||||
if (!parse_str(cur, "http"))
|
||||
return 0;
|
||||
|
||||
if (parse_char(cur, 's')) {
|
||||
if (!parse_str(cur, "://")) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (!parse_str(cur, "://")) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!consume_until_whitespace(cur, 1)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
block->type = BLOCK_URL;
|
||||
block->block.str.start = (const char *)start;
|
||||
block->block.str.end = (const char *)cur->p;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int damus_parse_content(struct blocks *blocks, const char *content) {
|
||||
int cp, c;
|
||||
struct cursor cur;
|
||||
struct block block;
|
||||
const u8 *start, *pre_mention;
|
||||
|
||||
blocks->num_blocks = 0;
|
||||
make_cursor(&cur, (const u8*)content, strlen(content));
|
||||
|
||||
start = cur.p;
|
||||
while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) {
|
||||
cp = peek_char(&cur, -1);
|
||||
c = peek_char(&cur, 0);
|
||||
|
||||
pre_mention = cur.p;
|
||||
if (cp == -1 || is_whitespace(cp)) {
|
||||
if (c == '#' && (parse_mention(&cur, &block) || parse_hashtag(&cur, &block))) {
|
||||
if (!add_text_block(blocks, start, pre_mention))
|
||||
return 0;
|
||||
|
||||
start = cur.p;
|
||||
|
||||
if (!add_block(blocks, block))
|
||||
return 0;
|
||||
|
||||
continue;
|
||||
} else if (c == 'h' && parse_url(&cur, &block)) {
|
||||
if (!add_text_block(blocks, start, pre_mention))
|
||||
return 0;
|
||||
|
||||
start = cur.p;
|
||||
|
||||
if (!add_block(blocks, block))
|
||||
return 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cur.p++;
|
||||
}
|
||||
|
||||
if (cur.p - start > 0) {
|
||||
if (!add_text_block(blocks, start, cur.p))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void blocks_init(struct blocks *blocks) {
|
||||
blocks->blocks = malloc(sizeof(struct block) * MAX_BLOCKS);
|
||||
blocks->num_blocks = 0;
|
||||
}
|
||||
|
||||
void blocks_free(struct blocks *blocks) {
|
||||
if (blocks->blocks) {
|
||||
free(blocks->blocks);
|
||||
blocks->num_blocks = 0;
|
||||
}
|
||||
}
|
||||
44
damus-c/damus.h
Normal file
44
damus-c/damus.h
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// damus.h
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2022-10-17.
|
||||
//
|
||||
|
||||
#ifndef damus_h
|
||||
#define damus_h
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define MAX_BLOCKS 1024
|
||||
|
||||
enum block_type {
|
||||
BLOCK_HASHTAG = 1,
|
||||
BLOCK_TEXT = 2,
|
||||
BLOCK_MENTION = 3,
|
||||
BLOCK_URL = 4,
|
||||
};
|
||||
|
||||
typedef struct str_block {
|
||||
const char *start;
|
||||
const char *end;
|
||||
} str_block_t;
|
||||
|
||||
typedef struct block {
|
||||
enum block_type type;
|
||||
union {
|
||||
struct str_block str;
|
||||
int mention;
|
||||
} block;
|
||||
} block_t;
|
||||
|
||||
typedef struct blocks {
|
||||
int num_blocks;
|
||||
struct block *blocks;
|
||||
} blocks_t;
|
||||
|
||||
void blocks_init(struct blocks *blocks);
|
||||
void blocks_free(struct blocks *blocks);
|
||||
int damus_parse_content(struct blocks *blocks, const char *content);
|
||||
|
||||
#endif /* damus_h */
|
||||
180
damus-c/utf8.c
Normal file
180
damus-c/utf8.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/* MIT (BSD) license - see LICENSE file for details - taken from ccan. thanks rusty! */
|
||||
|
||||
#include "utf8.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* I loved this table, so I stole it: */
|
||||
/*
|
||||
* Copyright (c) 2017 Christian Hansen <chansen@cpan.org>
|
||||
* <https://github.com/chansen/c-utf8-valid>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* UTF-8 Encoding Form
|
||||
*
|
||||
* U+0000..U+007F 0xxxxxxx <= 7 bits
|
||||
* U+0080..U+07FF 110xxxxx 10xxxxxx <= 11 bits
|
||||
* U+0800..U+FFFF 1110xxxx 10xxxxxx 10xxxxxx <= 16 bits
|
||||
* U+10000..U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx <= 21 bits
|
||||
*
|
||||
*
|
||||
* U+0000..U+007F 00..7F
|
||||
* N C0..C1 80..BF 1100000x 10xxxxxx
|
||||
* U+0080..U+07FF C2..DF 80..BF
|
||||
* N E0 80..9F 80..BF 11100000 100xxxxx
|
||||
* U+0800..U+0FFF E0 A0..BF 80..BF
|
||||
* U+1000..U+CFFF E1..EC 80..BF 80..BF
|
||||
* U+D000..U+D7FF ED 80..9F 80..BF
|
||||
* S ED A0..BF 80..BF 11101101 101xxxxx
|
||||
* U+E000..U+FFFF EE..EF 80..BF 80..BF
|
||||
* N F0 80..8F 80..BF 80..BF 11110000 1000xxxx
|
||||
* U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||
* U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
|
||||
* U+100000..U+10FFFF F4 80..8F 80..BF 80..BF 11110100 1000xxxx
|
||||
*
|
||||
* Legend:
|
||||
* N = Non-shortest form
|
||||
* S = Surrogates
|
||||
*/
|
||||
bool utf8_decode(struct utf8_state *utf8_state, char c)
|
||||
{
|
||||
if (utf8_state->used_len == utf8_state->total_len) {
|
||||
utf8_state->used_len = 1;
|
||||
/* First character in sequence. */
|
||||
if (((unsigned char)c & 0x80) == 0) {
|
||||
/* ASCII, easy. */
|
||||
if (c == 0)
|
||||
goto bad_encoding;
|
||||
utf8_state->total_len = 1;
|
||||
utf8_state->c = c;
|
||||
goto finished_decoding;
|
||||
} else if (((unsigned char)c & 0xE0) == 0xC0) {
|
||||
utf8_state->total_len = 2;
|
||||
utf8_state->c = ((unsigned char)c & 0x1F);
|
||||
return false;
|
||||
} else if (((unsigned char)c & 0xF0) == 0xE0) {
|
||||
utf8_state->total_len = 3;
|
||||
utf8_state->c = ((unsigned char)c & 0x0F);
|
||||
return false;
|
||||
} else if (((unsigned char)c & 0xF8) == 0xF0) {
|
||||
utf8_state->total_len = 4;
|
||||
utf8_state->c = ((unsigned char)c & 0x07);
|
||||
return false;
|
||||
}
|
||||
goto bad_encoding;
|
||||
}
|
||||
|
||||
if (((unsigned char)c & 0xC0) != 0x80)
|
||||
goto bad_encoding;
|
||||
|
||||
utf8_state->c <<= 6;
|
||||
utf8_state->c |= ((unsigned char)c & 0x3F);
|
||||
|
||||
utf8_state->used_len++;
|
||||
if (utf8_state->used_len == utf8_state->total_len)
|
||||
goto finished_decoding;
|
||||
return false;
|
||||
|
||||
finished_decoding:
|
||||
if (utf8_state->c == 0 || utf8_state->c > 0x10FFFF)
|
||||
errno = ERANGE;
|
||||
/* The UTF-16 "surrogate range": illegal in UTF-8 */
|
||||
else if (utf8_state->total_len == 3
|
||||
&& (utf8_state->c & 0xFFFFF800) == 0x0000D800)
|
||||
errno = ERANGE;
|
||||
else {
|
||||
int min_bits;
|
||||
switch (utf8_state->total_len) {
|
||||
case 1:
|
||||
min_bits = 0;
|
||||
break;
|
||||
case 2:
|
||||
min_bits = 7;
|
||||
break;
|
||||
case 3:
|
||||
min_bits = 11;
|
||||
break;
|
||||
case 4:
|
||||
min_bits = 16;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
if ((utf8_state->c >> min_bits) == 0)
|
||||
errno = EFBIG;
|
||||
else
|
||||
errno = 0;
|
||||
}
|
||||
return true;
|
||||
|
||||
bad_encoding:
|
||||
utf8_state->total_len = utf8_state->used_len;
|
||||
errno = EINVAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t utf8_encode(uint32_t point, char dest[UTF8_MAX_LEN])
|
||||
{
|
||||
if ((point >> 7) == 0) {
|
||||
if (point == 0) {
|
||||
errno = ERANGE;
|
||||
return 0;
|
||||
}
|
||||
/* 0xxxxxxx */
|
||||
dest[0] = point;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((point >> 11) == 0) {
|
||||
/* 110xxxxx 10xxxxxx */
|
||||
dest[1] = 0x80 | (point & 0x3F);
|
||||
dest[0] = 0xC0 | (point >> 6);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if ((point >> 16) == 0) {
|
||||
if (point >= 0xD800 && point <= 0xDFFF) {
|
||||
errno = ERANGE;
|
||||
return 0;
|
||||
}
|
||||
/* 1110xxxx 10xxxxxx 10xxxxxx */
|
||||
dest[2] = 0x80 | (point & 0x3F);
|
||||
dest[1] = 0x80 | ((point >> 6) & 0x3F);
|
||||
dest[0] = 0xE0 | (point >> 12);
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (point > 0x10FFFF) {
|
||||
errno = ERANGE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
|
||||
dest[3] = 0x80 | (point & 0x3F);
|
||||
dest[2] = 0x80 | ((point >> 6) & 0x3F);
|
||||
dest[1] = 0x80 | ((point >> 12) & 0x3F);
|
||||
dest[0] = 0xF0 | (point >> 18);
|
||||
return 4;
|
||||
}
|
||||
|
||||
54
damus-c/utf8.h
Normal file
54
damus-c/utf8.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/* MIT (BSD) license - see LICENSE file for details */
|
||||
#ifndef CCAN_UTF8_H
|
||||
#define CCAN_UTF8_H
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Unicode is limited to 21 bits. */
|
||||
#define UTF8_MAX_LEN 4
|
||||
|
||||
struct utf8_state {
|
||||
/* How many characters we are expecting as part of this Unicode point */
|
||||
uint16_t total_len;
|
||||
/* How many characters we've already seen. */
|
||||
uint16_t used_len;
|
||||
/* Compound character, aka Unicode point. */
|
||||
uint32_t c;
|
||||
};
|
||||
|
||||
#define UTF8_STATE_INIT { 0, 0, 0 }
|
||||
|
||||
static inline void utf8_state_init(struct utf8_state *utf8_state)
|
||||
{
|
||||
memset(utf8_state, 0, sizeof(*utf8_state));
|
||||
}
|
||||
|
||||
/**
|
||||
* utf8_decode - continue UTF8 decoding with this character.
|
||||
* @utf8_state - initialized UTF8 state.
|
||||
* @c - the character.
|
||||
*
|
||||
* Returns false if it needs another character to give results.
|
||||
* Otherwise returns true, @utf8_state can be reused without initializeation,
|
||||
* and sets errno:
|
||||
* 0: success
|
||||
* EINVAL: bad encoding (including a NUL character).
|
||||
* EFBIG: not a minimal encoding.
|
||||
* ERANGE: encoding of invalid character.
|
||||
*
|
||||
* You can extract the character from @utf8_state->c; @utf8_state->used_len
|
||||
* indicates how many characters have been consumed.
|
||||
*/
|
||||
bool utf8_decode(struct utf8_state *utf8_state, char c);
|
||||
|
||||
/**
|
||||
* utf8_encode - encode a point into UTF8.
|
||||
* @point - Unicode point to include.
|
||||
* @dest - buffer to fill.
|
||||
*
|
||||
* Returns 0 if point was invalid, otherwise bytes of dest used.
|
||||
* Sets errno to ERANGE if point was invalid.
|
||||
*/
|
||||
size_t utf8_encode(uint32_t point, char dest[UTF8_MAX_LEN]);
|
||||
#endif /* CCAN_UTF8_H */
|
||||
@@ -10,6 +10,8 @@
|
||||
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
|
||||
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
|
||||
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
|
||||
4C06670B28FDE64700038D2A /* damus.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670A28FDE64700038D2A /* damus.c */; };
|
||||
4C06670E28FDEAA000038D2A /* utf8.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670D28FDEAA000038D2A /* utf8.c */; };
|
||||
4C0A3F8C280F5FCA000448DE /* ChatroomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F8B280F5FCA000448DE /* ChatroomView.swift */; };
|
||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F8E280F640A000448DE /* ThreadModel.swift */; };
|
||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F90280F6528000448DE /* ChatView.swift */; };
|
||||
@@ -130,6 +132,11 @@
|
||||
/* Begin PBXFileReference section */
|
||||
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
|
||||
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
|
||||
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
4C06670928FDE64700038D2A /* damus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = damus.h; sourceTree = "<group>"; };
|
||||
4C06670A28FDE64700038D2A /* damus.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = damus.c; sourceTree = "<group>"; };
|
||||
4C06670C28FDEAA000038D2A /* utf8.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = utf8.h; sourceTree = "<group>"; };
|
||||
4C06670D28FDEAA000038D2A /* utf8.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = utf8.c; sourceTree = "<group>"; };
|
||||
4C0A3F8B280F5FCA000448DE /* ChatroomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatroomView.swift; sourceTree = "<group>"; };
|
||||
4C0A3F8E280F640A000448DE /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = "<group>"; };
|
||||
4C0A3F90280F6528000448DE /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
||||
@@ -262,6 +269,18 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
4C06670728FDE62900038D2A /* damus-c */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C06670928FDE64700038D2A /* damus.h */,
|
||||
4C06670A28FDE64700038D2A /* damus.c */,
|
||||
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */,
|
||||
4C06670C28FDEAA000038D2A /* utf8.h */,
|
||||
4C06670D28FDEAA000038D2A /* utf8.c */,
|
||||
);
|
||||
path = "damus-c";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C0A3F8D280F63FF000448DE /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -388,6 +407,7 @@
|
||||
4CE6DEDA27F7A08100C66700 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C06670728FDE62900038D2A /* damus-c */,
|
||||
4CE6DEE527F7A08100C66700 /* damus */,
|
||||
4CE6DEF627F7A08200C66700 /* damusTests */,
|
||||
4CE6DF0027F7A08200C66700 /* damusUITests */,
|
||||
@@ -533,6 +553,7 @@
|
||||
TargetAttributes = {
|
||||
4CE6DEE227F7A08100C66700 = {
|
||||
CreatedOnToolsVersion = 13.3;
|
||||
LastSwiftMigration = 1400;
|
||||
};
|
||||
4CE6DEF227F7A08200C66700 = {
|
||||
CreatedOnToolsVersion = 13.3;
|
||||
@@ -674,6 +695,7 @@
|
||||
4C363A962827096D006E126D /* PostBlock.swift in Sources */,
|
||||
4C5F9116283D855D0052CD1C /* EventsModel.swift in Sources */,
|
||||
4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */,
|
||||
4C06670E28FDEAA000038D2A /* utf8.c in Sources */,
|
||||
4C3AC7A728369BA200E1F516 /* SearchHomeView.swift in Sources */,
|
||||
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
|
||||
4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */,
|
||||
@@ -684,6 +706,7 @@
|
||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
|
||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
||||
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
|
||||
4C06670B28FDE64700038D2A /* damus.c in Sources */,
|
||||
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */,
|
||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||
@@ -850,6 +873,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
@@ -876,6 +900,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
@@ -886,6 +912,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
@@ -912,6 +939,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
enum MentionType {
|
||||
case pubkey
|
||||
case event
|
||||
@@ -89,6 +88,87 @@ func parse_textblock(str: String, from: Int, to: Int) -> Block {
|
||||
}
|
||||
|
||||
func parse_mentions(content: String, tags: [[String]]) -> [Block] {
|
||||
var out: [Block] = []
|
||||
|
||||
var bs = blocks()
|
||||
bs.num_blocks = 0;
|
||||
|
||||
blocks_init(&bs)
|
||||
|
||||
let bytes = content.utf8CString
|
||||
bytes.withUnsafeBufferPointer { p in
|
||||
damus_parse_content(&bs, p.baseAddress)
|
||||
}
|
||||
|
||||
var i = 0
|
||||
while (i < bs.num_blocks) {
|
||||
let block = bs.blocks[i]
|
||||
|
||||
if let converted = convert_block(block, tags: tags) {
|
||||
out.append(converted)
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
blocks_free(&bs)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func strblock_to_string(_ s: str_block_t) -> String? {
|
||||
let len = s.end - s.start
|
||||
let bytes = Data(bytes: s.start, count: len)
|
||||
return String(bytes: bytes, encoding: .utf8)
|
||||
}
|
||||
|
||||
func convert_block(_ b: block_t, tags: [[String]]) -> Block? {
|
||||
if b.type == BLOCK_HASHTAG {
|
||||
guard let str = strblock_to_string(b.block.str) else {
|
||||
return nil
|
||||
}
|
||||
return .hashtag(str)
|
||||
} else if b.type == BLOCK_TEXT {
|
||||
guard let str = strblock_to_string(b.block.str) else {
|
||||
return nil
|
||||
}
|
||||
return .text(str)
|
||||
} else if b.type == BLOCK_MENTION {
|
||||
return convert_mention_block(ind: b.block.mention, tags: tags)
|
||||
} else if b.type == BLOCK_URL {
|
||||
guard let str = strblock_to_string(b.block.str) else {
|
||||
return nil
|
||||
}
|
||||
guard let url = URL(string: str) else {
|
||||
return .text(str)
|
||||
}
|
||||
return .url(url)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_mention_block(ind: Int32, tags: [[String]]) -> Block?
|
||||
{
|
||||
let ind = Int(ind)
|
||||
|
||||
if ind < 0 || (ind + 1 > tags.count) || tags[ind].count < 2 {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
let tag = tags[ind]
|
||||
guard let mention_type = parse_mention_type(tag[0]) else {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
guard let ref = tag_to_refid(tag) else {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
return .mention(Mention(index: ind, type: mention_type, ref: ref))
|
||||
}
|
||||
|
||||
func parse_mentions_old(content: String, tags: [[String]]) -> [Block] {
|
||||
let p = Parser(pos: 0, str: content)
|
||||
var blocks: [Block] = []
|
||||
var starting_from: Int = 0
|
||||
|
||||
@@ -87,7 +87,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
|
||||
}
|
||||
|
||||
lazy var validity: ValidationResult = {
|
||||
return validate_event(ev: self)
|
||||
return .ok //validate_event(ev: self)
|
||||
}()
|
||||
|
||||
private var _blocks: [Block]? = nil
|
||||
|
||||
@@ -32,7 +32,7 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
|
||||
|
||||
func is_image_url(_ url: URL) -> Bool {
|
||||
let str = url.lastPathComponent
|
||||
return str.hasSuffix("png") || str.hasSuffix("jpg") || str.hasSuffix("jpeg")
|
||||
return str.hasSuffix("png") || str.hasSuffix("jpg") || str.hasSuffix("jpeg") || str.hasSuffix("gif")
|
||||
}
|
||||
|
||||
struct NoteContentView: View {
|
||||
|
||||
@@ -189,9 +189,9 @@ class ReplyTests: XCTestCase {
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text!, "this is ")
|
||||
XCTAssertEqual(parsed[0].is_text, "this is ")
|
||||
XCTAssertNotNil(parsed[1].is_mention)
|
||||
XCTAssertEqual(parsed[2].is_text!, " a mention")
|
||||
XCTAssertEqual(parsed[2].is_text, " a mention")
|
||||
}
|
||||
|
||||
func testEmptyPostReference() throws {
|
||||
@@ -515,8 +515,10 @@ class ReplyTests: XCTestCase {
|
||||
let parsed = parse_mentions(content: "this is #[0] a mention", tags: [])
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 1)
|
||||
XCTAssertEqual(parsed[0].is_text!, "this is #[0] a mention")
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "this is ")
|
||||
XCTAssertEqual(parsed[1].is_text, "#[0]")
|
||||
XCTAssertEqual(parsed[2].is_text, " a mention")
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user