Slide 1

Slide 1 text

Kotlin as scripting language Daniele Bonaldo

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Total pictures: 1536 Covered by 17-40: 839 (54%) Covered by 24-105: 1413 (91%) Covered by 24-70: 1094 (71%)

Slide 5

Slide 5 text

Bash Java Python Kotlin

Slide 6

Slide 6 text

fun main(args: Array) { println("Hello World!") } $ kotlinc hello.kt -include-runtime -d hello.jar $ java -jar hello.jar two params Hello World!

Slide 7

Slide 7 text

println("Hello world!") $ kotlinc -script helloScript.kts Hello world!

Slide 8

Slide 8 text

println("Called with args:") args.forEach { println("- $it") } $ kotlinc -script helloScript.kts hello ‘with spaces’ Called with args: - hello - with spaces

Slide 9

Slide 9 text

import java.io.File val folders: Array? = File(".").listFiles { file -> file.isDirectory } folders?.forEach { folder -> println(folder) } $ kotlinc -script dirsExplore.kts ./dir2 ./dir1

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

kscript

Slide 13

Slide 13 text

kscript https://github.com/holgerbrandl/kscript

Slide 14

Slide 14 text

kscript - install $ brew install holgerbrandl/tap/kscript

Slide 15

Slide 15 text

println("Hello world!") $ kotlinc -script helloScript.kts Hello world!

Slide 16

Slide 16 text

println("Hello world!") $ kscript helloScript.kts Hello world!

Slide 17

Slide 17 text

kscript - input modes $ kscript helloScript.kts $ kscript https://gist.githubusercontent.com/danybony/.../kscriptUrl.kts $ kscript ‘println(“Hello there”)’ $ echo ‘ println("Hello Kotlin.") ' | kscript -

Slide 18

Slide 18 text

#!/usr/bin/env kscript println(“Hello!") for (arg in args) { println("arg: $arg") } $ chmod +x interpreter.kts $ ./interpreter.kts world Hello! arg: world

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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"))

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

kscript - includes fun String.upperCase() { ... } //INCLUDE utils.kt @file:Include("util.kt") val shouted = "hello!".upperCase() script.kts utils.kts

Slide 23

Slide 23 text

kscript - help $ kscript --help Usage: kscript [options] [<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

Slide 24

Slide 24 text

Kscript - edit in IntelliJ IDEA $ kscript --idea photoStats.kts

Slide 25

Slide 25 text

Kscript - edit in IntelliJ IDEA

Slide 26

Slide 26 text

Kscript - edit in IntelliJ IDEA

Slide 27

Slide 27 text

kscript - deploy as binaries $ kscript --package helloScript.kts [kscript] Packaging script 'helloScript' into standalone executable... $ ./helloScript Hello world!

Slide 28

Slide 28 text

photoStats

Slide 29

Slide 29 text

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 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) { 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() 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()}%)") }

Slide 30

Slide 30 text

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 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) { filesList.addAll( dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter { it.isDirectory && it.listFiles().isNotEmpty()}

Slide 31

Slide 31 text

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 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) { filesList.addAll( dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter { it.isDirectory && it.listFiles().isNotEmpty()}

Slide 32

Slide 32 text

#!/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 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) { filesList.addAll( dir.listFiles() .filter { !it.isDirectory && it.isSupported() } ) dir.listFiles() .filter { it.isDirectory && it.listFiles().isNotEmpty()} photoStats

Slide 33

Slide 33 text

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() 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

Slide 34

Slide 34 text

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%)

Slide 35

Slide 35 text

Conclusions

Slide 36

Slide 36 text

Conclusions • Kotlin script support • Good for quick prototyping • Kotlin language power • Limitations • kscript • Only for *nix-based OS • Powerful • Efficient

Slide 37

Slide 37 text

Daniele Bonaldo Thank You! @danybony_ danybony danielebonaldo.com