Share
Embed
Kotlin as scripting language
Kotlin is gaining more and more popularity, especially after it graduated as official programming language to develop Android Applications. It can be used, however, to write elegant and highly maintainable scripts too and in this this talk I show how.
Presented at Devfest St Petersburg 2019
danybony
November 23, 2019
Transcript
37 slides
-
Total pictures: 1536 Covered by 17-40: 839 (54%) Covered by
24-105: 1413 (91%) Covered by 24-70: 1094 (71%) -
fun main(args: Array<String>) { println("Hello World!") } $ kotlinc hello.kt
-include-runtime -d hello.jar $ java -jar hello.jar two params Hello World! -
println("Called with args:") args.forEach { println("- $it") } $ kotlinc
-script helloScript.kts hello ‘with spaces’ Called with args: - hello - with spaces -
import java.io.File val folders: Array<File>? = File(".").listFiles { file ->
file.isDirectory } folders?.forEach { folder -> println(folder) } $ kotlinc -script dirsExplore.kts ./dir2 ./dir1 -
import java.io.File fun printCurrentAndSubdirs(currentDir: File) { println(currentDir.path) currentDir.listFiles { file
-> file.isDirectory }?.forEach { printCurrentAndSubdirs(it) } } printCurrentAndSubdirs(File(".")) $ kotlinc -script dirsExploreRecursion.kts . ./dir2 ./dir2/dir2.1 ./dir2/dir2.2 ./dir1 -
import java.io.File fun File.printPathAndSubdirs() { println(path) listFiles { file ->
file.isDirectory }?.forEach { it.printPathAndSubdirs() } } File(".").printPathAndSubdirs() $ kotlinc -script dirsExploreRecursionExtension.kts . ./dir2 ./dir2/dir2.1 ./dir2/dir2.2 ./dir1 -
kscript - input modes $ kscript helloScript.kts $ kscript https://gist.githubusercontent.com/danybony/.../kscriptUrl.kts
$ kscript ‘println(“Hello there”)’ $ echo ‘ println("Hello Kotlin.") ' | kscript - -
#!/usr/bin/env kscript println(“Hello!") for (arg in args) { println("arg: $arg")
} $ chmod +x interpreter.kts $ ./interpreter.kts world Hello! arg: world -
kscript - cache $ time kotlinc -script helloScript.kts $ time
kscript helloScript.kts 3.140s 3.112s 3.193s 3.087s 3.064s … 3.896s 0.604s 0.607s 0.620s 0.615s … Scripts cache: ~/.kscript/scriptname_hashcode.kts -
kscript - dependencies //DEPS com.drewnoakes:metadata-extractor:2.12.0 import com.drew.imaging.ImageMetadataReader import com.drew.metadata.Metadata import
java.io.File val metadata: Metadata = ImageMetadataReader.readMetadata(File("image.jpg")) -
kscript - dependencies @file:MavenRepository("central", "https://repo.maven.apache.org/maven2/") @file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2") @file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2") import kotlinx.coroutines.* println("Start")
// Start a coroutine GlobalScope.launch { delay(1000) println("Stop") } println("Hello") Thread.sleep(2000) // wait for 2 seconds -
kscript - includes fun String.upperCase() { ... } //INCLUDE utils.kt
@file:Include("util.kt") val shouted = "hello!".upperCase() script.kts utils.kts -
kscript - help $ kscript --help Usage: kscript [options] <script>
[<script_args>]... kscript --clear-cache The <script> can be a script file (*kts), a script URL, - for stdin, a *.kt source file with a main method, or some kotlin code. Use '--clear-cache' to wipe cached script jars and urls Options: -i --interactive Create interactive shell with dependencies as declared in script -t --text Enable stdin support API for more streamlined text processing --idea Open script in temporary Intellij session -s --silent Suppress status logging to stderr --package Package script and dependencies into self-dependent binary --add-bootstrap-header Prepend bash header that installs kscript if necessary -
kscript - deploy as binaries $ kscript --package helloScript.kts [kscript]
Packaging script 'helloScript' into standalone executable... $ ./helloScript Hello world! -
photoStats #!/usr/bin/env kscript @file:DependsOn("com.drewnoakes:metadata-extractor:2.12.0") import PhotoStats.LensRange import com.drew.imaging.ImageMetadataReader import com.drew.metadata.Metadata
import java.io.File typealias LensRange = Pair<Int, Int> data class Lens( val name: String, val range: LensRange ) val lenses = listOf(LensRange(17, 40), LensRange(24, 105), LensRange(24, 70), LensRange(70, 200)) fun appendFiles(dir: File, filesList: MutableList<File>) { filesList.addAll( dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter { it.isDirectory && it.listFiles().isNotEmpty()} .forEach { appendFiles(it, filesList) } } fun File.focalLength(): Int? { val metadata: Metadata = ImageMetadataReader.readMetadata(this) return metadata.directories .filter { it.name.contains("exif", true) } .firstOrNull { it.tags.find { tag -> tag.tagName == "Focal Length 35" || tag.tagName == "Focal Length" } != null } ?.let { directory -> (directory.tags.firstOrNull { it.tagName == "Focal Length 35" } ?: directory.tags.first { it.tagName == "Focal Length" }) .let { it.description.substringBefore(" ").toInt() } } } val supportedExtensions = listOf("jpg", "jpeg", "cr2", "dng") fun File.isSupported(): Boolean = supportedExtensions.find { it.equals(this.extension, true) } != null val allFiles = ArrayList<File>() val paths = if (args.isNotEmpty()) args.toList() else listOf("./") paths.forEach { val currentDir = File(it) if (currentDir.exists() && currentDir.isDirectory) { println("Scanning ${currentDir.absolutePath}") appendFiles(currentDir, allFiles) } } val focals = allFiles.map { it.focalLength() } .filterNotNull() .sorted() val total = focals.size println("Total pictures: $total") lenses.forEach { lensRange -> val matching = focals.filter { it >= lensRange.first && it <= lensRange.second }.size println("Covered by ${lensRange.first}-${lensRange.second}: $matching (${(matching.toFloat() / total.toFloat() * 100).toInt()}%)") } -
photoStats #!/usr/bin/env kscript @file:DependsOn("com.drewnoakes:metadata-extractor:2.12.0") import PhotoStats.LensRange import com.drew.imaging.ImageMetadataReader import com.drew.metadata.Metadata
import java.io.File typealias LensRange = Pair<Int, Int> data class Lens( val name: String, val range: LensRange ) val lenses = listOf(LensRange(17, 40), LensRange(24, 105), LensRange(24, 70), LensRange(70, 200)) fun appendFiles(dir: File, filesList: MutableList<File>) { filesList.addAll( dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter { it.isDirectory && it.listFiles().isNotEmpty()} -
photoStats #!/usr/bin/env kscript @file:DependsOn("com.drewnoakes:metadata-extractor:2.12.0") import PhotoStats.LensRange import com.drew.imaging.ImageMetadataReader import com.drew.metadata.Metadata
import java.io.File typealias LensRange = Pair<Int, Int> data class Lens( val name: String, val range: LensRange ) val lenses = listOf(LensRange(17, 40), LensRange(24, 105), LensRange(24, 70), LensRange(70, 200)) fun appendFiles(dir: File, filesList: MutableList<File>) { filesList.addAll( dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter { it.isDirectory && it.listFiles().isNotEmpty()} -
#!/usr/bin/env kscript @file:DependsOn("com.drewnoakes:metadata-extractor:2.12.0") import PhotoStats.LensRange import com.drew.imaging.ImageMetadataReader import com.drew.metadata.Metadata import
java.io.File typealias LensRange = Pair<Int, Int> data class Lens( val name: String, val range: LensRange ) val lenses = listOf(LensRange(17, 40), LensRange(24, 105), LensRange(24, 70), LensRange(70, 200)) fun appendFiles(dir: File, filesList: MutableList<File>) { filesList.addAll( dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter { it.isDirectory && it.listFiles().isNotEmpty()} photoStats -
dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter
{ it.isDirectory && it.listFiles().isNotEmpty()} .forEach { appendFiles(it, filesList) } } fun File.focalLength(): Int? { val metadata: Metadata = ImageMetadataReader.readMetadata(this) return metadata.directories .filter { it.name.contains("exif", true) } .firstOrNull { it.tags.find { tag -> tag.tagName == "Focal Length 35" || tag.tagName == "Foca Length" } != null } ?.let { directory -> (directory.tags.firstOrNull { it.tagName == "Focal Length 35" } ?: directory.tags.first { it.tagName == "Focal Length" }) .let { it.description.substringBefore(" ").toInt() } } } val supportedExtensions = listOf("jpg", "jpeg", "cr2", "dng") fun File.isSupported(): Boolean = supportedExtensions.find { it.equals(this.extension, true) } != nul val allFiles = ArrayList<File>() val paths = if (args.isNotEmpty()) args.toList() else listOf("./") paths.forEach { val currentDir = File(it) if (currentDir.exists() && currentDir.isDirectory) { println("Scanning ${currentDir.absolutePath}") appendFiles(currentDir, allFiles) photoStats -
paths.forEach { val currentDir = File(it) if (currentDir.exists() && currentDir.isDirectory)
{ println("Scanning ${currentDir.absolutePath}") appendFiles(currentDir, allFiles) } } val focals = allFiles.map { it.focalLength() } .filterNotNull() .sorted() val total = focals.size println("Total pictures: $total") lenses.forEach { lensRange -> val matching = focals.filter { it >= lensRange.first && it <= lensRange.second }.size println("Covered by ${lensRange.first}-${lensRange.second}: $matching (${(matching.toFloat() / total.toFloat() * 100).toInt()}%)") } photoStats Total pictures: 1536 Covered by 17-40: 839 (54%) Covered by 24-105: 1413 (91%) Covered by 24-70: 1094 (71%) -
Conclusions • Kotlin script support • Good for quick prototyping
• Kotlin language power • Limitations • kscript • Only for *nix-based OS • Powerful • Efficient