├── .gitignore ├── .idea ├── detekt.xml ├── kotlinc.xml ├── ktfmt.xml ├── misc.xml └── vcs.xml ├── 0.hello.main.kts ├── 1.shell-commands.main.kts ├── 2.files-csv.main.kts ├── 2.files-json.main.kts ├── 2.files.main.kts ├── 3.input-from-shell.main.kts ├── 4.coroutines.main.kts ├── 5.pluck-tags-from-blog-posts.main.kts ├── 6.tootbot.main.kts ├── 7.carrot-wars-tabulate.main.kts ├── 8.embed-external-scripts.main.kts ├── 9.curl-vs-okhttp-api-system-env-json-parse-gitlab.main.kts ├── LICENSE ├── README.md ├── common.main.kts ├── feed.json ├── star-wars-demo-results.csv ├── toots.csv └── x.test.main.kts /.gitignore: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | ### macOS template 3 | 4 | # General 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | ############################################################################## 32 | ### Windows template 33 | 34 | # Windows thumbnail cache files 35 | Thumbs.db 36 | Thumbs.db:encryptable 37 | ehthumbs.db 38 | ehthumbs_vista.db 39 | 40 | # Dump file 41 | *.stackdump 42 | 43 | # Folder config file 44 | [Dd]esktop.ini 45 | 46 | # Recycle Bin used on file shares 47 | $RECYCLE.BIN/ 48 | 49 | # Windows Installer files 50 | *.cab 51 | *.msi 52 | *.msix 53 | *.msm 54 | *.msp 55 | 56 | # Windows shortcuts 57 | *.lnk 58 | 59 | ############################################################################## 60 | ### Linux template 61 | *~ 62 | 63 | # temporary files which can be created if a process still has a handle open of a deleted file 64 | .fuse_hidden* 65 | 66 | # KDE directory preferences 67 | .directory 68 | 69 | # Linux trash folder which might appear on any partition or disk 70 | .Trash-* 71 | 72 | # .nfs files are created when an open file is removed but is still being accessed 73 | .nfs* 74 | 75 | ############################################################################## 76 | ### Generic Files (across platforms) 77 | 78 | # Log file 79 | *.log 80 | *.log.txt 81 | 82 | # CONVENIENCE 83 | *.ignore 84 | 85 | ############################################################################## 86 | ### Visual Studio template 87 | 88 | # Visual studio project files 89 | *.vcxproj 90 | *.vcxproj.filters 91 | *.sln 92 | 93 | # Visual Studio Code files 94 | .vscode 95 | 96 | ############################################################################## 97 | ### JetBrains template 98 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 99 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 100 | 101 | *.iml 102 | *.ipr 103 | *.iws 104 | /.idea/* 105 | 106 | # Don't gitignore project specific files 107 | # ref: https://www.jetbrains.com/help/idea/directories-used-by-the-ide-to-store-settings-caches-plugins-and-logs.html#system-directory 108 | 109 | !/.idea/copyright/ 110 | !/.idea/encodings.xml 111 | !/.idea/fileTemplates/ 112 | !/.idea/inspectionProfiles/ 113 | !/.idea/scopes/ 114 | !/.idea/vcs.xml 115 | 116 | # customized code style schemes 117 | !/.idea/codeStyles/ 118 | 119 | # Intellij Project Icon 120 | !.idea/icon.svg 121 | 122 | # Intellij Project Name 123 | !.idea/.name 124 | 125 | # contains compiler special arguments (like -Xallow-any-scripts-in-source-roots) for kotlin 1.9 126 | !.idea/kotlinc.xml 127 | 128 | # project jdk config 129 | !.idea/misc.xml 130 | 131 | ############################################################################## 132 | ### Gradle template 133 | 134 | .gradle 135 | .gradle/ 136 | # Note that you may need to exclude by hand other folders 137 | # named build if necessary (e.g., in src/) 138 | /build/ 139 | **/build/ 140 | !src/**/build/ 141 | 142 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 143 | !gradle/wrapper/gradle-wrapper.jar 144 | 145 | # Cache of project 146 | .gradletasknamecache 147 | 148 | # Local configuration file (sdk path, etc) 149 | local.properties 150 | 151 | ############################################################################## 152 | ### Bazel template 153 | /bazel-out 154 | /bazel-bin 155 | /bazel-caper-repo 156 | /bazel-testlogs 157 | /bazel-*monorepo* 158 | .ijwb 159 | .clwb 160 | .cache 161 | .bazelrc-ci 162 | .bazelrc-user 163 | 164 | ############################################################################## 165 | ### Node modules npm 166 | 167 | node_modules/ 168 | 169 | ############################################################################## 170 | ### C++ template 171 | 172 | # Compiled class file 173 | *.cxx 174 | 175 | # Logs 176 | CMakeFiles/*-log.txt 177 | 178 | ############################################################################## 179 | ### Python template 180 | 181 | venv/ 182 | 183 | ############################################################################## 184 | ### Kotlin template 185 | 186 | # Compiled class file 187 | *.class 188 | 189 | # Package Files # 190 | *.jar 191 | *.war 192 | *.nar 193 | *.ear 194 | *.zip 195 | *.tar.gz 196 | *.rar 197 | 198 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 199 | hs_err_pid* 200 | 201 | ############################################################################## 202 | ### Android-specific stuff 203 | 204 | # Built application files 205 | *.apk 206 | *.ap_ 207 | *.aab 208 | *.apks 209 | 210 | # Files for the Dalvik VM 211 | *.dex 212 | 213 | # Generated files 214 | bin/ 215 | gen/ 216 | obj/ 217 | 218 | # Google/Firebase secrets 219 | google-services.json 220 | 221 | # Android Studio generated files and folders 222 | captures/ 223 | .externalNativeBuild/ 224 | .cxx/ 225 | output.json 226 | 227 | # Keystore files 228 | *.jks 229 | *.keystore 230 | 231 | # Android Profiling 232 | *.hprof 233 | 234 | ############################################################################## 235 | ### SECRETS 236 | 237 | ############################################################################## 238 | ### Project Specific 239 | 240 | ## Jetbrains specific 241 | 242 | # controls plugin dependencies (like ktmfmt/detekt) 243 | !/.idea/externalDependencies.xml 244 | # Plugins used in project 245 | !.idea/detekt.xml 246 | !.idea/ktfmt.xml 247 | 248 | # to start clean, remove the ".default" suffix in these xmls 249 | # and replace your existing ones with them 250 | 251 | # List of gradle projects supported (right tab) 252 | !/.idea/gradle.default.xml 253 | 254 | # List of disabled projects (loading too many will crash Intellij) 255 | !/.idea/compiler.default.xml 256 | 257 | # Controls Project View (left tab) 258 | !/.idea/modules.default.xml 259 | -------------------------------------------------------------------------------- /.idea/detekt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/ktfmt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /0.hello.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | /* 4 | * This script is a starter template script. 5 | * It takes in whatever you send as arguments and prints them out. 6 | * 7 | * run this script like so: 8 | 9 | $> brew install kotlin 10 | $> kotlin 0.hello.main.kts Hello 🌎 11 | * 12 | * alternatively if you want to make the script executable 13 | 14 | $> chmod +x 0.hello.main.kts 15 | $> ./0.hello.main.kts Hello 🌎 16 | */ 17 | 18 | println("Hello from Kotlin Scripts!") 19 | println("******* PROGRAM START ***************** ") 20 | program(args) 21 | println("******* PROGRAM END ***************** ") 22 | 23 | 24 | fun program(args: Array) { 25 | println("\uD83D\uDEE0️ This program received the following arguments: ${args.joinToString()} ") 26 | } 27 | -------------------------------------------------------------------------------- /1.shell-commands.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | import java.io.BufferedReader 3 | import java.util.concurrent.TimeUnit 4 | import kotlin.system.exitProcess 5 | 6 | /* 7 | * This script 8 | * 9 | * To run kscripts do it like so: 10 | $> brew install kotlin 11 | $> kotlin .main.kts 12 | * 13 | * Alternatively, make this script executable 14 | $> chmod +x .main.kts 15 | $> ./.main.kts 16 | * 17 | * Read more about this at https://kau.sh/blog/kscript 18 | */ 19 | 20 | val ANSI_RESET = "\u001B[0m" 21 | val ANSI_GRAY = "\u001B[90m" // use this mostly 22 | val ANSI_PURPLE = "\u001B[35m" // input commands 23 | val ANSI_GREEN = "\u001B[32m" // highlighting values 24 | val ANSI_RED = "\u001B[31m" // error 25 | val ANSI_YELLOW = "\u001B[33m" // important messages 26 | //val ANSI_BLUE = "\u001B[34m" 27 | //val ANSI_CYAN = "\u001B[36m" 28 | //val ANSI_WHITE = "\u001B[37m" 29 | 30 | val DEBUG = true 31 | 32 | if (DEBUG) println("$ANSI_GRAY ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ PROGRAM START$ANSI_RESET") 33 | program(args) 34 | if (DEBUG) println("$ANSI_GRAY ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ PROGRAM END $ANSI_RESET") 35 | 36 | fun program(args: Array) { 37 | 38 | if (DEBUG) println("$ANSI_GRAY[args]$ANSI_GREEN${args.joinToString()}$ANSI_RESET") 39 | 40 | // notice use of unix shell commands 41 | "find scratch.txt".exec() 42 | "rm -rf scratch.txt".exec() 43 | "find scratch.txt".exec() 44 | "touch scratch.txt".exec() 45 | 46 | // find all files ending with ".main.kts" 47 | // in this directory recursively 48 | // directory path must contain "res/values" 49 | // split the output by newline and filename in a list 50 | val fileList = 51 | "find . -type f -path '*.main.kts'" 52 | .exec() 53 | .split("\n") 54 | .filter { it.isNotBlank() } 55 | 56 | println("$ANSI_GRAY Found the following files: $ANSI_RESET") 57 | fileList.forEach { 58 | println("$ANSI_GREEN - $it $ANSI_RESET") 59 | } 60 | 61 | } 62 | 63 | 64 | fun String.exec( 65 | exitOnError: Boolean = false, 66 | verbose: Boolean = true, 67 | timeoutInMinutes: Long = 3, 68 | ): String { 69 | if (verbose) println("${ANSI_GRAY}[command] $this ${ANSI_RESET}") 70 | val process = 71 | ProcessBuilder() 72 | // .directory(workingDirectory) 73 | .redirectErrorStream(true) 74 | .redirectOutput(ProcessBuilder.Redirect.PIPE) 75 | .redirectError(ProcessBuilder.Redirect.PIPE) 76 | // /bin/bash -c -l necessary to use programs like "find" 77 | // from your real user shell environment 78 | .command("/bin/bash", "-c", "-l", this) 79 | .start() 80 | 81 | process.waitFor(timeoutInMinutes, TimeUnit.MINUTES) 82 | return process.retrieveOutput(exitOnError, verbose) 83 | } 84 | 85 | private fun Process.retrieveOutput(exitOnError: Boolean, verbose: Boolean): String { 86 | val outputText = inputStream.bufferedReader().use(BufferedReader::readText) 87 | val exitCode = exitValue() 88 | 89 | val color: String 90 | val sign: String 91 | when { 92 | exitOnError -> { 93 | color = ANSI_RED 94 | sign = "✗ | err: $exitCode" 95 | } 96 | exitCode != 0 -> { 97 | color = ANSI_YELLOW 98 | sign = "⚠️ | err: $exitCode" 99 | } 100 | else -> { 101 | color = ANSI_GRAY 102 | sign = "" 103 | } 104 | } 105 | 106 | if (verbose || exitCode != 0) { 107 | println( 108 | """$color[output] $sign: 109 | ${outputText.trim()} 110 | $ANSI_RESET""", 111 | ) 112 | } 113 | 114 | if (exitOnError) { 115 | println("${ANSI_RED}✗ Exiting... $ANSI_RESET") 116 | exitProcess(1) 117 | } 118 | return outputText.trim() 119 | } 120 | -------------------------------------------------------------------------------- /2.files-csv.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:DependsOn("com.squareup.okio:okio:3.3.0") 4 | 5 | import okio.FileSystem 6 | import okio.IOException 7 | import okio.Path 8 | import okio.Path.Companion.toPath 9 | 10 | /* 11 | * This script shows you how you can read a csv file. 12 | * run this script like so: 13 | 14 | $> brew install kotlin 15 | $> chmod +x 2.csv.main.kts 16 | $> ./2.csv.main.kts 17 | */ 18 | 19 | println("\uD83D\uDEE0️ ****** Reading from CSV File ****** \uD83D\uDEE0") 20 | 21 | val filePath = "star-wars-demo-results.csv".toPath() 22 | 23 | println(" --- Start of file --- 🏁") 24 | forEachLine(filePath) { line -> 25 | println(line) 26 | } 27 | println("--- End of file --- ✅") 28 | 29 | @Throws(IOException::class) 30 | fun forEachLine(path: Path, lineAction: (String) -> Unit) { 31 | FileSystem.SYSTEM.read(path) { 32 | while (true) { 33 | val line = readUtf8Line() ?: break 34 | lineAction.invoke(line) 35 | } 36 | } 37 | } 38 | 39 | 40 | 41 | println("\uD83D\uDEE0️ ****** Writing to a CSV File ****** \uD83D\uDEE0") 42 | 43 | val tootsFile = "./toots.csv".toPath() 44 | var tooted = mutableListOf() 45 | 46 | println(" --- ☝\uD83C\uDFFD Reading from another existing CSV file ") 47 | forEachLine(tootsFile) { line -> 48 | val (path, tootId) = line.split(',', ignoreCase = false, limit = 2) 49 | tooted.add(Tooted(path, tootId)) 50 | } 51 | println("existing CSV file has ${tooted.count()} toots/lines") 52 | 53 | println("--- Writing back to the CSV file --- ✅") 54 | FileSystem.SYSTEM.write(tootsFile) { 55 | tooted.forEach { tooted -> 56 | writeUtf8(tooted.postId) 57 | writeUtf8(",") 58 | writeUtf8(tooted.tootId) 59 | writeUtf8("\n") 60 | 61 | // simply write into it again (so you can see the change in file) 62 | writeUtf8(tooted.postId) 63 | writeUtf8(",") 64 | writeUtf8(tooted.tootId) 65 | writeUtf8("\n") 66 | } 67 | } 68 | 69 | data class Tooted( 70 | val postId: String, 71 | val tootId: String, 72 | ) 73 | -------------------------------------------------------------------------------- /2.files-json.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:Repository("https://repo.maven.apache.org/maven2/") 4 | @file:DependsOn("com.squareup.okhttp3:okhttp:4.10.0") 5 | @file:DependsOn("com.squareup.okio:okio:3.9.0") 6 | @file:DependsOn("com.squareup.moshi:moshi-kotlin:1.15.1") 7 | @file:DependsOn("com.squareup.moshi:moshi-adapters:1.13.0") 8 | @file:DependsOn("dev.zacsweers.moshix:moshi-metadata-reflect:0.27.1") 9 | 10 | import com.squareup.moshi.Json 11 | import com.squareup.moshi.Moshi 12 | import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter 13 | import dev.zacsweers.moshix.reflect.MetadataKotlinJsonAdapterFactory 14 | import okhttp3.OkHttpClient 15 | import okio.FileSystem 16 | import okio.Path.Companion.toPath 17 | import okio.buffer 18 | import java.util.* 19 | import okhttp3.Request 20 | 21 | /* 22 | * This script shows you how you can read a json file 23 | * then parse it into a Kotlin data classes. 24 | * It also shows you how to download a json file from the web 25 | * 26 | * run this script like so: 27 | 28 | $> brew install kotlin 29 | $> chmod +x 3.json.main.kts 30 | $> ./3.json.main.kts 31 | */ 32 | 33 | 34 | /* 35 | * **************************** 36 | * Read from local json file 37 | * **************************** 38 | */ 39 | val jsonFeedFilePath = "./feed.json".toPath() 40 | 41 | println("\uD83D\uDEE0️ ****** Load json file from \uD83D\uDCBE ****** \uD83D\uDEE0") 42 | val jsonFile: String = FileSystem.SYSTEM 43 | .source(jsonFeedFilePath) 44 | .buffer() 45 | .readUtf8() 46 | 47 | val moshi: Moshi = Moshi.Builder() 48 | .add(MetadataKotlinJsonAdapterFactory()) 49 | .add(Date::class.java, Rfc3339DateJsonAdapter()) 50 | .build() 51 | 52 | var feed: Feed = moshi 53 | .adapter(Feed::class.java) 54 | .fromJson(jsonFile) as Feed 55 | 56 | println("🤖 local feed has ${feed.pages.count()} pages") 57 | 58 | /* 59 | * **************************** 60 | * Download RSS json file 61 | * **************************** 62 | */ 63 | val jsonFeedUrl = "https://kau.sh/blog/feed.json" 64 | 65 | println("\uD83D\uDEE0️ ****** Download blog feed json file from \uD83C\uDF0D ****** \uD83D\uDEE0") 66 | 67 | OkHttpClient() 68 | .newCall( 69 | Request.Builder() 70 | .url(jsonFeedUrl) 71 | .build(), 72 | ) 73 | .execute() 74 | .use { response -> 75 | feed = moshi 76 | .adapter(Feed::class.java) 77 | .fromJson(response.body!!.source()) as Feed 78 | } 79 | 80 | println("🤖 remote found ${feed.pages.count()} pages") 81 | 82 | data class Page( 83 | val title: String, 84 | @Json(name = "date_published") 85 | val publishedDate: Date, 86 | @Json(name = "file_path") 87 | val filePath: String?, 88 | val description: String?, 89 | ) 90 | 91 | data class Feed( 92 | val title: String, 93 | val description: String, 94 | @Json(name = "items") 95 | val pages: List 96 | ) 97 | 98 | -------------------------------------------------------------------------------- /2.files.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:DependsOn("com.squareup.okio:okio:3.5.0") 4 | 5 | import okio.BufferedSink 6 | import okio.FileSystem 7 | import okio.Path 8 | import okio.Path.Companion.toOkioPath 9 | import okio.Path.Companion.toPath 10 | import okio.appendingSink 11 | import okio.buffer 12 | import okio.source 13 | import java.io.File 14 | 15 | 16 | /* 17 | * This script shows how to deal with files in kotlin scripts. 18 | 19 | * In this file you'll see how to: 20 | * 1. read each line from a file 21 | * 2. write to a file 22 | * 3. download a file from the web 23 | * 24 | * To run kscripts do it like so: 25 | $> brew install kotlin 26 | $> kotlin .main.kts 27 | * 28 | * Alternatively, make this script executable 29 | $> chmod +x .main.kts 30 | $> ./.main.kts 31 | * 32 | * Read more about this at https://kau.sh/blog/kscript 33 | */ 34 | 35 | val ANSI_RESET = "\u001B[0m" 36 | val ANSI_GRAY = "\u001B[90m" // use this mostly 37 | val ANSI_PURPLE = "\u001B[35m" // input commands 38 | val ANSI_GREEN = "\u001B[32m" // highlighting values 39 | val ANSI_RED = "\u001B[31m" // error 40 | val ANSI_YELLOW = "\u001B[33m" // important messages 41 | // val ANSI_BLUE = "\u001B[34m" 42 | // val ANSI_CYAN = "\u001B[36m" 43 | // val ANSI_WHITE = "\u001B[37m" 44 | 45 | val DEBUG = true 46 | 47 | if (DEBUG) println("$ANSI_GRAY ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ PROGRAM START$ANSI_RESET") 48 | program(args) 49 | if (DEBUG) println("$ANSI_GRAY ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ PROGRAM END $ANSI_RESET") 50 | 51 | fun program(args: Array) { 52 | if (DEBUG) println("$ANSI_GRAY[args]$ANSI_GREEN${args.joinToString()}$ANSI_RESET") 53 | 54 | readEachLine() 55 | println("\n\n") 56 | createFileWithContent() 57 | println("\n\n") 58 | appendContentToFile() 59 | 60 | } 61 | 62 | fun readEachLine() { 63 | println("${ANSI_GRAY}🚧 printing first 5 lines of feed.json $ANSI_RESET") 64 | // print the first 10 lines of a file 65 | forEachLine("feed.json".toPath()) { text: String, index: Int -> 66 | // if (index !in 1424..1505) return@forEachLine 67 | if(index > 10) return@forEachLine 68 | println("$ANSI_GRAY Line $index: $ANSI_GREEN $text $ANSI_RESET") 69 | } 70 | } 71 | 72 | fun createFileWithContent() { 73 | val filename = "scratch.txt" 74 | val file = File(filename) 75 | 76 | if (file.exists()) { 77 | println("${ANSI_GRAY}🚧 $file already exists. going to delete it $ANSI_RESET") 78 | file.delete() 79 | } 80 | 81 | println("${ANSI_GRAY}🚧 creating $file $ANSI_RESET") 82 | file.createNewFile() 83 | 84 | writeToFile(file.toOkioPath()) { sink -> 85 | sink.writeUtf8("---- head of file ---- \n") 86 | sink.writeUtf8("... riveting content\n") 87 | sink.writeUtf8("---- tail of file ---- \n") 88 | } 89 | 90 | println("$ANSI_YELLOW ✓ open $file to read some riveting content $ANSI_RESET") 91 | } 92 | 93 | 94 | fun appendContentToFile() { 95 | val filename = "scratch.txt" 96 | val file = File(filename) 97 | file.appendingSink().buffer().use { 98 | it.writeUtf8("*** appended content ***") 99 | } 100 | println("$ANSI_YELLOW ✓ appended content to $file $ANSI_RESET") 101 | } 102 | 103 | /** 104 | * Okio recipe to read a file line by line 105 | * https://square.github.io/okio/recipes/#read-a-text-file-line-by-line-javakotlin 106 | */ 107 | fun forEachLine(path: Path, lineAction: (String, Int) -> Unit) { 108 | var index: Int = 0 109 | FileSystem.SYSTEM.read(path) { 110 | loop@ while (true) { 111 | val line = readUtf8Line() ?: break 112 | lineAction.invoke(line, index) 113 | index += 1 114 | } 115 | } 116 | } 117 | 118 | /** 119 | * Okio recipe to write to a file 120 | * https://square.github.io/okio/recipes/#read-a-text-file-line-by-line-javakotlin 121 | */ 122 | fun writeToFile(path: Path, writingSink: (BufferedSink) -> Unit) { 123 | FileSystem.SYSTEM.write(path) { writingSink.invoke(this) } 124 | } 125 | -------------------------------------------------------------------------------- /3.input-from-shell.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:Repository("https://jitpack.io") 4 | @file:DependsOn("com.github.kaushikgopal:shell.main.kts:276950a346") 5 | 6 | import sh.kau.shell.ShellConsole.Companion.ANSI_GRAY 7 | import sh.kau.shell.ShellConsole.Companion.ANSI_GREEN 8 | import sh.kau.shell.ShellConsole.Companion.ANSI_PURPLE 9 | import sh.kau.shell.ShellConsole.Companion.ANSI_RESET 10 | import sh.kau.shell.ShellConsole.Companion.ANSI_YELLOW 11 | import sh.kau.shell.runInShell 12 | 13 | /* 14 | * This script demonstrates how you can take inputs from the user 15 | * through an interactive way by prompting users for inputs step by step. 16 | * 17 | * There's a few concepts being demonstrated here: 18 | * 1. how to print things in color 19 | * 2. how to take user inputs from the command line 20 | * 3. how to execute shell commands from within a kotlin script 21 | * 22 | * To run kscripts do it like so: 23 | $> brew install kotlin 24 | $> kotlin 3.input-from-shell.main.kts 25 | * 26 | * Alternatively, make this script executable 27 | $> chmod +x .main.kts 28 | $> ./.main.kts 29 | * 30 | * Read more about this at https://kau.sh/blog/kscript 31 | */ 32 | 33 | println("$ANSI_GRAY ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ PROGRAM START$ANSI_RESET") 34 | program(args) 35 | println("$ANSI_GRAY ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ PROGRAM END $ANSI_RESET") 36 | 37 | fun program(args: Array) { 38 | println("$ANSI_GRAY[args]$ANSI_GREEN${args.joinToString()}$ANSI_RESET") 39 | 40 | // simple command - taking an input 41 | // print it back out 42 | 43 | print("\n\n$ANSI_PURPLE Enter your name: $ANSI_RESET") 44 | val name = readlnOrNull() 45 | println("\n\n$ANSI_GRAY Why hello $ANSI_YELLOW $name! $ANSI_RESET\n\n\n") 46 | 47 | 48 | // take a domain name 49 | // open social media links such as http://threads. 50 | 51 | print("Do you have a domain name? (y/N): ") 52 | val hasDomain = readlnOrNull() 53 | 54 | var domain: String? = null 55 | if (hasDomain == "y") { 56 | print("What is your domain name: ") 57 | domain = readlnOrNull() 58 | } 59 | 60 | domain = domain ?: "kau.sh" 61 | 62 | println("${ANSI_GRAY}🚧 Opening all the social profiles now!$ANSI_RESET") 63 | 64 | // execute a shell command 65 | listOf( 66 | "open https://threads.$domain", 67 | "open https://github.$domain", 68 | "open https://twitter.$domain", 69 | "open https://mastodon.$domain", 70 | "open https://instagram.$domain" 71 | ).forEach { it.runInShell() } 72 | } 73 | -------------------------------------------------------------------------------- /4.coroutines.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:Repository("https://repo.maven.apache.org/maven2/") 4 | @file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2") 5 | @file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2") 6 | 7 | import kotlin.system.measureTimeMillis 8 | import kotlinx.coroutines.* 9 | 10 | 11 | /* 12 | * This script shows you how you can use coroutines to do async programming 13 | * 14 | * run this script like so: 15 | 16 | $> brew install kotlin 17 | $> chmod +x 4.coroutines.main.kts 18 | $> ./4.coroutines.main.kts 19 | */ 20 | 21 | //main() 22 | fun main() = runBlocking { 23 | println("------ first program ") 24 | println("------ two coroutines run in series") 25 | 26 | val time = measureTimeMillis { 27 | val one = doOne() 28 | val two = doTwo() 29 | println("One and Two is ${one + two}") 30 | } 31 | 32 | println("finished in $time") 33 | } 34 | 35 | 36 | 37 | //main2() 38 | fun main2() = runBlocking { 39 | println("------ second program ") 40 | println("------ multiple coroutines non-waiting") 41 | 42 | launch { 43 | delay(200L) 44 | println("---- A") 45 | } 46 | 47 | coroutineScope { 48 | launch { 49 | delay(500L) 50 | println("---- B") 51 | } 52 | 53 | delay(100L) 54 | println("---- C") 55 | } 56 | 57 | println("---- D") 58 | } 59 | 60 | 61 | 62 | //main3() 63 | fun main3() = runBlocking { 64 | println("\n\n\n\n------ next program ") 65 | println("------ 100s of coroutines") 66 | 67 | // these will "wait" on the next one 68 | launch { 69 | println("\n\n") 70 | 71 | repeat(1_00) { 72 | print("$it ") 73 | delay(150L) 74 | } 75 | } 76 | 77 | // these will all run in "parallel" 78 | repeat(1_00) { 79 | println("\n\n") 80 | launch { 81 | delay(100L) 82 | print("-${it}-") 83 | } 84 | } 85 | 86 | } 87 | 88 | suspend fun doOne(): Int { 89 | delay(500L) 90 | return 13 91 | } 92 | 93 | suspend fun doTwo(): Int { 94 | delay(1000L) 95 | return 29 96 | } 97 | 98 | 99 | 100 | main4() 101 | 102 | fun main4() = runBlocking { 103 | println("\n\n\n\n------ next program ") 104 | println("------ coroutines sequencing") 105 | 106 | launch { 107 | println("This is executed before the first delay <1> [${Thread.currentThread().name}]") 108 | println("This is executed before the first delay <2> [${Thread.currentThread().name}]") 109 | stallForTime() 110 | println("This is executed after the first delay [${Thread.currentThread().name}]") 111 | } 112 | 113 | launch { 114 | println("This is executed before the second delay [${Thread.currentThread().name}]") 115 | stallForTime() 116 | println("This is executed after the second delay [${Thread.currentThread().name}]") 117 | } 118 | 119 | println("This is executed immediately [${Thread.currentThread().name}]") 120 | } 121 | 122 | suspend fun stallForTime() { 123 | println("before delay [${Thread.currentThread().name}]") 124 | // withContext(Dispatchers.IO) { 125 | // println("delaying on [${Thread.currentThread().name}]") 126 | delay(4000L) 127 | // } 128 | } 129 | -------------------------------------------------------------------------------- /5.pluck-tags-from-blog-posts.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | /* 4 | * 5 | * This script goes through a specified folder 6 | * searches through all blog posts (markdown files) 7 | * plucks out the tags/categories from the front-matter of each post (should be in the "tags: []" format) 8 | * then prints out a list of all the tags/categories 9 | * 10 | * run this script like so: 11 | $> brew install kotlin 12 | $> kotlin 5.pluck-tags-from-blog-posts.main.kts ~/kau.sh/content/blog tags 13 | $> kotlin 5.pluck-tags-from-blog-posts.main.kts ~/kau.sh/content/blog categories 14 | * 15 | * I live coded another version of this script using Github Copilot: 16 | * You can read more [about it here](https://kau.sh/kscript-copilot/) 17 | * or watch the [youtube video here](https://www.youtube.com/watch?v=Tr2YBmecdw4) 18 | */ 19 | 20 | import java.io.File 21 | 22 | 23 | println("******* PROGRAM START ***************** ") 24 | program(args) 25 | println("******* PROGRAM END ***************** ") 26 | 27 | fun program(args: Array) { 28 | 29 | if (args.isEmpty()) { 30 | println(" ⚠️ ERROR: a path is required ") 31 | println(" You need to provide two arguments to this script") 32 | println(" 1. path") 33 | println(" 2. tags, categories or duplicates") 34 | println(" duplicates will print out tags that are also categories") 35 | return 36 | } 37 | 38 | // return if we can't find atleast one .md file 39 | // also check any subdirectories 40 | val files = File(args[0]).walkTopDown().filter { file -> file.name.endsWith(".md") }.toList() 41 | if (files.isEmpty()) { 42 | println(" ⚠️ ERROR: no markdown files found ") 43 | return 44 | } 45 | 46 | println(" ℹ️ found ${files.size} markdown files to process") 47 | 48 | // return if second argument is not present 49 | if (args.size < 2) { 50 | println(" ⚠️ ERROR: second argument is required") 51 | return 52 | } 53 | 54 | val items: List = when (args[1]) { 55 | "tags", "categories" -> { 56 | collectTagsOrCategories(files, args[1]) 57 | } 58 | 59 | "duplicates", "duplicate" -> { 60 | findDuplicates(files) 61 | } 62 | 63 | else -> { 64 | println(" ⚠️ ERROR: second argument must be either 'tags', 'categories' or 'duplicate(s)'") 65 | return 66 | } 67 | } 68 | 69 | justifiedPrint(items) 70 | } 71 | 72 | fun collectTagsOrCategories(files: List, tagOrCategorySearch: String): List { 73 | // read every line of file and pluck the line that has "tags: [" in it 74 | return files 75 | .map { file -> 76 | file.readLines() 77 | .filter { it.contains("${tagOrCategorySearch}: [") } 78 | .map { it.replace("${tagOrCategorySearch}: [", "").replace("]", "") } 79 | .map { it.split(",") } 80 | .flatten() 81 | .map { it.trim() } 82 | .filter { it.isNotEmpty() } 83 | } 84 | .flatten() 85 | .distinct() 86 | .sorted() 87 | } 88 | 89 | // This function collects all categories and tags from all files 90 | // then prints out the words that are both categories and tags 91 | fun findDuplicates(files: List): List { 92 | val tags = collectTagsOrCategories(files, "tags") 93 | val categories = collectTagsOrCategories(files, "categories") 94 | return tags.filter { categories.contains(it) } 95 | } 96 | 97 | /** 98 | * This function takes a list of strings and 99 | * prints them out in a justified format 100 | */ 101 | fun justifiedPrint(words: List) { 102 | if(words.isEmpty()) { 103 | println(" \uD83E\uDEB9️ nothing to print") 104 | return 105 | } 106 | 107 | val maxWordLength = words.maxBy { it.length }.length ?: 0 108 | val maxWordsPerLine = 80 / (maxWordLength + 2) 109 | 110 | var line = "" 111 | words.forEach { word -> 112 | if (line.length + word.length + 2 > 80) { 113 | println(line) 114 | line = "" 115 | } 116 | line += word.padEnd(maxWordLength + 2) 117 | } 118 | println(line) 119 | } 120 | -------------------------------------------------------------------------------- /6.tootbot.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | println("moved to a separate repo - https://github.com/kaushikgopal/tootbot") 4 | -------------------------------------------------------------------------------- /7.carrot-wars-tabulate.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:DependsOn("com.squareup.okio:okio:3.3.0") 4 | 5 | import okio.FileSystem 6 | import okio.IOException 7 | import okio.Path 8 | import okio.Path.Companion.toPath 9 | import java.io.File 10 | 11 | /* 12 | * ---------------- Carrot Wars 2018 ----------------------------------- 13 | * This script was used in Instacart's 2018 Summer Hackathon 14 | * it tabulates the results from a CSV file populated by Google Forms 15 | * and outputs the results of votes 16 | * 17 | * See the [accompanying blog post](https://tech.instacart.com/free-hackathon-vote-tabulation-using-google-forms-kotlin-3c7b7080ea) for more details. 18 | * 19 | * run this script like so: 20 | 21 | $> brew install kotlin 22 | $> kotlin 7.carrot-wars-tabulate.main.kts star-wars-demo-results.csv 23 | */ 24 | 25 | 26 | println("******* PROGRAM START ***************** ") 27 | program(args) 28 | println("******* PROGRAM END ***************** ") 29 | 30 | fun program(args: Array) { 31 | if (checkValidCsvFile(args)) return 32 | 33 | var voterCount: Int = 0 34 | var projectCsv: Map = emptyMap() 35 | var projectCsvWithResults: Map = emptyMap() 36 | 37 | val filePath = args[0].toPath() 38 | 39 | processLinesFromFile(filePath) { line, index -> 40 | line ?: return@processLinesFromFile 41 | 42 | if (index == 0) { 43 | projectCsv = processHeaderAndBuildMap(line) 44 | } else { 45 | projectCsvWithResults = processVote(projectCsv, line) 46 | voterCount += 1 47 | } 48 | } 49 | 50 | println("$voterCount folk(s) voted!") 51 | 52 | printResults(projectCsvWithResults) 53 | } 54 | // --------------------------------------------------------------------------------------------- 55 | // Internal Helpers 56 | 57 | fun printResults(projectCsvColumn: Map) { 58 | 59 | val categoryComparator: Comparator = 60 | compareBy { it.project.category } 61 | val categoryAndMaxPointsComparator: Comparator = 62 | categoryComparator.thenByDescending { it.points } 63 | 64 | val orderedResults = projectCsvColumn.values 65 | .filter { it.points > 0 } 66 | .sortedWith(categoryAndMaxPointsComparator) 67 | 68 | orderedResults 69 | .groupBy { it.project.category } 70 | .forEach { map: Map.Entry> -> 71 | println("-----------------------\nCategory: ${map.key}\n----") 72 | map.value.forEach { println(it) } 73 | } 74 | } 75 | 76 | 77 | /** 78 | * A typical row response from Google Forms looks like this: 79 | * 80 | * (HEADER) "Timestamp","Username","Best Team Collaboration [1. Project 1]","Best Team Collaboration [2. Project 2]","Best Team Collaboration [3. Project 3]","Best Team Collaboration [4. Project 4]","Best Team Collaboration [5. Project 5]","Best Team Collaboration [6. Project 6]","Best Team Collaboration [7. Project 7]","Best Team Collaboration [8. Project 8]","Best Team Collaboration [9. Project 9]","Best Team Collaboration [10. Project 10]","Best Team Collaboration [11. Project 11]","Best Team Collaboration [12. Project 12]","Best Team Collaboration [13. Project 13]","Best Team Collaboration [14. Project 14]","Best Team Collaboration [15. Project 15]","Best Team Collaboration [16. Project 16]","Best Team Collaboration [17. Project 17]","Best Team Collaboration [18. Project 18]","Best Team Collaboration [19. Project 19]","Best Team Collaboration [20. Project 20]","Best Team Collaboration [21. Project 21]","Best Team Collaboration [22. Project 22]","Best Team Collaboration [23. Project 23]","Best Team Collaboration [24. Project 24]","Best Team Collaboration [25. Project 25]","Fixed most annoying longstanding issue [1. Project 1]","Fixed most annoying longstanding issue [2. Project 2]","Fixed most annoying longstanding issue [3. Project 3]","Fixed most annoying longstanding issue [4. Project 4]","Fixed most annoying longstanding issue [5. Project 5]","Fixed most annoying longstanding issue [6. Project 6]","Fixed most annoying longstanding issue [7. Project 7]","Fixed most annoying longstanding issue [8. Project 8]","Fixed most annoying longstanding issue [9. Project 9]","Fixed most annoying longstanding issue [10. Project 10]","Fixed most annoying longstanding issue [11. Project 11]","Fixed most annoying longstanding issue [12. Project 12]","Fixed most annoying longstanding issue [13. Project 13]","Fixed most annoying longstanding issue [14. Project 14]","Fixed most annoying longstanding issue [15. Project 15]","Fixed most annoying longstanding issue [16. Project 16]","Fixed most annoying longstanding issue [17. Project 17]","Fixed most annoying longstanding issue [18. Project 18]","Fixed most annoying longstanding issue [19. Project 19]","Fixed most annoying longstanding issue [20. Project 20]","Fixed most annoying longstanding issue [21. Project 21]","Fixed most annoying longstanding issue [22. Project 22]","Fixed most annoying longstanding issue [23. Project 23]","Fixed most annoying longstanding issue [24. Project 24]","Fixed most annoying longstanding issue [25. Project 25]","Greatest business impact [1. Project 1]","Greatest business impact [2. Project 2]","Greatest business impact [3. Project 3]","Greatest business impact [4. Project 4]","Greatest business impact [5. Project 5]","Greatest business impact [6. Project 6]","Greatest business impact [7. Project 7]","Greatest business impact [8. Project 8]","Greatest business impact [9. Project 9]","Greatest business impact [10. Project 10]","Greatest business impact [11. Project 11]","Greatest business impact [12. Project 12]","Greatest business impact [13. Project 13]","Greatest business impact [14. Project 14]","Greatest business impact [15. Project 15]","Greatest business impact [16. Project 16]","Greatest business impact [17. Project 17]","Greatest business impact [18. Project 18]","Greatest business impact [19. Project 19]","Greatest business impact [20. Project 20]","Greatest business impact [21. Project 21]","Greatest business impact [22. Project 22]","Greatest business impact [23. Project 23]","Greatest business impact [24. Project 24]","Greatest business impact [25. Project 25]","Saves the most time [1. Project 1]","Saves the most time [2. Project 2]","Saves the most time [3. Project 3]","Saves the most time [4. Project 4]","Saves the most time [5. Project 5]","Saves the most time [6. Project 6]","Saves the most time [7. Project 7]","Saves the most time [8. Project 8]","Saves the most time [9. Project 9]","Saves the most time [10. Project 10]","Saves the most time [11. Project 11]","Saves the most time [12. Project 12]","Saves the most time [13. Project 13]","Saves the most time [14. Project 14]","Saves the most time [15. Project 15]","Saves the most time [16. Project 16]","Saves the most time [17. Project 17]","Saves the most time [18. Project 18]","Saves the most time [19. Project 19]","Saves the most time [20. Project 20]","Saves the most time [21. Project 21]","Saves the most time [22. Project 22]","Saves the most time [23. Project 23]","Saves the most time [24. Project 24]","Saves the most time [25. Project 25]","Most innovative solution [1. Project 1]","Most innovative solution [2. Project 2]","Most innovative solution [3. Project 3]","Most innovative solution [4. Project 4]","Most innovative solution [5. Project 5]","Most innovative solution [6. Project 6]","Most innovative solution [7. Project 7]","Most innovative solution [8. Project 8]","Most innovative solution [9. Project 9]","Most innovative solution [10. Project 10]","Most innovative solution [11. Project 11]","Most innovative solution [12. Project 12]","Most innovative solution [13. Project 13]","Most innovative solution [14. Project 14]","Most innovative solution [15. Project 15]","Most innovative solution [16. Project 16]","Most innovative solution [17. Project 17]","Most innovative solution [18. Project 18]","Most innovative solution [19. Project 19]","Most innovative solution [20. Project 20]","Most innovative solution [21. Project 21]","Most innovative solution [22. Project 22]","Most innovative solution [23. Project 23]","Most innovative solution [24. Project 24]","Most innovative solution [25. Project 25]" 81 | * (ROW 1) "2018/06/28 10:29:47 AM MDT","hackathon@hotmail.com","3","","","","","","","","2","","","","1","","","","","","","","","","","","","","","","","","","","","","","","","","","","","2","","1","","3","","","","","","","","","","1","","2","","","","","","","","3","","","","","","","","","","3","","","","","","","","","2","","1","","","","","","","","","","","","","","1","2","","","","","3","","","","","","","","","","","","","","","","","","" 82 | * 83 | * We go through each row 84 | * From the map position, we query the project info 85 | * We update the points standing based on the vote value (weighted) 86 | * 87 | * return a new map that has project info + summary of all points 88 | */ 89 | fun processVote( 90 | tableOfResults: Map, vote: String 91 | ): Map { 92 | 93 | val projectVoteResults: Map = tableOfResults 94 | val splitVote: List = vote.split(",") 95 | splitVote 96 | .drop(2) // not doing anything with the date 97 | .forEachIndexed loop@{ index, voteResultColumn -> 98 | 99 | val voteResultWithoutQuote = voteResultColumn.removeSurrounding("\"") 100 | if (voteResultWithoutQuote.isBlank()) return@loop 101 | val voteResult: Int = voteResultWithoutQuote.toInt() 102 | 103 | val position = index + 2 // we dropped 2 indices 104 | 105 | val projectVoteResult: HProjectVoteResult = projectVoteResults[position] 106 | ?: return@loop 107 | 108 | val project: HProject = projectVoteResult.project 109 | val voter: String = splitVote[1].removeSurrounding("\"") 110 | 111 | projectVoteResult.points += voteResult.voteWeight() 112 | 113 | projectVoteResult.voters.add(voter) 114 | projectVoteResult.votes.add(voteResult) 115 | // println("project '${project.name}' was voted '$voteResult' [${voteResult.voteWeight()}] in category '${project.category}' by '$voter'") 116 | } 117 | 118 | return projectVoteResults 119 | } 120 | 121 | /** 122 | * Process the header alone and initialize with 0 points everywhere 123 | * Google forms generates all combinations of entries 124 | * and creates a header column for all 125 | * 126 | * A typical header form the Google Forms response looks like this: 127 | * 128 | * (HEADER) "Timestamp","Username","Best Team Collaboration [1. Project 1]","Best Team Collaboration [2. Project 2]","Best Team Collaboration [3. Project 3]","Best Team Collaboration [4. Project 4]","Best Team Collaboration [5. Project 5]","Best Team Collaboration [6. Project 6]","Best Team Collaboration [7. Project 7]","Best Team Collaboration [8. Project 8]","Best Team Collaboration [9. Project 9]","Best Team Collaboration [10. Project 10]","Best Team Collaboration [11. Project 11]","Best Team Collaboration [12. Project 12]","Best Team Collaboration [13. Project 13]","Best Team Collaboration [14. Project 14]","Best Team Collaboration [15. Project 15]","Best Team Collaboration [16. Project 16]","Best Team Collaboration [17. Project 17]","Best Team Collaboration [18. Project 18]","Best Team Collaboration [19. Project 19]","Best Team Collaboration [20. Project 20]","Best Team Collaboration [21. Project 21]","Best Team Collaboration [22. Project 22]","Best Team Collaboration [23. Project 23]","Best Team Collaboration [24. Project 24]","Best Team Collaboration [25. Project 25]","Fixed most annoying longstanding issue [1. Project 1]","Fixed most annoying longstanding issue [2. Project 2]","Fixed most annoying longstanding issue [3. Project 3]","Fixed most annoying longstanding issue [4. Project 4]","Fixed most annoying longstanding issue [5. Project 5]","Fixed most annoying longstanding issue [6. Project 6]","Fixed most annoying longstanding issue [7. Project 7]","Fixed most annoying longstanding issue [8. Project 8]","Fixed most annoying longstanding issue [9. Project 9]","Fixed most annoying longstanding issue [10. Project 10]","Fixed most annoying longstanding issue [11. Project 11]","Fixed most annoying longstanding issue [12. Project 12]","Fixed most annoying longstanding issue [13. Project 13]","Fixed most annoying longstanding issue [14. Project 14]","Fixed most annoying longstanding issue [15. Project 15]","Fixed most annoying longstanding issue [16. Project 16]","Fixed most annoying longstanding issue [17. Project 17]","Fixed most annoying longstanding issue [18. Project 18]","Fixed most annoying longstanding issue [19. Project 19]","Fixed most annoying longstanding issue [20. Project 20]","Fixed most annoying longstanding issue [21. Project 21]","Fixed most annoying longstanding issue [22. Project 22]","Fixed most annoying longstanding issue [23. Project 23]","Fixed most annoying longstanding issue [24. Project 24]","Fixed most annoying longstanding issue [25. Project 25]","Greatest business impact [1. Project 1]","Greatest business impact [2. Project 2]","Greatest business impact [3. Project 3]","Greatest business impact [4. Project 4]","Greatest business impact [5. Project 5]","Greatest business impact [6. Project 6]","Greatest business impact [7. Project 7]","Greatest business impact [8. Project 8]","Greatest business impact [9. Project 9]","Greatest business impact [10. Project 10]","Greatest business impact [11. Project 11]","Greatest business impact [12. Project 12]","Greatest business impact [13. Project 13]","Greatest business impact [14. Project 14]","Greatest business impact [15. Project 15]","Greatest business impact [16. Project 16]","Greatest business impact [17. Project 17]","Greatest business impact [18. Project 18]","Greatest business impact [19. Project 19]","Greatest business impact [20. Project 20]","Greatest business impact [21. Project 21]","Greatest business impact [22. Project 22]","Greatest business impact [23. Project 23]","Greatest business impact [24. Project 24]","Greatest business impact [25. Project 25]","Saves the most time [1. Project 1]","Saves the most time [2. Project 2]","Saves the most time [3. Project 3]","Saves the most time [4. Project 4]","Saves the most time [5. Project 5]","Saves the most time [6. Project 6]","Saves the most time [7. Project 7]","Saves the most time [8. Project 8]","Saves the most time [9. Project 9]","Saves the most time [10. Project 10]","Saves the most time [11. Project 11]","Saves the most time [12. Project 12]","Saves the most time [13. Project 13]","Saves the most time [14. Project 14]","Saves the most time [15. Project 15]","Saves the most time [16. Project 16]","Saves the most time [17. Project 17]","Saves the most time [18. Project 18]","Saves the most time [19. Project 19]","Saves the most time [20. Project 20]","Saves the most time [21. Project 21]","Saves the most time [22. Project 22]","Saves the most time [23. Project 23]","Saves the most time [24. Project 24]","Saves the most time [25. Project 25]","Most innovative solution [1. Project 1]","Most innovative solution [2. Project 2]","Most innovative solution [3. Project 3]","Most innovative solution [4. Project 4]","Most innovative solution [5. Project 5]","Most innovative solution [6. Project 6]","Most innovative solution [7. Project 7]","Most innovative solution [8. Project 8]","Most innovative solution [9. Project 9]","Most innovative solution [10. Project 10]","Most innovative solution [11. Project 11]","Most innovative solution [12. Project 12]","Most innovative solution [13. Project 13]","Most innovative solution [14. Project 14]","Most innovative solution [15. Project 15]","Most innovative solution [16. Project 16]","Most innovative solution [17. Project 17]","Most innovative solution [18. Project 18]","Most innovative solution [19. Project 19]","Most innovative solution [20. Project 20]","Most innovative solution [21. Project 21]","Most innovative solution [22. Project 22]","Most innovative solution [23. Project 23]","Most innovative solution [24. Project 24]","Most innovative solution [25. Project 25]" 129 | * (ROW 1) "2018/06/28 10:29:47 AM MDT","hackathon@hotmail.com","3","","","","","","","","2","","","","1","","","","","","","","","","","","","","","","","","","","","","","","","","","","","2","","1","","3","","","","","","","","","","1","","2","","","","","","","","3","","","","","","","","","","3","","","","","","","","","2","","1","","","","","","","","","","","","","","1","2","","","","","3","","","","","","","","","","","","","","","","","","" 130 | * 131 | * We try to parse the header and build an empty map with the 132 | * position as the map's key -> when going through each row column, we'd know which project the vote was for 133 | * value -> project vote result (basically project + category + current points standing) 134 | */ 135 | fun processHeaderAndBuildMap(line: String): Map { 136 | var results: MutableMap = mutableMapOf() 137 | 138 | val splitHeader: List = line.split(",") 139 | 140 | /* 141 | println("---- Debugging Header processing") 142 | splitHeader 143 | .take(5) 144 | .forEachIndexed { index, value -> 145 | println("column ${value} is at ${index}") 146 | } 147 | */ 148 | 149 | splitHeader 150 | .drop(2) // we don't want the username & timestamp just yet; so ignore columns 151 | .forEachIndexed { index, headerColumn -> 152 | // println("column name ${headerColumn} at position ${index + 2}") 153 | results[index + 2] = HProjectVoteResult(processProjectFromColumnHeader(headerColumn)) 154 | // println("processed $projectResult") 155 | } 156 | 157 | return results 158 | } 159 | 160 | fun processProjectFromColumnHeader(headerColumn: String): HProject { 161 | val projectCategory: String = headerColumn.split("[")[0].trim() 162 | val projectName: String = headerColumn.let { 163 | it.substring(it.indexOf("[") + 1, it.indexOf("]")).trim() 164 | } 165 | return HProject(projectCategory, projectName) 166 | } 167 | 168 | fun Int.voteWeight(): Int { 169 | return when (this) { 170 | 1 -> 6 171 | 2 -> 3 172 | 3 -> 2 173 | else -> throw RuntimeException("there can be only 3") 174 | } 175 | } 176 | 177 | // --------------------------------------------------------------------------------------------- 178 | // Data structures 179 | 180 | 181 | /** 182 | * Hackathon project 183 | */ 184 | data class HProject( 185 | val category: String, 186 | val name: String 187 | ) 188 | 189 | /** 190 | * Hackathon project result row 191 | */ 192 | data class HProjectVoteResult( 193 | val project: HProject, 194 | var points: Int = 0, 195 | var voters: MutableList = arrayListOf(), 196 | var votes: MutableList = arrayListOf() 197 | ) { 198 | override fun toString(): String { 199 | return "${project.name.padEnd(50)} ----> ${points} <- ${votes}" 200 | } 201 | } 202 | 203 | fun checkValidCsvFile(args: Array): Boolean { 204 | if (args.isEmpty()) { 205 | println("⚠️ We need a csv file to proceed!") 206 | return true 207 | } 208 | 209 | // make sure one argument provided and it is a csv file 210 | val file = File(args[0]) 211 | 212 | if (!file.exists()) { 213 | println("⚠️ File ${file.absolutePath} does not exist!") 214 | return true 215 | } 216 | 217 | if (!file.isFile) { 218 | println("⚠️ File ${file.absolutePath} is not a file!") 219 | return true 220 | } 221 | if (!file.canRead()) { 222 | println("⚠️ File ${file.absolutePath} is not readable!") 223 | return true 224 | } 225 | if (!file.extension.equals("csv", ignoreCase = true)) { 226 | println("⚠️ File ${file.absolutePath} is not a CSV file!") 227 | return true 228 | } 229 | return false 230 | } 231 | 232 | @Throws(IOException::class) 233 | fun processLinesFromFile(path: Path, lineAction: (String?, Int) -> Unit) { 234 | FileSystem.SYSTEM.read(path) { 235 | var lineCount = 0 236 | while (true) { 237 | val line = readUtf8Line() ?: break 238 | lineAction.invoke(line, lineCount) 239 | lineCount += 1 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /8.embed-external-scripts.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:Import("common.main.kts") 4 | 5 | import java.io.File 6 | 7 | /* 8 | * This script demonstrates how you can import or embed external kts scripts 9 | * run this script like so: 10 | 11 | $> brew install kotlin 12 | $> kotlin 8.embed-external-scripts.main.kts 13 | * 14 | * alternatively if you want to make the script executable 15 | 16 | $> chmod +x 8.embed-external-scripts.main.kts 17 | $> ./8.embed-external-scripts.main.kts 18 | */ 19 | 20 | println("******* PROGRAM START ***************** ") 21 | program(args) 22 | println("******* PROGRAM END ***************** ") 23 | 24 | fun program(args : Array) { 25 | val person = Person(name = "Albert Einstein", age = 76) 26 | println("printing from class MyClassObject - ${person.name} is ${person.age} years old") 27 | } 28 | 29 | -------------------------------------------------------------------------------- /9.curl-vs-okhttp-api-system-env-json-parse-gitlab.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:Repository("https://repo.maven.apache.org/maven2/") @file:DependsOn("com.squareup.okhttp3:okhttp:4.10.0") @file:DependsOn( 4 | "com.squareup.okio:okio:3.9.0", 5 | ) @file:DependsOn("com.squareup.moshi:moshi-kotlin:1.15.1") @file:DependsOn("com.squareup.moshi:moshi-adapters:1.13.0") @file:DependsOn( 6 | "dev.zacsweers.moshix:moshi-metadata-reflect:0.27.1", 7 | ) 8 | 9 | @file:Repository("https://jitpack.io") @file:DependsOn("com.github.kaushikgopal:shell.main.kts:276950a346") 10 | 11 | import com.squareup.moshi.Json 12 | import com.squareup.moshi.Moshi 13 | import com.squareup.moshi.Types 14 | import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter 15 | import dev.zacsweers.moshix.reflect.MetadataKotlinJsonAdapterFactory 16 | import okhttp3.OkHttpClient 17 | import okhttp3.Request 18 | import okio.buffer 19 | import java.util.* 20 | import okio.source 21 | import java.io.File 22 | import java.io.IOException 23 | 24 | val ANSI_RESET = "\u001B[0m" 25 | val ANSI_GRAY = "\u001B[90m" // use this mostly 26 | val ANSI_PURPLE = "\u001B[35m" // input commands 27 | val ANSI_GREEN = "\u001B[32m" // highlighting values 28 | val ANSI_RED = "\u001B[31m" // error 29 | val ANSI_YELLOW = "\u001B[33m" // important messages 30 | //val ANSI_BLUE = "\u001B[34m" 31 | //val ANSI_CYAN = "\u001B[36m" 32 | //val ANSI_WHITE = "\u001B[37m" 33 | 34 | val DEBUG = true 35 | val gitlabApiToken = 36 | System.getenv("GITLAB_API_TOKEN") ?: throw IllegalStateException("PRIVATE_TOKEN must be set") 37 | 38 | val moshi: Moshi = Moshi.Builder().add(MetadataKotlinJsonAdapterFactory()) 39 | .add(Date::class.java, Rfc3339DateJsonAdapter()).build() 40 | 41 | val client = OkHttpClient() 42 | 43 | println("$ANSI_GRAY----$ANSI_RESET") 44 | program(args) 45 | println("$ANSI_GRAY----$ANSI_RESET") 46 | 47 | fun program(args: Array) { 48 | if (DEBUG) println("$ANSI_GRAY[args]$ANSI_GREEN${args.joinToString()}$ANSI_RESET") 49 | 50 | getProjects().forEach { project -> 51 | println( 52 | """ 53 | $ANSI_GRAY[${project.id}]$ANSI_GREEN ${project.name} 54 | """.trimMargin(), 55 | ) 56 | } 57 | 58 | getMergeRequests().forEach { mr -> 59 | println( 60 | """ 61 | $ANSI_GRAY[$ANSI_YELLOW${mr.id}$ANSI_GRAY]: ${ANSI_GREEN}${mr.title} - ${ANSI_GREEN}${mr.author.name} 62 | """.trimMargin(), 63 | ) 64 | } 65 | } 66 | 67 | private fun getProjects(): List { 68 | 69 | val listType = Types.newParameterizedType(List::class.java, GitlabProject::class.java) 70 | val adapter = moshi.adapter>(listType) 71 | 72 | // Command to execute the API call 73 | val command = listOf( 74 | "curl", 75 | "-H", "PRIVATE-TOKEN: $gitlabApiToken", 76 | "https://gitlab.com/api/v4/groups/caperai/projects", 77 | ) 78 | 79 | // Temporarily store output in a file 80 | val tempFile = File.createTempFile("response", ".json") 81 | 82 | val projects = try { 83 | val process = 84 | ProcessBuilder(command).redirectOutput(ProcessBuilder.Redirect.to(tempFile)).start() 85 | process.waitFor() 86 | 87 | // Read the output 88 | val source = tempFile.source().buffer() 89 | val jsonOutput = source.readUtf8() 90 | source.close() 91 | 92 | // Parse JSON to Kotlin object 93 | adapter.fromJson(jsonOutput) ?: emptyList() 94 | 95 | } catch (e: IOException) { 96 | e.printStackTrace() 97 | emptyList() 98 | } finally { 99 | // Clean up the temporary file 100 | tempFile.delete() 101 | } 102 | 103 | return projects 104 | } 105 | 106 | private fun getMergeRequests(): List { 107 | val listType = Types.newParameterizedType(List::class.java, MergeRequest::class.java) 108 | val adapter = moshi.adapter>(listType) 109 | 110 | // Build the request 111 | val request = Request.Builder() 112 | .url("https://gitlab.com/api/v4/projects/40196776/merge_requests?state=opened") 113 | .addHeader("PRIVATE-TOKEN", gitlabApiToken).build() 114 | 115 | return try { 116 | // Execute the request 117 | val response = client.newCall(request).execute() 118 | response.use { 119 | if (!it.isSuccessful) throw IOException("[${it.code}] ${it.body?.string()}") 120 | val jsonOutput = it.body!!.source() 121 | adapter.fromJson(jsonOutput) ?: emptyList() 122 | } 123 | } catch (e: IOException) { 124 | e.printStackTrace() 125 | emptyList() 126 | } 127 | } 128 | 129 | data class GitlabProject(val name: String, val id: Int) 130 | 131 | data class MergeRequest( 132 | val id: Int, 133 | val title: String, 134 | @Json(name = "created_at") val created: Date, 135 | @Json(name = "web_url") val webUrl: String, 136 | val author: User, 137 | val assignees: List, 138 | val assignee: User?, 139 | val reviewers: List, 140 | ) 141 | 142 | class User( 143 | val id: Int, 144 | val username: String, 145 | val name: String, 146 | ) 147 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # setup 2 | 3 | Each script should have instructions + the command on how to run it. 4 | But in general you'll need to install kotlin like so: 5 | 6 | ```sh 7 | # install sdkman 8 | # i like sdkman cause it allows us to install different versions 9 | curl -s "https://get.sdkman.io" | bash 10 | # install kotlin (1.9.24 until 2.0 starts working well with the libs used) 11 | sdk install kotlin 1.9.24 12 | 13 | # run the script 14 | kotlin 0.hello.main.kts Hello 🌎 15 | ``` 16 | 17 | Alternatively, if you want to make the script executable 18 | 19 | ```sh 20 | chmod +x 0.hello.main.kts 21 | 22 | # you can now run the script like so: 23 | ./0.hello.main.kts Hello 🌎 24 | ``` 25 | 26 | _Note: The first time you run the script it'll look like it's taking forever, thereafter the script runs super fast_. 27 | -------------------------------------------------------------------------------- /common.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | data class Person( 4 | val name: String, 5 | val age: Int 6 | ) 7 | -------------------------------------------------------------------------------- /star-wars-demo-results.csv: -------------------------------------------------------------------------------- 1 | "Timestamp","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE VIII THE LAST JEDI]","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE VII THE FORCE AWAKENS]","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE VI RETURN OF THE JEDI]","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE V THE EMPIRE STRIKES BACK]","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE IV A NEW HOPE]","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE III REVENGE OF THE SITH]","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE II ATTACK OF THE CLONES]","Which was your favorite SW movie from the original episodes? [STAR WARS: EPISODE I THE PHANTOM MENACE]","Which was your favorite standalone SW movie? [SOLO: A STAR WARS STORY]","Which was your favorite standalone SW movie? [ROGUE ONE: A STAR WARS STORY]","Which is your favorite star wars movie overall? [SOLO: A STAR WARS STORY]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE VIII THE LAST JEDI]","Which is your favorite star wars movie overall? [ROGUE ONE: A STAR WARS STORY]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE VII THE FORCE AWAKENS]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE VI RETURN OF THE JEDI]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE V THE EMPIRE STRIKES BACK]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE IV A NEW HOPE]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE III REVENGE OF THE SITH]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE II ATTACK OF THE CLONES]","Which is your favorite star wars movie overall? [STAR WARS: EPISODE I THE PHANTOM MENACE]" 2 | "2018/09/10 10:34:21 AM GMT+3","","2","","3","1","","","","2","1","","","3","2","","","1","","","" 3 | "2018/09/11 6:11:47 AM GMT+3","","","3","1","2","","","","2","1","","","","","3","1","2","","","" -------------------------------------------------------------------------------- /toots.csv: -------------------------------------------------------------------------------- 1 | blog/2023-02-03-toot-bot-post-mastodon-automatically-kotlin-script/index.md,109804252811332111 2 | -------------------------------------------------------------------------------- /x.test.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:Repository("https://repo.maven.apache.org/maven2/") 4 | @file:DependsOn("org.jetbrains.kotlin:kotlin-test:1.3.72") 5 | 6 | import kotlin.test.assertEquals 7 | 8 | /* 9 | * This script is a template for quickly running tests 10 | * 11 | * To run kscripts do it like so: 12 | $> brew install kotlin 13 | $> chmod +x x.test.main.kts 14 | $> ./x.test.main.kts 15 | */ 16 | 17 | 18 | fun isEditDistance1(string1: String, string2: String): Boolean { 19 | return false 20 | } 21 | 22 | assertEquals(true, isEditDistance1("cat", "cat"), "\uD83D\uDEA8") 23 | 24 | assertEquals(true, isEditDistance1("cat", "cat"), "🚨") 25 | assertEquals(false, isEditDistance1("cat", "dog"), "🚨") 26 | assertEquals(true, isEditDistance1("cat", "mat"), "🚨") 27 | assertEquals(true, isEditDistance1("cat", "chat"), "🚨") 28 | assertEquals(false, isEditDistance1("cat", "act"), "🚨") 29 | assertEquals(false, isEditDistance1("cat", "palindrome"), "🚨") 30 | assertEquals(true, isEditDistance1("cat", "cats"), "🚨") 31 | 32 | 33 | fun isEditDistance(maxDifference: Int, string1: String, string2: String): Boolean { 34 | return false 35 | } 36 | 37 | assertEquals(true, isEditDistance(0, "cat", "cat"), "\uD83D\uDEA8") 38 | assertEquals(true, isEditDistance(0, "cat", "cat"), "🚨") 39 | assertEquals(false, isEditDistance(1, "cat", "dog"), "🚨") 40 | assertEquals(true, isEditDistance(1, "cat", "mat"), "🚨") 41 | assertEquals(true, isEditDistance(1, "cat", "chat"), "🚨") 42 | assertEquals(true, isEditDistance(2, "ban", "bhane"), "🚨") 43 | assertEquals(false, isEditDistance(2, "pal", "palindrome"), "🚨") 44 | assertEquals(false, isEditDistance(1, "cat", "act"), "🚨") 45 | assertEquals(true, isEditDistance(1, "cats", "cat"), "🚨") 46 | assertEquals(true, isEditDistance(2, "cat", "chats"), "🚨") 47 | assertEquals(true, isEditDistance(3, "sitting", "kitten"), "🚨") 48 | assertEquals(false, isEditDistance(2, "sitting", "kitten"), "🚨") 49 | 50 | --------------------------------------------------------------------------------