package info.mechyrdia
import info.mechyrdia.lore.*
-import io.ktor.application.*
-import io.ktor.features.*
-import io.ktor.html.*
import io.ktor.http.*
-import io.ktor.http.content.*
-import io.ktor.request.*
-import io.ktor.response.*
-import io.ktor.routing.*
+import io.ktor.server.application.*
import io.ktor.server.engine.*
+import io.ktor.server.html.*
+import io.ktor.server.http.content.*
import io.ktor.server.netty.*
+import io.ktor.server.plugins.*
+import io.ktor.server.plugins.callid.*
+import io.ktor.server.plugins.callloging.*
+import io.ktor.server.plugins.forwardedheaders.*
+import io.ktor.server.plugins.statuspages.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
import org.slf4j.event.Level
import java.io.File
import java.io.IOException
embeddedServer(Netty, port = Configuration.CurrentConfiguration.port, host = Configuration.CurrentConfiguration.host) {
install(IgnoreTrailingSlash)
- install(XForwardedHeaderSupport)
+ install(XForwardedHeaders)
install(CallId) {
val counter = AtomicLong(0)
}
install(StatusPages) {
- status(HttpStatusCode.NotFound) {
+ status(HttpStatusCode.NotFound) { call, _ ->
call.respondHtml(HttpStatusCode.NotFound, call.error404())
}
- exception<HttpRedirectException> { (url, permanent) ->
+ exception<HttpRedirectException> { call, (url, permanent) ->
call.respondRedirect(url, permanent)
}
- exception<MissingRequestParameterException> {
+ exception<MissingRequestParameterException> { call, _ ->
call.respondHtml(HttpStatusCode.BadRequest, call.error400())
}
- exception<NullPointerException> {
+ exception<NullPointerException> { call, _ ->
call.respondHtml(HttpStatusCode.NotFound, call.error404())
}
- exception<IOException> {
+ exception<IOException> { call, _ ->
call.respondHtml(HttpStatusCode.NotFound, call.error404())
}
- exception<Throwable> {
- call.respondHtml(HttpStatusCode.InternalServerError, call.error503())
- throw it
+ exception<Throwable> { call, ex ->
+ call.application.log.error("Got uncaught exception from serving call ${call.callId}", ex)
+
+ call.respondHtml(HttpStatusCode.InternalServerError, call.error500())
+ throw ex
}
}
package info.mechyrdia.lore
data class TextParserScope<TContext>(
+ var buffer: String = "",
val write: Appendable,
val tags: TextParserTags<TContext>,
val ctx: TContext
sealed class TextParserState<TContext>(
val scope: TextParserScope<TContext>,
- val insideTags: List<Pair<String, String?>>,
+ val insideTags: List<Pair<TextParserTagType.Indirect<TContext>, String?>>,
val insideDirectTags: List<String>
) {
abstract fun processCharacter(char: Char): TextParserState<TContext>
abstract fun processEndOfText()
protected fun appendText(text: String) {
- scope.write.append(
- insideTags.foldRight(censorText(text)) { (tag, param), t ->
- (scope.tags[tag] as? TextParserTagType.Indirect<TContext>)
- ?.process(param, t, scope.ctx)
- ?: "[$tag${param?.let { "=$it" } ?: ""}]$t[/$tag]"
- }
- )
+ scope.buffer += censorText(text)
}
protected fun appendTextRaw(text: String) {
- scope.write.append(text)
+ scope.buffer += text
+ }
+
+ protected fun flushBuffer() {
+ scope.write.append(
+ insideTags.foldRight(scope.buffer) { (tag, param), t ->
+ tag.process(param, t, scope.ctx)
+ }
+ )
+ scope.buffer = ""
}
class Initial<TContext>(scope: TextParserScope<TContext>) : TextParserState<TContext>(scope, listOf(), listOf()) {
}
}
- class PlainText<TContext>(scope: TextParserScope<TContext>, val text: String, insideTags: List<Pair<String, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
+ class PlainText<TContext>(scope: TextParserScope<TContext>, val text: String, insideTags: List<Pair<TextParserTagType.Indirect<TContext>, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
override fun processCharacter(char: Char): TextParserState<TContext> {
return if (char == '[') {
appendText(text)
override fun processEndOfText() {
appendText(text)
+ flushBuffer()
}
}
- class NoFormatText<TContext>(scope: TextParserScope<TContext>, val text: String, insideTags: List<Pair<String, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
+ class NoFormatText<TContext>(scope: TextParserScope<TContext>, val text: String, insideTags: List<Pair<TextParserTagType.Indirect<TContext>, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
override fun processCharacter(char: Char): TextParserState<TContext> {
val newText = text + char
return if (newText.endsWith("[/$NO_FORMAT_TAG]")) {
override fun processEndOfText() {
appendText(text)
+ flushBuffer()
}
}
- class OpenTag<TContext>(scope: TextParserScope<TContext>, val tag: String, insideTags: List<Pair<String, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
+ class OpenTag<TContext>(scope: TextParserScope<TContext>, val tag: String, insideTags: List<Pair<TextParserTagType.Indirect<TContext>, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
override fun processCharacter(char: Char): TextParserState<TContext> {
return if (char == ']') {
if (tag.equals(NO_FORMAT_TAG, ignoreCase = true))
(scope.tags[tag] as? TextParserTagType.Direct<TContext>)?.begin(null, scope.ctx)?.let {
appendTextRaw(it)
}
+ flushBuffer()
PlainText(scope, "", insideTags, insideDirectTags + tag)
- } else
- PlainText(scope, "", insideTags + (tag to null), insideDirectTags)
+ } else if (scope.tags[tag] is TextParserTagType.Indirect<TContext>) {
+ val tagType = scope.tags[tag] as TextParserTagType.Indirect<TContext>
+ flushBuffer()
+
+ PlainText(scope, "", insideTags + (tagType to null), insideDirectTags)
+ } else {
+ appendText("[$tag]")
+
+ PlainText(scope, "", insideTags, insideDirectTags)
+ }
} else if (char == '/' && tag == "")
CloseTag(scope, tag, insideTags, insideDirectTags)
else if (char == '=' && tag != "")
override fun processEndOfText() {
appendText("[$tag")
+ flushBuffer()
}
}
- class TagParam<TContext>(scope: TextParserScope<TContext>, val tag: String, val param: String, insideTags: List<Pair<String, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
+ class TagParam<TContext>(scope: TextParserScope<TContext>, val tag: String, val param: String, insideTags: List<Pair<TextParserTagType.Indirect<TContext>, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
override fun processCharacter(char: Char): TextParserState<TContext> {
return if (char == ']') {
- val tagType = scope.tags[tag]
- if (tagType is TextParserTagType.Direct<TContext>) {
- appendTextRaw(tagType.begin(param, scope.ctx))
+ when (val tagType = scope.tags[tag]) {
+ is TextParserTagType.Direct<TContext> -> {
+ appendTextRaw(tagType.begin(param, scope.ctx))
+ flushBuffer()
+
+ PlainText(scope, "", insideTags, insideDirectTags + tag)
+ }
- PlainText(scope, "", insideTags, insideDirectTags + tag)
- } else
- PlainText(scope, "", insideTags + (tag to param), insideDirectTags)
+ is TextParserTagType.Indirect<TContext> -> {
+ flushBuffer()
+ PlainText(scope, "", insideTags + (tagType to param), insideDirectTags)
+ }
+ else -> {
+ appendText("[$tag=$param]")
+
+ PlainText(scope, "", insideTags, insideDirectTags)
+ }
+ }
} else
TagParam(scope, tag, param + char, insideTags, insideDirectTags)
}
override fun processEndOfText() {
appendText("[$tag=$param")
+ flushBuffer()
}
}
- class CloseTag<TContext>(scope: TextParserScope<TContext>, val tag: String, insideTags: List<Pair<String, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
+ class CloseTag<TContext>(scope: TextParserScope<TContext>, val tag: String, insideTags: List<Pair<TextParserTagType.Indirect<TContext>, String?>>, insideDirectTags: List<String>) : TextParserState<TContext>(scope, insideTags, insideDirectTags) {
override fun processCharacter(char: Char): TextParserState<TContext> {
return if (char == ']') {
val tagType = scope.tags[tag]
if (tagType is TextParserTagType.Direct<TContext> && insideDirectTags.last().equals(tag, ignoreCase = true)) {
appendTextRaw(tagType.end(scope.ctx))
-
+ flushBuffer()
PlainText(scope, "", insideTags, insideDirectTags.dropLast(1))
- } else if (insideTags.isNotEmpty() && insideTags.last().first.equals(tag, ignoreCase = true)) {
+ } else if (insideTags.isNotEmpty() && insideTags.last().first == scope.tags[tag]) {
+ flushBuffer()
PlainText(scope, "", insideTags.dropLast(1), insideDirectTags)
} else {
appendText("[/$tag]")
override fun processEndOfText() {
appendText("[/$tag")
+ flushBuffer()
}
}
val builder = StringBuilder()
try {
val fixedText = text.replace("\r\n", "\n").replace('\r', '\n')
- fixedText.fold<TextParserState<TContext>>(Initial(TextParserScope(builder, tags, context))) { state, char -> state.processCharacter(char) }.processEndOfText()
+ fixedText.fold<TextParserState<TContext>>(Initial(TextParserScope("", builder, tags, context))) { state, char -> state.processCharacter(char) }.processEndOfText()
} catch (ex: Exception) {
return ParseOutcome("<p>$builder</p><h1>Internal Error!</h1><pre>${ex.stackTraceToString()}</pre>", false)
}