Commit fe122d6f authored by Lysander Trischler's avatar Lysander Trischler

Support twt links and fix memory management

parent 3a442bfa
......@@ -3,6 +3,7 @@ import datetime
_so = ctypes.CDLL('./libtwtxt.so')
class Twter:
def __init__(self, nick=None, url=None, avatar=None, tagline=None):
self.nick = nick
......@@ -22,6 +23,7 @@ class Twter:
twter.tagline = self.tagline.encode("utf-8")
return ctypes.pointer(twter)
class _Twter(ctypes.Structure):
_fields_ = [("nick", ctypes.c_char_p),
("url", ctypes.c_char_p),
......@@ -41,16 +43,39 @@ class _Twter(ctypes.Structure):
return twter
class Link:
def __init__(self, text=None, target=None):
self.text = text
self.target = target
class _Link(ctypes.Structure):
_fields_ = [("text", ctypes.c_char_p),
("target", ctypes.c_char_p)]
def to_python(self):
link = Link()
if self.text is not None:
link.text = self.text.decode("utf-8")
if self.target is not None:
link.target = self.target.decode("utf-8")
return link
class Twt:
def __init__(self, twter=None, created=None, hash=None):
def __init__(self, twter=None, created=None, hash=None, links=None):
self.twter = twter
self.created = created
self.hash = hash
self.links = links
class _Twt(ctypes.Structure):
_fields_ = [("twter", ctypes.POINTER(_Twter)),
("created", ctypes.c_char_p),
("hash", ctypes.c_char_p)]
("hash", ctypes.c_char_p),
("links", ctypes.POINTER(ctypes.POINTER(_Link))),
("links_len", ctypes.c_int)]
def to_python(self):
twt = Twt()
......@@ -60,8 +85,14 @@ class _Twt(ctypes.Structure):
twt.created = datetime.datetime.fromisoformat(self.created.decode("utf-8"))
if self.hash is not None:
twt.hash = self.hash.decode("utf-8")
twt.links = []
for i in range(self.links_len):
twt.links.append(self.links[i].contents.to_python())
return twt
class TwtFile:
def __init__(self, twter=None, twts=None):
self.twter = twter
......
......@@ -26,7 +26,8 @@ struct twt {
char *hash;
//struct subject *subject;
//struct twter *mentions[];
//struct link *links[];
struct link *links;
int links_len;
//struct link *tags[];
};
......@@ -91,29 +92,11 @@ func parse_file(input *C.char, twter *C.struct_twter) (*C.struct_twt_file, *C.ch
return twtfile, nil
}
//export free_string
func free_string(s *C.char) {
if s != nil {
C.free(unsafe.Pointer(s))
}
}
func freeTwter(t *C.struct_twter) {
if t != nil {
free_string(t.nick)
free_string(t.url)
free_string(t.avatar)
free_string(t.tagline)
}
}
//export free_twt_file
func free_twt_file(t *C.struct_twt_file) {
if t != nil {
freeTwter(t.twter)
if t.twts != nil {
C.free(unsafe.Pointer(t.twts))
}
freeTwts(t.twts, t.twts_len)
C.free(unsafe.Pointer(t))
}
}
......@@ -128,13 +111,85 @@ func convertTwter(t types.Twter) *C.struct_twter {
return twter
}
func freeTwter(t *C.struct_twter) {
if t != nil {
free_string(t.nick)
free_string(t.url)
free_string(t.avatar)
free_string(t.tagline)
C.free(unsafe.Pointer(t))
}
}
func convertTwt(t types.Twt) *C.struct_twt {
ptr := C.malloc(C.sizeof_struct_twt)
twt := (*C.struct_twt)(ptr)
twt.twter = convertTwter(t.Twter())
twt.created = C.CString(t.Created().Format(time.RFC3339))
twt.hash = C.CString(t.Hash())
linksPtr := C.malloc(C.size_t(len(t.Links())) * C.size_t(unsafe.Sizeof(uintptr(0))))
links := (*[1<<30 - 1]*C.struct_link)(linksPtr)
for i, link := range t.Links() {
links[i] = convertLink(link)
}
twt.links = (*C.struct_link)(linksPtr)
twt.links_len = C.int(len(t.Links()))
return twt
}
func freeTwt(t *C.struct_twt) {
if t != nil {
freeTwter(t.twter)
free_string(t.created)
free_string(t.hash)
freeLinks(t.links, t.links_len)
C.free(unsafe.Pointer(t))
}
}
func freeTwts(t *C.struct_twt, length C.int) {
if t != nil {
twts := (*[1<<30 - 1]*C.struct_twt)(unsafe.Pointer(t))
for i := 0; i < int(length); i++ {
freeTwt(twts[i])
}
C.free(unsafe.Pointer(t))
}
}
func convertLink(l types.TwtLink) *C.struct_link {
ptr := C.malloc(C.sizeof_struct_link)
link := (*C.struct_link)(ptr)
link.text = C.CString(l.Text())
link.target = C.CString(l.Target())
return link
}
func freeLink(l *C.struct_link) {
if l != nil {
free_string(l.text)
free_string(l.target)
C.free(unsafe.Pointer(l))
}
}
func freeLinks(l *C.struct_link, length C.int) {
if l != nil {
links := (*[1<<30 - 1]*C.struct_link)(unsafe.Pointer(l))
for i := 0; i < int(length); i++ {
freeLink(links[i])
}
C.free(unsafe.Pointer(l))
}
}
//export free_string
func free_string(s *C.char) {
if s != nil {
C.free(unsafe.Pointer(s))
}
}
func main() {}
#!/usr/bin/python3
import datetime
import libgotwtxt
import unittest
from libgotwtxt import Link, parse_file, Twt, Twter
UTC_PLUS_2 = datetime.timezone(datetime.timedelta(hours=2))
TWTER = Twter(nick="hugo",
url="https://example.com/~hugo/twtxt.txt",
avatar="https://example.com/~hugo/avatar.png",
tagline="Bla")
class ParseFileTest(unittest.TestCase):
def test_single_line(self):
twter = libgotwtxt.Twter(nick="hugo",
url="https://example.com/~hugo/twtxt.txt",
avatar="https://example.com/~hugo/avatar.png",
tagline="Bla")
twtfile = libgotwtxt.parse_file("2021-08-02T10:27:42+02:00\tHello world.", twter)
twtfile = parse_file("2021-08-02T10:27:42+02:00\tHello world.", TWTER)
self.assertIsNotNone(twtfile, "parse_file returned None")
self.assertTwterEqual(twter, twtfile.twter, "twter of twt file does not match")
self.assertTwterEqual(TWTER, twtfile.twter, "twter of twt file does not match")
self.assertEqual(1, len(twtfile.twts), "number of twts does not match")
self.assertTwtEqual(libgotwtxt.Twt(twter=twter,
created=datetime.datetime(2021, 8, 2, 10, 27, 42, tzinfo=UTC_PLUS_2),
hash="slrnx6a"),
self.assertTwtEqual(Twt(twter=TWTER,
created=datetime.datetime(2021, 8, 2, 10, 27, 42, tzinfo=UTC_PLUS_2),
hash="slrnx6a", links=[]),
twtfile.twts[0])
def test_two_lines(self):
twter = libgotwtxt.Twter(nick="hugo",
url="https://example.com/~hugo/twtxt.txt",
avatar="https://example.com/~hugo/avatar.png",
tagline="Bla")
twtfile = libgotwtxt.parse_file(
twtfile = parse_file(
"2021-08-02T10:27:42+02:00\tHello world.\n"
"2021-08-03T09:28:45+02:00\tFoo bar eggs and spam.\n",
twter)
TWTER)
self.assertIsNotNone(twtfile, "parse_file returned None")
self.assertTwterEqual(twter, twtfile.twter, "twter of twt file does not match")
self.assertTwterEqual(TWTER, twtfile.twter, "twter of twt file does not match")
self.assertEqual(2, len(twtfile.twts), "number of twts does not match")
self.assertTwtEqual(libgotwtxt.Twt(twter=twter,
created=datetime.datetime(2021, 8, 2, 10, 27, 42, tzinfo=UTC_PLUS_2),
hash="slrnx6a"),
self.assertTwtEqual(Twt(twter=TWTER,
created=datetime.datetime(2021, 8, 2, 10, 27, 42, tzinfo=UTC_PLUS_2),
hash="slrnx6a", links=[]),
twtfile.twts[0], "first twt does not match")
self.assertTwtEqual(libgotwtxt.Twt(twter=twter,
created=datetime.datetime(2021, 8, 3, 9, 28, 45, tzinfo=UTC_PLUS_2),
hash="zm7fnka"),
self.assertTwtEqual(Twt(twter=TWTER,
created=datetime.datetime(2021, 8, 3, 9, 28, 45, tzinfo=UTC_PLUS_2),
hash="zm7fnka", links=[]),
twtfile.twts[1], "second twt does not match")
def test_markdown_links(self):
twtfile = parse_file(
"2021-08-03T11:16:13+02:00\tHello [wonderful](https://example.com/) "
"and [nice](https://example.com/test) world!",
TWTER)
self.assertIsNotNone(twtfile, "parse_file returned None")
self.assertTwterEqual(TWTER, twtfile.twter, "twter of twt file does not match")
self.assertEqual(1, len(twtfile.twts), "number of twts does not match")
self.assertTwtEqual(Twt(twter=TWTER,
created=datetime.datetime(2021, 8, 3, 11, 16, 13, tzinfo=UTC_PLUS_2),
hash="zv6vujq",
links=[Link(text="wonderful", target="https://example.com/"),
Link(text="nice", target="https://example.com/test")]),
twtfile.twts[0])
def msg(self, message_prefix):
"""Construct a message factory using the prefix, if present."""
def inner(message):
......@@ -67,6 +78,20 @@ class ParseFileTest(unittest.TestCase):
self.assertTwterEqual(expected.twter, actual.twter, msg("twter of twt does not match"))
self.assertEqual(expected.created, actual.created, msg("twt creation timestamp does not match"))
self.assertEqual(expected.hash, actual.hash, msg("twt hash does not match"))
if expected.links is None:
self.assertIsNone(actual.links, "twt links do not match")
else:
self.assertIsNotNone(actual.links, "twt links do not match")
self.assertEqual(len(expected.links), len(actual.links), "number of twt links does not match")
for i in range(len(expected.links)):
self.assertLinkEqual(expected.links[i], actual.links[i], "twt link at index %d does not match" % i)
def assertLinkEqual(self, expected, actual, msg=None):
msg = self.msg(msg)
self.assertIsNotNone(actual, msg("link is None"))
self.assertEqual(expected.text, actual.text, msg("link text does not match"))
self.assertEqual(expected.target, actual.target, msg("link target does not match"))
if __name__ == "__main__":
unittest.main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment