Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin as scripting language

danybony
November 23, 2019

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
Tweet

More Decks by danybony

Other Decks in Programming

Transcript

  1. Total pictures: 1536 Covered by 17-40: 839 (54%) Covered by

    24-105: 1413 (91%) Covered by 24-70: 1094 (71%)
  2. fun main(args: Array<String>) { println("Hello World!") } $ kotlinc hello.kt

    -include-runtime -d hello.jar $ java -jar hello.jar two params Hello World!
  3. println("Called with args:") args.forEach { println("- $it") } $ kotlinc

    -script helloScript.kts hello ‘with spaces’ Called with args: - hello - with spaces
  4. import java.io.File val folders: Array<File>? = File(".").listFiles { file ->

    file.isDirectory } folders?.forEach { folder -> println(folder) } $ kotlinc -script dirsExplore.kts ./dir2 ./dir1
  5. 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
  6. 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
  7. kscript - input modes $ kscript helloScript.kts $ kscript https://gist.githubusercontent.com/danybony/.../kscriptUrl.kts

    $ kscript ‘println(“Hello there”)’ $ echo ‘ println("Hello Kotlin.") ' | kscript -
  8. #!/usr/bin/env kscript println(“Hello!") for (arg in args) { println("arg: $arg")

    } $ chmod +x interpreter.kts $ ./interpreter.kts world Hello! arg: world
  9. 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
  10. kscript - includes fun String.upperCase() { ... } //INCLUDE utils.kt

    @file:Include("util.kt") val shouted = "hello!".upperCase() script.kts utils.kts
  11. 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
  12. kscript - deploy as binaries $ kscript --package helloScript.kts [kscript]

    Packaging script 'helloScript' into standalone executable... $ ./helloScript Hello world!
  13. 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()}%)") }
  14. 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()}
  15. 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()}
  16. #!/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
  17. 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
  18. 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%)
  19. Conclusions • Kotlin script support • Good for quick prototyping

    • Kotlin language power • Limitations • kscript • Only for *nix-based OS • Powerful • Efficient