--- /dev/null
+package info.mechyrdia
+
+fun Iterable<String>.concat(delimiter: String = "", prefix: String = "", suffix: String = "") = joinToString(separator = delimiter, prefix = prefix, postfix = suffix)
+
+fun <T> Iterable<T>.concat(delimiter: String = "", prefix: String = "", suffix: String = "", converter: (T) -> String = Any?::toString) = joinToString(separator = delimiter, prefix = prefix, postfix = suffix, transform = converter)
import com.mongodb.client.model.UpdateOneModel
import com.mongodb.client.model.UpdateOptions
import com.mongodb.client.model.Updates
+import info.mechyrdia.concat
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import kotlinx.serialization.SerialName
}
suspend fun getCommentsIn(page: List<String>): Flow<Comment> {
- return Table.select(Filters.eq(Comment::submittedIn.serialName, page.joinToString(separator = "/")), Sorts.descending(Comment::submittedAt.serialName))
+ return Table.select(Filters.eq(Comment::submittedIn.serialName, page.concat("/")), Sorts.descending(Comment::submittedAt.serialName))
}
suspend fun getCommentsBy(user: Id<NationData>): Flow<Comment> {
import com.mongodb.reactivestreams.client.gridfs.GridFSBucket
import info.mechyrdia.Configuration
import info.mechyrdia.FileStorageConfig
+import info.mechyrdia.concat
import info.mechyrdia.lore.StoragePathAttributeKey
import info.mechyrdia.lore.forEachSuspend
import io.ktor.http.ContentType
init {
for ((i, element) in elements.withIndex())
require(element.any { it != '.' }) {
- "Cannot have elements . or .. in path, got $element at index $i in path /${elements.joinToString(separator = "/")}"
+ elements.concat("/", prefix = "Cannot have elements . or .. in path, got $element at index $i in path /")
}
}
}.all { it }
override fun toString(): String {
- return elements.joinToString(separator = "/")
+ return elements.concat("/")
}
companion object {
) : DataDocument<GridFsEntry>
private class GridFsStorage(val table: DocumentTable<GridFsEntry>, val bucket: GridFSBucket) : FileStorage {
- private fun toExactPath(path: StoragePath) = path.elements.joinToString(separator = "") { "/$it" }
- private fun toPrefixPath(path: StoragePath) = "${toExactPath(path)}/"
+ private fun toExactPath(path: StoragePath) = path.elements.concat("/", prefix = "/")
+ private fun toPrefixPath(path: StoragePath) = path.elements.concat("/", prefix = "/", suffix = "/")
private suspend fun testExact(path: StoragePath) = table.number(Filters.eq(GridFsEntry::path.serialName, toExactPath(path))) > 0L
private suspend fun getExact(path: StoragePath) = table.locate(Filters.eq(GridFsEntry::path.serialName, toExactPath(path)))
import com.mongodb.client.model.Sorts
import info.mechyrdia.OwnerNationId
import info.mechyrdia.auth.ForbiddenException
+import info.mechyrdia.concat
import info.mechyrdia.lore.ParserTree
import info.mechyrdia.lore.PokhwalishAlphabetFont
import info.mechyrdia.lore.TylanAlphabetFont
val now = Instant.now()
val comment = Comment(
submittedBy = loggedInAs.id,
- submittedIn = pagePathParts.joinToString("/"),
+ submittedIn = pagePathParts.concat("/"),
submittedAt = now,
numEdits = 0,
import info.mechyrdia.Configuration
import info.mechyrdia.OwnerNationId
import info.mechyrdia.auth.UserSession
+import info.mechyrdia.concat
import info.mechyrdia.data.FileStorage
import info.mechyrdia.data.StoragePath
import info.mechyrdia.route.Root
return lorePath.indices.mapSuspend { index ->
StoragePath(lorePath.take(index + 1)).toFriendlyPageTitle().title
- }.joinToString(separator = " - ")
+ }.concat(" - ")
}
package info.mechyrdia.lore
+import info.mechyrdia.concat
import info.mechyrdia.data.StoragePath
data class ArticleTitle(val title: String, val css: String = "")
object ArticleTitleCache : FileDependentCache<ArticleTitle>() {
private val StoragePath.defaultTitle: String
get() = if (elements.size > 1)
- elements.lastOrNull()?.split('-')?.joinToString(separator = " ") { word ->
+ elements.last().split('-').concat(" ") { word ->
word.lowercase().replaceFirstChar { it.titlecase() }
- }.orEmpty()
+ }
else TOC_TITLE
private val StoragePath.defaultCssProps: Map<String, Any>
if (name.endsWith(".old")) "text-decoration" to "line-through" else null,
)
- private fun Map<String, Any>.toStyleString() = map { (k, v) -> "$k:$v" }.joinToString(separator = ";")
+ private fun Map<String, Any>.toStyleString() = asIterable().concat(";") { (k, v) -> "$k:$v" }
override fun default(path: StoragePath): ArticleTitle {
return ArticleTitle(path.defaultTitle, path.defaultCssProps.toStyleString())
import com.jaredrummler.fontreader.truetype.FontFileReader
import com.jaredrummler.fontreader.truetype.TTFFile
import com.jaredrummler.fontreader.util.GlyphSequence
+import info.mechyrdia.concat
import info.mechyrdia.data.FileStorage
import info.mechyrdia.data.StoragePath
import info.mechyrdia.data.XmlTagConsumer
svgDoc.viewBoxY,
svgDoc.viewBoxW,
svgDoc.viewBoxH,
- ).joinToString(separator = " ") { it.xmlValue }
+ ).concat(" ") { it.xmlValue }
)
) { "path"(attributes = mapOf("d" to svgDoc.path.d, "fill-rule" to svgDoc.path.fillRule)) }
package info.mechyrdia.lore
import info.mechyrdia.MainDomainName
+import info.mechyrdia.concat
import info.mechyrdia.data.Comment
import info.mechyrdia.data.Id
levels.add(addedLevel)
}
- val number = levels.joinToString(separator = ".") { it.toString() }
- links.add(NavLink("#$toAnchor", "$number. $text", aClasses = "left"))
+ val number = levels.concat(".", suffix = ". $text")
+ links.add(NavLink("#$toAnchor", number, aClasses = "left"))
}
private var description: String? = null
package info.mechyrdia.lore
import info.mechyrdia.JsonStorageCodec
+import info.mechyrdia.concat
import kotlinx.html.*
import kotlinx.html.org.w3c.dom.events.*
import kotlinx.html.stream.*
}
}
-fun ParserTree.treeToText(): String = joinToString(separator = "") {
+fun ParserTree.treeToText(): String = concat {
when (it) {
is ParserTreeNode.Text -> it.text
ParserTreeNode.LineBreak -> " "
package info.mechyrdia.lore
+import info.mechyrdia.concat
+
typealias PlainTextBuilderContext = Unit
typealias PlainTextBuilderSubject = String
}
override fun combine(env: LexerTagEnvironment<PlainTextBuilderContext, PlainTextBuilderSubject>, subjects: List<PlainTextBuilderSubject>): PlainTextBuilderSubject {
- return subjects.joinToString(separator = "")
+ return subjects.concat()
}
}
package info.mechyrdia.lore
import info.mechyrdia.JsonStorageCodec
+import info.mechyrdia.concat
import info.mechyrdia.data.StoragePath
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
fun defaults(lorePath: StoragePath) = defaults(lorePath.elements.drop(1))
fun defaults(lorePath: List<String>) = mapOf(
- PAGE_PATH_KEY to "/${lorePath.joinToString(separator = "/")}".textToTree(),
+ PAGE_PATH_KEY to lorePath.concat("/", prefix = "/").textToTree(),
INSTANT_NOW_KEY to Instant.now().toEpochMilli().numberToTree(),
)
}
package info.mechyrdia.lore
import info.mechyrdia.JsonStorageCodec
+import info.mechyrdia.concat
import info.mechyrdia.data.FileStorage
import info.mechyrdia.data.StoragePath
import kotlinx.serialization.json.JsonArray
}
}
-fun ParserTree.unparse() = joinToString(separator = "") { it.unparse() }
+fun ParserTree.unparse() = concat { it.unparse() }
fun ParserTree.toPreProcessJson(): JsonElement {
val noBlanks = filterNot { it.isWhitespace() }
package info.mechyrdia.lore
+import info.mechyrdia.concat
import info.mechyrdia.robot.toOpenAiName
import java.time.Instant
val filePath = if (startsWith("/"))
this.removePrefix("/")
else
- context.siblingFile(this).joinToString(separator = "/")
+ context.siblingFile(this).concat("/")
return filePath.toOpenAiName()
}
}
override fun combine(env: LexerTagEnvironment<RobotTextContext, RobotTextSubject>, subjects: List<RobotTextSubject>): RobotTextSubject {
- return subjects.joinToString(separator = "")
+ return subjects.concat()
}
}
}),
UL(RobotTextTag { env, _, subNodes ->
- subNodes
- .mapNotNull { subNode ->
- if (subNode is ParserTreeNode.Tag && subNode isTag "li")
- " * ${env.processTree(subNode.subNodes)}"
- else null
- }.joinToString(separator = "")
+ subNodes.concat { subNode ->
+ if (subNode is ParserTreeNode.Tag && subNode isTag "li")
+ " * ${env.processTree(subNode.subNodes)}"
+ else ""
+ }
}),
OL(RobotTextTag { env, _, subNodes ->
- subNodes
- .mapIndexedNotNull { i, subNode ->
- if (subNode is ParserTreeNode.Tag && subNode isTag "li")
- " ${i + 1}. ${env.processTree(subNode.subNodes)}"
- else null
- }.joinToString(separator = "")
+ subNodes.withIndex().concat { (i, subNode) ->
+ if (subNode is ParserTreeNode.Tag && subNode isTag "li")
+ " ${i + 1}. ${env.processTree(subNode.subNodes)}"
+ else ""
+ }
}),
TABLE(RobotTextTag { env, _, subNodes ->
return allPages(null).mapSuspend { pathStat ->
val lorePath = pathStat.path.elements.drop(1)
FactbookLoader.loadFactbook(lorePath)?.toFactbookRobotText(lorePath)?.let { robotText ->
- lorePath.joinToString(separator = "/") to robotText
+ lorePath.concat("/") to robotText
}
}.filterNotNull().toMap()
}
if (pathStat.stat.updated >= lastUpdated) {
val lorePath = pathStat.path.elements.drop(1)
FactbookLoader.loadFactbook(lorePath)?.toFactbookRobotText(lorePath)?.let { robotText ->
- lorePath.joinToString(separator = "/") to robotText
+ lorePath.concat("/") to robotText
}
} else null
}.filterNotNull().toMap()
import com.mongodb.client.model.Sorts
import info.mechyrdia.MainDomainName
import info.mechyrdia.OwnerNationId
+import info.mechyrdia.concat
import info.mechyrdia.data.Comment
import info.mechyrdia.data.CommentRenderData
import info.mechyrdia.data.FileStorage
)
}
+ val pageHref = pageLink.concat("/", prefix = "$MainDomainName/lore/")
RssItem(
title = pageToC.toPageTitle(),
description = pageOg?.description,
- link = "$MainDomainName/lore${pageLink.joinToString(separator = "") { "/$it" }}",
+ link = pageHref,
author = null,
- comments = "$MainDomainName/lore${pageLink.joinToString(separator = "") { "/$it" }}#comments",
+ comments = "$pageHref#comments",
enclosure = imageEnclosure,
pubDate = page.stat.updated,
categories = mechyrdiaCategories,
import info.mechyrdia.Configuration\r
import info.mechyrdia.MainDomainName\r
import info.mechyrdia.OpenAiConfig\r
+import info.mechyrdia.concat\r
import info.mechyrdia.data.DataDocument\r
import info.mechyrdia.data.DocumentTable\r
import info.mechyrdia.data.Id\r
import kotlin.collections.fold\r
import kotlin.collections.forEach\r
import kotlin.collections.iterator\r
-import kotlin.collections.joinToString\r
import kotlin.collections.listOf\r
import kotlin.collections.map\r
import kotlin.collections.minus\r
annotation.text to " [${annotationIndex + 1}]"\r
}\r
\r
- val contents = eventData.delta.content.joinToString(separator = "") { textContent ->\r
+ val contents = eventData.delta.content.concat { textContent ->\r
textContent.text.value\r
}\r
\r
import info.mechyrdia.auth.loginPage
import info.mechyrdia.auth.loginRoute
import info.mechyrdia.auth.logoutRoute
+import info.mechyrdia.concat
import info.mechyrdia.data.Comment
import info.mechyrdia.data.Id
import info.mechyrdia.data.NationData
override suspend fun PipelineContext<Unit, ApplicationCall>.handleCall(payload: MechyrdiaSansPayload) {
with(utils) { call.filterCall() }
- val svgDoc = MechyrdiaSansFont.renderTextToSvg(payload.lines.joinToString(separator = "\n") { it.trim() }, payload.bold, payload.italic, payload.align)
+ val svgDoc = MechyrdiaSansFont.renderTextToSvg(payload.lines.concat("\n") { it.trim() }, payload.bold, payload.italic, payload.align)
call.respondXml(contentType = ContentType.Image.SVG) {
svg(svgDoc)
}
override suspend fun PipelineContext<Unit, ApplicationCall>.handleCall(payload: TylanLanguagePayload) {
with(utils) { call.filterCall() }
- call.respondText(TylanAlphabetFont.tylanToFontAlphabet(payload.lines.joinToString(separator = "\n")))
+ call.respondText(TylanAlphabetFont.tylanToFontAlphabet(payload.lines.concat("\n")))
}
}
override suspend fun PipelineContext<Unit, ApplicationCall>.handleCall(payload: PokhwalishLanguagePayload) {
with(utils) { call.filterCall() }
- call.respondText(PokhwalishAlphabetFont.pokhwalToFontAlphabet(payload.lines.joinToString(separator = "\n")))
+ call.respondText(PokhwalishAlphabetFont.pokhwalToFontAlphabet(payload.lines.concat("\n")))
}
}
with(utils) { call.filterCall() }
call.respondText(
- text = payload.lines.joinToString(separator = "\n").parseAs(ParserTree::toCommentHtml).toFragmentString(),
+ text = payload.lines.concat("\n").parseAs(ParserTree::toCommentHtml).toFragmentString(),
contentType = ContentType.Text.Html
)
}
import info.mechyrdia.auth.WebDavToken
import info.mechyrdia.auth.toNationId
+import info.mechyrdia.concat
import info.mechyrdia.data.FileStorage
import info.mechyrdia.data.Id
import info.mechyrdia.data.StoragePath
.filterNotNull()
.flatten()
- val pathWithSuffix = path.elements.joinToString(separator = "") { "$it/" }
+ val pathWithSuffix = path.elements.concat("/", suffix = "/")
listOf(
WebDavProperties.Collection(
creationDate = subProps.mapNotNull { it.first.creationDate }.maxOrNull(),