从常规Kotlin程序运行Kotlin脚本(KTS)

从kotlin程序运行kotlin脚本

本文介绍了从Kotlin程序运行Kotlin脚本的方法,以利用DSL的功率。

Kotlin can be used as a scripting language. Simply write top-level executable code inside a file with .kts extension and run it with the kotlinc as described in the 文件。这也是Gradle构建文件的格式,它与之结合使用 Gradle Kotlin DSL. 像这样 Gradle.Build.Kts.. Gradle shows a fantastic example of a domain specific language that can be written standalone in .kts files to be read by the gradle tool later on. When we try to find a way to do the same with custom DSLs (Tutorial can be found 这里),我们首先需要知道如何从Kotlin程序运行Kotlin脚本。文章揭示了如何这样做。

Java脚本API.(JSR-223)

Java脚本API. 是使用脚本发动机的工具(例如 Nashorn.)来自Java代码。它使用户能够编写可根据运行时由Java应用程序拾取的可自定义脚本代码。在某种程度上,API是写入可扩展应用程序的简洁方式。

作为 Kotlin 1.1.,对应的JSR-223也支持Kotlin脚本。这意味着它可以从常规Kotlin程序运行Kotlin脚本,以便通过这些脚本进行自定义的应用程序。

使用Kotlin脚本引擎

In order to use the mentioned Kotlin script engine, a file called javax.script.ScriptEngineFactory has to be placed inside META-INF/services of your application. It should contain the following entry: org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory.
After that, the javax.script.ScriptEngineManager will be able to find the corresponding engine when looked up via ScriptEngineManager().getEngineByExtension("kts"). This code now finds the Kotlin ScriptEngine implementation, an instance that can be used to evaluate String-based scripts such as "5 + 2", or directly read scripts from the file system. Here's a short example:

with(ScriptEngineManager().getEngineByExtension("kts")) {
    eval("val x = 3")
    val res2 = eval("x + 2")
    assertEquals(5, res2)
}

您还可以编译脚本并稍后评估它们:

val script = compile("""listOf(1,2,3).joinToString(":")""")
assertEquals(listOf(1, 2, 3).joinToString(":"), script.eval())

在图书馆包装胶水代码

如图所示,由于Kotlin的Java Scripting API实现,执行从Kotlin程序的Kotlin脚本非常简单。然而,由于将支持集成到应用程序中有点繁琐,因此写了一个微小的库,该库封装了脚本API胶合代码。它被称为 KtsRunner. 并且可以找到 GitHub..

这 KtsRunner is a lightweight tool for executing Kotlin scripts from your custom applications. The API, as of the very first version, provides a slim KtsObjectLoader class whose usage is shown in the following example:

data class ClassFromScript(val x: String)
import de.swirtz.ktsobjectloader.ClassFromScript

ClassFromScript("I was created in kts")

这 previous snippets show the definition of some arbitrary data class and the code that instantiates an object of it. The object instantiation is basically what we write into a .kts file.

val scriptReader = Files.newBufferedReader(Paths.get("path/classDeclaration.kts"))
val loadedObj: ClassFromScript = KtsObjectLoader().load<ClassFromScript>(scriptReader)
assertEquals("I was created in kts", loadedObj.x)

Using the KtsObjectLoader makes it simple to load the correspoding object of ClassFromScript from the script file. Alternatively, the script could also be provided as a String:

val scriptContent = "5 + 10"
val result: Int = KtsObjectLoader().load<Int>(scriptContent))
assertEquals(15, result)

充足的使用情况

As mentioned in the beginning, it can make sense to make your application customizable through external scripts, similar to how Gradle can be extended with any custom build script. Imagine an application that provides a test suite runtime. The actual test cases are provided by technical testers who write their test scripts using a domain specific language that is provided by the main application. Since you don't want testers to add source files (defining new test cases) to your application all the time, the test case creation is made in independent .kts files in which the DSL is utilized by the testing team. The test suite main application can use the presented KtsRunner. library for loading the test cases provided in .kts files and process them further afterward.

一个例子

Kotlin的一个非常受欢迎的DSL是 kotlinx.html., a language for describing type-safe HTML. You let the client of your application provide some arbitrary HTML that you want to render at a later time. The HTML DSL code is provided as .kts script files and might look like this:

import kotlinx.html.*
import kotlinx.html.dom.create
import org.w3c.dom.Element
import java.io.OutputStream
import java.io.OutputStreamWriter
import javax.xml.parsers.DocumentBuilderFactory

val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
document.create.html {
    head {
        title("Hello world")
    }
    body {
        h1("h1Class") {
            style = "background-color:red"
            +"My header1"
        }
        p("pClass") {
            +"paragraph1"
        }
    }
}

When executed, an instance of org.w3c.dom.Element is created that contains the described HTML code in an XML document:

<?xml version="1.0" encoding="UTF-8"?><html>
    <head>
        <title>Hello world</title>
    </head>
    <body>
        <h1 class="h1Class" style="background-color:red">My header1</h1>
        <p class="pClass">paragraph1</p>
    </body>
</html>

这很简单,但有趣的部分是脚本实际上应该从主程序执行。为此目的,我们添加了 KtsRunner. 通过将存储库和依赖项本身添加到Gradle构建文件来应用程序:

maven { 
    setUrl("//dl.bintray.com/s1m0nw1/KtsRunner")
}
dependencies {
    //...
    compile("de.swirtz:ktsRunner:0.0.x")
}  

这 final code for loading the Element from the external script looks as follows:

KtsObjectLoader().load<Element>(script)

简单,不是吗?不幸的是,Kotlin的显示脚本API实现相当慢,您肯定会注意到一些性能约束。完全是,呢 KtsRunner. 是一个非常小的工具,只封装胶合代码,用于在随机应用程序中启用Kotlin脚本支持。图书馆发布 Bintray. 因此,可以从您自己的应用程序中轻松使用。

4 thoughts on “从常规Kotlin程序运行Kotlin脚本(KTS)

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *