从常规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 通过 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 是用于使用脚本引擎(例如 纳斯霍恩 )。它使用户能够编写可定制的脚本代码,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脚本API实现,从Kotlin程序执行Kotlin脚本非常容易。但是,由于将支持集成到应用程序中比较麻烦,因此我编写了一个小型库,其中封装了Scripting API粘合代码。叫做 KtsRunner 可以在找到 的GitHub .

的 KtsRunner is a lightweight tool for executing Kotlin scripts from your custom applications. 的 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. 的 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. 的 actual test cases are provided 通过 technical testers who write their test scripts using a domain specific language that is provided 通过 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 通过 the testing team. 的 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. 的 HTML DSL code is provided as .kts script files and might look 像这样 :

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脚本支持。该库发布于 托盘 因此可以轻松地在您自己的应用程序中使用。

关于4个想法 “从常规Kotlin程序运行Kotlin脚本(kts)

发表评论

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