refactor: improve memory footprint of zipline-parser code

I miss the ART GC already...
This commit is contained in:
Harsh Shandilya
2026-06-08 17:12:56 +05:30
parent 3658aafd61
commit 344da06591
4 changed files with 41 additions and 50 deletions
@@ -31,11 +31,13 @@ private fun MutableList<Comment>.addSubtree(
val comment = commentElement.toComment(parentComment)
add(comment)
val childContainer = if (subtree.`is`("div.comment")) subtree.parent() ?: subtree else subtree
childContainer
.children()
.filter { it.`is`("ol.comments") }
.flatMap { comments -> comments.children().filter { it.`is`("li.comments_subtree") } }
.forEach { child -> addSubtree(child, parentComment = comment.shortId, seen) }
for (childList in childContainer.children()) {
if (!childList.`is`("ol.comments")) continue
for (child in childList.children()) {
if (!child.`is`("li.comments_subtree")) continue
addSubtree(child, parentComment = comment.shortId, seen)
}
}
}
private fun Element.toComment(parentComment: String?): Comment {
@@ -10,40 +10,30 @@ import com.fleeksoft.ksoup.Ksoup
import dev.msfjarvis.claw.model.LobstersPostDetails
private const val BASE_URL = "https://lobste.rs"
private val commentCountRegex = "\\d+".toRegex()
private const val STORY_SELECTOR = "ol.stories > li.story"
private const val SUBMITTER_SELECTOR =
"ol.stories > li.story div.byline > a[href^=/~]:not([tabindex]):not([aria-hidden=true])"
internal fun parsePostDetails(html: String): LobstersPostDetails {
val document = Ksoup.parse(html, baseUri = BASE_URL)
val storyElement = document.select(STORY_SELECTOR)
val timestampElement = document.select("$STORY_SELECTOR div.byline > time")
val titleElement = document.select("$STORY_SELECTOR span.link.h-cite > a")
val commentsElement = document.select("$STORY_SELECTOR span.comments_label a")
val submitterElement = document.select(SUBMITTER_SELECTOR)
val tags = document.select("$STORY_SELECTOR span.tags > a").map { it.text() }
return LobstersPostDetails(
shortId = document.select("ol.stories > li.story").attr("data-shortid"),
createdAt =
normalizeCreatedAt(
document.select("ol.stories > li.story div.byline > time").attr("data-at-unix")
),
title = document.select("ol.stories > li.story span.link.h-cite > a").text(),
url = document.select("ol.stories > li.story span.link.h-cite > a").attr("abs:href"),
shortId = storyElement.attr("data-shortid"),
createdAt = normalizeCreatedAt(timestampElement.attr("data-at-unix")),
title = titleElement.text(),
url = titleElement.attr("abs:href"),
description = document.select("div.story_content div.story_text").html(),
commentCount =
"\\d+"
.toRegex()
.find(document.select("ol.stories > li.story span.comments_label a").text())
?.value
?.toInt() ?: 0,
commentsUrl = document.select("ol.stories > li.story span.comments_label a").attr("abs:href"),
submitter =
document
.select(
"ol.stories > li.story div.byline > a[href^=/~]:not([tabindex]):not([aria-hidden=true])"
)
.text(),
tags = document.select("ol.stories > li.story span.tags > a").map { it.text() },
commentCount = commentCountRegex.find(commentsElement.text())?.value?.toInt() ?: 0,
commentsUrl = commentsElement.attr("abs:href"),
submitter = submitterElement.text(),
tags = tags,
comments = parseComments(document),
userIsAuthor =
document
.select(
"ol.stories > li.story div.byline > a[href^=/~]:not([tabindex]):not([aria-hidden=true])"
)
.attr("class")
.split(' ')
.contains("user_is_author"),
userIsAuthor = submitterElement.attr("class").split(' ').contains("user_is_author"),
)
}
@@ -11,6 +11,7 @@ import com.fleeksoft.ksoup.nodes.Element
import dev.msfjarvis.claw.model.LobstersPost
private const val BASE_URL = "https://lobste.rs"
private val commentCountRegex = "\\d+".toRegex()
internal fun parsePostsPage(html: String): List<LobstersPost> {
return Ksoup.parse(html, baseUri = BASE_URL).select("li.story").map(::parsePost)
@@ -24,19 +25,20 @@ private fun parsePost(element: Element): LobstersPost {
"> div.story_liner div.byline > a[href^=/~]:not([tabindex]):not([aria-hidden=true])"
)
val timestampElement = element.select("> div.story_liner div.byline > time")
val descriptionElement = element.select("> div.story_liner a.description_present")
val tags = element.select("> div.story_liner span.tags > a").map(Element::text)
return LobstersPost(
shortId = element.attr("data-shortid"),
createdAt =
normalizeCreatedAt(
element.select("> div.story_liner div.byline > time").attr("data-at-unix")
),
createdAt = normalizeCreatedAt(timestampElement.attr("data-at-unix")),
title = titleElement.text(),
url = titleElement.attr("abs:href"),
description = element.select("> div.story_liner a.description_present").attr("title"),
commentCount = "\\d+".toRegex().find(commentElement.text())?.value?.toInt() ?: 0,
description = descriptionElement.attr("title"),
commentCount = commentCountRegex.find(commentElement.text())?.value?.toInt() ?: 0,
commentsUrl = commentElement.attr("abs:href"),
submitter = submitterElement.text(),
userIsAuthor = submitterElement.attr("class").split(' ').contains("user_is_author"),
tags = element.select("> div.story_liner span.tags > a").map(Element::text),
tags = tags,
)
}
@@ -17,18 +17,15 @@ internal fun parseTagsPage(html: String): List<Tag> {
}
private fun parseTag(element: Element): Tag {
val tagElement = element.select("> a.tag")
val descriptionElement = element.select("> span:not(.byline)")
return Tag(
tag = element.select("> a.tag").text(),
description = element.select("> span:not(.byline)").text(),
tag = tagElement.text(),
description = descriptionElement.text(),
privileged = element.attr("data-privileged").toBoolean(),
active =
!element
.select("> span:not(.byline)")
.attr("class")
.split(Regex("\\s+"))
.contains("inactive_tag"),
active = !descriptionElement.hasClass("inactive_tag"),
category = element.attr("data-category"),
isMedia = element.select("> a.tag").attr("class").split(Regex("\\s+")).contains("tag_is_media"),
isMedia = tagElement.hasClass("tag_is_media"),
hotnessMod = element.attr("data-hotness-mod").toDoubleOrNull() ?: 0.0,
)
}