From 87dc4a7e0f24e5eeb7fd57b8fd4008bafd0f74a9 Mon Sep 17 00:00:00 2001 From: Doomsdayrs <38189170+Doomsdayrs@users.noreply.github.com> Date: Fri, 21 May 2021 15:21:35 -0400 Subject: [PATCH] KTS Scripting implemented (#17) * ByteArray return implemented. To lower overhead and provide greater abstraction on what can be returned from an extension - IExtension.kt; getPassage return changed to ByteArray - LuaExtension.kt; use checkstring to get the byte array of that string (which can be easily decoded to a string). Lua strings are just bytearrays. So this works fine. - Test.kt; Implemented bytearray decoding * KTS Scripting implemented This implements KTS extension support. Included are additional libraries, some minor changes to the way libs are handled - build.gradle.kts; Updated kotlin, Updated dokka, Implemented scripting support - ExtensionType.kt; Defines the type of extension - javax.script.ScriptEngineFactory; Definition to use the kts script engine - KtsExtension.kt; Delegated class that redirects to the parsed kts script - names.kt; Added json type parameter for extension type - RepoData.kt; RepoExtension now has a type parameter - ShosetsuKtsLib.kt; translation of ShosetsuLuaLib.kt for KTS - ShosetsuSharedLib.kt; httpClient is now located here to be shared - ShosetsuLuaLib.kt; now uses ShosetsuSharedLib.kt, old httpClient is deprecated - Test.kt; Modified to use path values instead and patched for KTS support using ExtensionType.kt --- build.gradle.kts | 29 +++++++---- .../kotlin/app/shosetsu/lib/ExtensionType.kt | 12 +++++ .../app/shosetsu/lib/ShosetsuSharedLib.kt | 12 +++++ .../kotlin/app/shosetsu/lib/json/RepoData.kt | 5 +- .../kotlin/app/shosetsu/lib/json/names.kt | 1 + .../app/shosetsu/lib/kts/KtsExtension.kt | 17 +++++++ .../app/shosetsu/lib/kts/KtsObjectLoader.kt | 22 ++++++++ .../app/shosetsu/lib/kts/ShosetsuKtsLib.kt | 49 ++++++++++++++++++ .../app/shosetsu/lib/lua/ShosetsuLuaLib.kt | 15 ++++-- .../services/javax.script.ScriptEngineFactory | 1 + src/test/kotlin/app/shosetsu/lib/Test.kt | 50 ++++++++----------- 11 files changed, 168 insertions(+), 45 deletions(-) create mode 100644 src/main/kotlin/app/shosetsu/lib/ExtensionType.kt create mode 100644 src/main/kotlin/app/shosetsu/lib/ShosetsuSharedLib.kt create mode 100644 src/main/kotlin/app/shosetsu/lib/kts/KtsExtension.kt create mode 100755 src/main/kotlin/app/shosetsu/lib/kts/KtsObjectLoader.kt create mode 100644 src/main/kotlin/app/shosetsu/lib/kts/ShosetsuKtsLib.kt create mode 100644 src/main/resources/META-INF/services/javax.script.ScriptEngineFactory diff --git a/build.gradle.kts b/build.gradle.kts index d2dd649..ed32ea0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,17 +6,14 @@ version = "1.0.0" description = "Kotlin library for shosetsu" plugins { - kotlin("jvm") version "1.4.20" - id("org.jetbrains.dokka") version "0.10.0" - kotlin("plugin.serialization") version "1.4.20" + kotlin("jvm") version "1.5.0" + id("org.jetbrains.dokka") version "1.4.32" + kotlin("plugin.serialization") version "1.5.0" maven } -tasks.withType { kotlinOptions.jvmTarget = "1.8" } +tasks.withType { kotlinOptions.jvmTarget = "1.8" } + -tasks.dokka { - outputFormat = "html" - outputDirectory = "$buildDir/javadoc" -} val dokkaJar by tasks.creating(Jar::class) { group = JavaBasePlugin.DOCUMENTATION_GROUP @@ -32,19 +29,29 @@ repositories { dependencies { implementation(kotlin("stdlib")) implementation("org.jsoup:jsoup:1.12.1") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.20") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0") + dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.4.32") // java only implementation("org.luaj:luaj-jse:3.0.1") implementation("com.squareup.okhttp3:okhttp:4.2.1") implementation("com.google.guava:guava:30.0-jre") + implementation("net.java.dev.jna:jna:4.2.2") // Cross platform confirmed implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") - testImplementation("org.jetbrains.kotlin:kotlin-test:1.4.20") + testImplementation("org.jetbrains.kotlin:kotlin-test:1.5.0") + + implementation(kotlin("reflect")) + implementation(kotlin("script-runtime")) + implementation(kotlin("script-util")) + implementation(kotlin("compiler-embeddable")) + implementation(kotlin("scripting-compiler-embeddable")) + implementation(kotlin("script-util")) + } val compileTestKotlin: KotlinCompile by tasks compileTestKotlin.kotlinOptions { - languageVersion = "1.4" + languageVersion = "1.5" } \ No newline at end of file diff --git a/src/main/kotlin/app/shosetsu/lib/ExtensionType.kt b/src/main/kotlin/app/shosetsu/lib/ExtensionType.kt new file mode 100644 index 0000000..f141142 --- /dev/null +++ b/src/main/kotlin/app/shosetsu/lib/ExtensionType.kt @@ -0,0 +1,12 @@ +package app.shosetsu.lib + +/** + * Type of extension + */ +enum class ExtensionType { + /** .lua */ + LuaScript, + + /** .kts */ + KotlinScript +} diff --git a/src/main/kotlin/app/shosetsu/lib/ShosetsuSharedLib.kt b/src/main/kotlin/app/shosetsu/lib/ShosetsuSharedLib.kt new file mode 100644 index 0000000..d83045b --- /dev/null +++ b/src/main/kotlin/app/shosetsu/lib/ShosetsuSharedLib.kt @@ -0,0 +1,12 @@ +package app.shosetsu.lib + +import okhttp3.OkHttpClient + +/** + * shosetsu-kotlin-lib + * 06 / 10 / 2020 + */ +object ShosetsuSharedLib { + /** okhttp HTTP Client used by lib functions. */ + lateinit var httpClient: OkHttpClient +} diff --git a/src/main/kotlin/app/shosetsu/lib/json/RepoData.kt b/src/main/kotlin/app/shosetsu/lib/json/RepoData.kt index 59e3848..620fa6f 100644 --- a/src/main/kotlin/app/shosetsu/lib/json/RepoData.kt +++ b/src/main/kotlin/app/shosetsu/lib/json/RepoData.kt @@ -1,5 +1,6 @@ package app.shosetsu.lib.json +import app.shosetsu.lib.ExtensionType import app.shosetsu.lib.Version import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -36,7 +37,9 @@ data class RepoExtension internal constructor( @SerialName(J_LIB_VERSION) val libVersion: Version, @SerialName(J_MD5) - val md5: String + val md5: String, + @SerialName(J_EXTENSION_TYPE) + val type: ExtensionType ) diff --git a/src/main/kotlin/app/shosetsu/lib/json/names.kt b/src/main/kotlin/app/shosetsu/lib/json/names.kt index 8165fa9..e4737f2 100644 --- a/src/main/kotlin/app/shosetsu/lib/json/names.kt +++ b/src/main/kotlin/app/shosetsu/lib/json/names.kt @@ -9,6 +9,7 @@ const val J_VERSION = "ver" const val J_LIB_VERSION = "libVer" const val J_FILE_NAME = "fileName" const val J_IMAGE_URL = "imageURL" +const val J_EXTENSION_TYPE = "type" // LuaExtension diff --git a/src/main/kotlin/app/shosetsu/lib/kts/KtsExtension.kt b/src/main/kotlin/app/shosetsu/lib/kts/KtsExtension.kt new file mode 100644 index 0000000..c0763b0 --- /dev/null +++ b/src/main/kotlin/app/shosetsu/lib/kts/KtsExtension.kt @@ -0,0 +1,17 @@ +package app.shosetsu.lib.kts + +import app.shosetsu.lib.IExtension +import java.io.File +import kotlin.time.ExperimentalTime + +/** + * shosetsu-services + * 06 / 10 / 2020 + */ +@ExperimentalTime +class KtsExtension( + private val content: String, + private val _kts: IExtension = KtsObjectLoader().load(content) +) : IExtension by _kts { + constructor(file: File) : this(file.readText()) +} diff --git a/src/main/kotlin/app/shosetsu/lib/kts/KtsObjectLoader.kt b/src/main/kotlin/app/shosetsu/lib/kts/KtsObjectLoader.kt new file mode 100755 index 0000000..3bf758e --- /dev/null +++ b/src/main/kotlin/app/shosetsu/lib/kts/KtsObjectLoader.kt @@ -0,0 +1,22 @@ +package app.shosetsu.lib.kts + +import javax.script.ScriptEngine +import javax.script.ScriptEngineManager + +/** + * This class is not thread-safe, don't use it for parallel executions and create new instances instead. + */ +class KtsObjectLoader(classLoader: ClassLoader? = Thread.currentThread().contextClassLoader) { + + val engine: ScriptEngine = ScriptEngineManager(classLoader).getEngineByExtension("kts") + + @Throws(IllegalArgumentException::class) + inline fun Any?.castOrError(): T = takeIf { it is T }?.let { it as T } + ?: throw IllegalArgumentException("Cannot cast $this to expected type ${T::class}") + + @Throws(RuntimeException::class) + inline fun load(script: String): T = + kotlin.runCatching { engine.eval(script) } + .getOrElse @Throws(RuntimeException::class) { throw RuntimeException("Cannot load script", it) } + .castOrError() +} \ No newline at end of file diff --git a/src/main/kotlin/app/shosetsu/lib/kts/ShosetsuKtsLib.kt b/src/main/kotlin/app/shosetsu/lib/kts/ShosetsuKtsLib.kt new file mode 100644 index 0000000..56ddf4e --- /dev/null +++ b/src/main/kotlin/app/shosetsu/lib/kts/ShosetsuKtsLib.kt @@ -0,0 +1,49 @@ +package app.shosetsu.lib.kts + +import app.shosetsu.lib.ShosetsuSharedLib.httpClient +import app.shosetsu.lib.exceptions.HTTPException +import app.shosetsu.lib.lua.ShosetsuLuaLib.LibFunctions.RequestDocument +import okhttp3.* +import okhttp3.internal.closeQuietly +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import java.util.concurrent.TimeUnit + +/** + * shosetsu-kotlin-lib + * 06 / 10 / 2020 + */ +object ShosetsuKtsLib { + val defaultCacheControl: CacheControl + get() = CacheControl.Builder().maxAge(10, TimeUnit.MINUTES).build() + + val defaultHeaders: Headers + get() = Headers.Builder().build() + + val defaultBody: RequestBody + get() = FormBody.Builder().build() + + + // For normal extensions, these simple functions are sufficient. + fun get(url: String, headers: Headers, cacheControl: CacheControl): Request = + Request.Builder().url(url).headers(headers).cacheControl(cacheControl).build() + + fun post(url: String, headers: Headers, body: RequestBody, cacheControl: CacheControl): Request = + Request.Builder().url(url).post(body).headers(headers).cacheControl(cacheControl).build() + + + fun parseDocument(content: String): Document = Jsoup.parse(content)!! + + fun requestResponse(request: Request): Response = httpClient.newCall(request).execute() + + fun requestDocument(request: Request): Document = Document( + requestResponse(request).let { r -> + r.takeIf { it.code == 200 }?.body?.string() ?: run { + r.closeQuietly() + throw HTTPException(r.code) + } + } + ) + + fun getDocument(url: String): Document = RequestDocument(get(url, defaultHeaders, defaultCacheControl)) +} \ No newline at end of file diff --git a/src/main/kotlin/app/shosetsu/lib/lua/ShosetsuLuaLib.kt b/src/main/kotlin/app/shosetsu/lib/lua/ShosetsuLuaLib.kt index 6f3770b..e79bedf 100644 --- a/src/main/kotlin/app/shosetsu/lib/lua/ShosetsuLuaLib.kt +++ b/src/main/kotlin/app/shosetsu/lib/lua/ShosetsuLuaLib.kt @@ -200,7 +200,7 @@ class ShosetsuLuaLib : TwoArgFunction() { } fun Document(str: String): Document = Jsoup.parse(str)!! - fun Request(req: Request): Response = httpClient.newCall(req).execute() + fun Request(req: Request): Response = ShosetsuSharedLib.httpClient.newCall(req).execute() @Throws(HTTPException::class) fun RequestDocument(req: Request): Document = Document( @@ -222,7 +222,7 @@ class ShosetsuLuaLib : TwoArgFunction() { ) // For advanced users who want to (or need to) do everything themselves. - fun HttpClient(): OkHttpClient = httpClient + fun HttpClient(): OkHttpClient = ShosetsuSharedLib.httpClient fun RequestBuilder(): Request.Builder = Request.Builder() fun HeadersBuilder(): Headers.Builder = Headers.Builder() @@ -268,8 +268,15 @@ class ShosetsuLuaLib : TwoArgFunction() { lateinit var libLoader: (name: String) -> LuaValue? /** okhttp client used by [LibFunctions] */ - lateinit var httpClient: OkHttpClient - + @Deprecated( + "Use ShosetsuSharedLib", + ReplaceWith("ShosetsuSharedLib.httpClient", "app.shosetsu.lib.ShosetsuSharedLib") + ) + var httpClient: OkHttpClient + get() = ShosetsuSharedLib.httpClient + set(value) { + ShosetsuSharedLib.httpClient = value + } private val permaLuaFuncs by lazy { mapOf( "GET" to loadResource("GET.lua"), diff --git a/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory new file mode 100644 index 0000000..f8f5900 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory @@ -0,0 +1 @@ +org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory \ No newline at end of file diff --git a/src/test/kotlin/app/shosetsu/lib/Test.kt b/src/test/kotlin/app/shosetsu/lib/Test.kt index c6de73e..36150b8 100644 --- a/src/test/kotlin/app/shosetsu/lib/Test.kt +++ b/src/test/kotlin/app/shosetsu/lib/Test.kt @@ -1,6 +1,10 @@ package app.shosetsu.lib +import app.shosetsu.lib.ExtensionType.KotlinScript +import app.shosetsu.lib.ExtensionType.LuaScript +import app.shosetsu.lib.ShosetsuSharedLib.httpClient import app.shosetsu.lib.json.RepoIndex +import app.shosetsu.lib.kts.KtsExtension import app.shosetsu.lib.lua.LuaExtension import app.shosetsu.lib.lua.ShosetsuLuaLib import app.shosetsu.lib.lua.shosetsuGlobals @@ -12,6 +16,7 @@ import java.io.File import java.util.concurrent.TimeUnit.MILLISECONDS import kotlin.system.exitProcess import kotlin.time.Duration +import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime import kotlin.time.measureTimedValue @@ -33,10 +38,7 @@ import kotlin.time.measureTimedValue * shosetsu-services * 03 / June / 2019 * - * @author github.com/doomsdayrs;github.com/TechnoJo4 - * - * In IDEA, The Classpath should be shosetsu-services but - * the Working directory should be shosetsu-extensions. + * @author github.com/doomsdayrs; github.com/TechnoJo4 */ @ExperimentalTime object Test { @@ -59,27 +61,13 @@ object Test { private const val SPECIFIC_NOVEL_URL = "/" private const val SPECIFIC_CHAPTER = 0 - private val SOURCES: List = arrayOf( - "en/BestLightNovel", - //"en/BoxNovel", - //"en/CreativeNovels", - //"en/FastNovel", - //"en/Foxaholic", // TODO: Investigate - //"en/KissLightNovels", - //"en/MNovelFree", //Doesn't seem to be a novelfull - //"en/MTLNovel", - //"en/NovelFull", - //"en/NovelTrench", - //"en/ReadLightNovel", - //"en/ReadNovelFull", - //"en/VipNovel", - //"en/VolareNovels", - //"en/WuxiaWorld", - //"jp/Syosetsu", - //"pt/SaikaiScan", - //"zn/15doc", - //"zn/Tangsanshu" - ).map { "src/main/resources/src/$it.lua" } + /** Replace with the directory of the extensions you want to use*/ + private const val DIRECTORY = "" + + private val SOURCES: Array> = + // Should be an array of the path of the script to the type of that script + arrayOf>() + // END CONFIG private val globals = shosetsuGlobals() @@ -95,12 +83,12 @@ object Test { ShosetsuLuaLib.libLoader = { outputTimedValue("loadScript") { loadScript( - File("src/main/resources/lib/$it.lua"), + File("$DIRECTORY/src/main/resources/lib/$it.lua"), "lib" ) } } - ShosetsuLuaLib.httpClient = OkHttpClient.Builder().addInterceptor { + httpClient = OkHttpClient.Builder().addInterceptor { outputTimedValue("Time till response") { it.proceed(it.request().also { request -> println(request.url.toUrl().toString()) @@ -246,7 +234,7 @@ object Test { @ExperimentalTime private fun printExecutionTime(job: String, time: Duration) { - printExecutionTime(job, time.inMilliseconds) + printExecutionTime(job, time.toDouble(DurationUnit.MILLISECONDS)) } private fun printExecutionTime(job: String, timeMs: Double) { @@ -275,7 +263,11 @@ object Test { val extension = outputTimedValue("LuaExtension") { - LuaExtension(File(extensionPath)) + val file = File(extensionPath.first) + when (extensionPath.second) { + LuaScript -> LuaExtension(file) + KotlinScript -> KtsExtension(file) + } } if (SPECIFIC_NOVEL) {