diff --git a/tgbotapi.webapps/.templates/events/EventType{{$event_name_uppercase}}.kt b/tgbotapi.webapps/.templates/events/EventType{{$event_name_uppercase}}.kt new file mode 100644 index 0000000000..1c6f3908de --- /dev/null +++ b/tgbotapi.webapps/.templates/events/EventType{{$event_name_uppercase}}.kt @@ -0,0 +1 @@ +data object {{$event_name_uppercase}} : EventType("{{$event_name}}") \ No newline at end of file diff --git a/tgbotapi.webapps/.templates/events/WebApp{{$event_name_uppercase}}.kt b/tgbotapi.webapps/.templates/events/WebApp{{$event_name_uppercase}}.kt new file mode 100644 index 0000000000..fe13e7263b --- /dev/null +++ b/tgbotapi.webapps/.templates/events/WebApp{{$event_name_uppercase}}.kt @@ -0,0 +1,2 @@ +@JsName("onEvent") +internal fun on{{$event_name_uppercase}}(type: String, callback: ({{$callback_args}}) -> Unit) \ No newline at end of file diff --git a/tgbotapi.webapps/.templates/events/{{$event_name_uppercase}}.kt b/tgbotapi.webapps/.templates/events/{{$event_name_uppercase}}.kt new file mode 100644 index 0000000000..70ebcbeea1 --- /dev/null +++ b/tgbotapi.webapps/.templates/events/{{$event_name_uppercase}}.kt @@ -0,0 +1,22 @@ +// Part for callback typealias + +typealias {{$callback_typealias_name}} = WebApp.({{$callback_args}}) -> Unit + +// Part for outside of WebApp + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onEvent(type: EventType.{{$event_name_uppercase}}, eventHandler: {{$callback_typealias_name}}) = { it: {{$callback_typealias_name}} -> + eventHandler(js("this").unsafeCast(), it) +}.also { + on{{$event_name_uppercase}}( + type.typeName, + callback = it + ) +} + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.on{{$event_name_uppercase}}(eventHandler: {{$callback_typealias_name}}) = onEvent(EventType.{{$event_name_uppercase}}, eventHandler) diff --git a/tgbotapi.webapps/.templates/generator.kts b/tgbotapi.webapps/.templates/generator.kts new file mode 100755 index 0000000000..7b88a95d6b --- /dev/null +++ b/tgbotapi.webapps/.templates/generator.kts @@ -0,0 +1,238 @@ +#!/usr/bin/env kotlin +/** + * Generates files and folders as they have been put in the folder. Envs uses common syntax, but + * values may contains {{${'$'}sampleVariable}} parts, where {{${'$'}sampleVariable}} will be replaced with variable value. + * Example: + * + * .env: + * sampleVariable=${'$'}prompt # require request from command line + * sampleVariable2=just some value + * sampleVariable3=${'$'}{sampleVariable}.${'$'}{sampleVariable2} + * + * Result variables: + * sampleVariable=your input in console # lets imagine you typed it + * sampleVariable2=just some value + * sampleVariable3=your input in console.just some value + * + * To use these variables in template, you will need to write {{${'$'}sampleVariable}}. + * You may use it in text of files as well as in files/folders names. + * + * Usage: kotlin generator.kts [args] folders... + * Args: + * -e, --env: Path to file with args for generation; Use "${'$'}prompt" as values to read variable value from console + * -o, --outputFolder: Folder where templates should be used. Folder of calling by default + * folders: Folders-templates + * -s, --skip: Skip variables setup + * -a, --args: Pass several for several args. Use with syntax `--args a=b` or `-a a=b` to set variable with key `a` to value `b` + */ +import java.io.File + +val console = System.console() + +var envFile: File? = null +var outputFolder: File = File("./") // current folder by default +val templatesFolders = mutableListOf() +var extensions: List? = null +var skipPrompts: Boolean = false +val commandLineArgs = mutableMapOf() +val globalEnvs = System.getenv() + +fun String.replaceWithVariables(envs: Map): String { + var currentString = this + var changed = false + + do { + changed = false + envs.forEach { (k, v) -> + val previousString = currentString + currentString = currentString.replace("{{$${k}}}", v) + changed = changed || currentString != previousString + } + } while (changed) + + return currentString +} + +fun requestVariable(variableName: String, defaultValue: String?): String { + console.printf("Enter value for variable $variableName${defaultValue ?.let { " [$it]" } ?: ""}: ") + return console.readLine().ifEmpty { defaultValue } ?: "" +} + +fun readEnvs(content: String, presets: Map?): Map { + val initialEnvs = mutableMapOf() + val contentAsMap = mutableMapOf() + content.split("\n").forEach { + val withoutComment = it.replace(Regex("\\#.*"), "") + + runCatching { + val (key, value) = withoutComment.split("=") + + contentAsMap[key] = value + } + } + + if (skipPrompts) { + initialEnvs.putAll( + contentAsMap + (presets ?: emptyMap()) + globalEnvs + commandLineArgs + ) + } else { + contentAsMap.forEach { (key, value) -> + val existsValue = presets ?.get(key) ?: commandLineArgs[key] ?: globalEnvs[key] + initialEnvs[key] = when { + value == "\$prompt" -> requestVariable(key, existsValue) + else -> requestVariable(key, value.replaceWithVariables(initialEnvs)) + } + } + } + var i = 0 + val readEnvs = initialEnvs.toMutableMap() + while (i < readEnvs.size) { + val key = readEnvs.keys.elementAt(i) + val currentValue = readEnvs.getValue(key) + val withReplaced = currentValue.replaceWithVariables(readEnvs) + var changed = false + if (withReplaced != currentValue) { + i = 0 + readEnvs[key] = withReplaced + } else { + i++ + } + } + return (presets ?: emptyMap()) + readEnvs +} + +fun readParameters() { + var i = 0 + while (i < args.size) { + val arg = args[i] + when (arg) { + "--env", + "-e" -> { + i++ + envFile = File(args[i]) + } + "--skip", + "-s" -> { + skipPrompts = true + } + "--extensions", + "-ex" -> { + i++ + extensions = args[i].split(",") + } + "--outputFolder", + "-o" -> { + i++ + outputFolder = File(args[i]) + } + "--args", + "-a" -> { + i++ + val subarg = args[i] + val key = subarg.takeWhile { it != '=' } + val value = subarg.dropWhile { it != '=' }.removePrefix("=") + commandLineArgs[key] = value + } + "--help", + "-h" -> { + println(""" + Generates files and folders as the have been put in the folder. Envs uses common syntax, but + values may contains {{${'$'}sampleVariable}} parts, where {{${'$'}sampleVariable}} will be replaced with variable value. + Example: + + .env: + sampleVariable=${'$'}prompt # require request from command line + sampleVariable2=just some value + sampleVariable3=${'$'}{sampleVariable}.${'$'}{sampleVariable2} + + Result variables: + sampleVariable=your input in console # lets imagine you typed it + sampleVariable2=just some value + sampleVariable3=your input in console.just some value + + To use these variables in template, you will need to write {{${'$'}sampleVariable}}. + You may use it in text of files as well as in files/folders names. + + Usage: kotlin generator.kts [args] folders... + Args: + -e, --env: Path to file with args for generation; Use "${'$'}prompt" as values to read variable value from console + -o, --outputFolder: Folder where templates should be used. Folder of calling by default + folders: Folders-templates + -s, --skip: Skip variables setup + -a, --args: Pass several for several args. Use with syntax `--args a=b` or `-a a=b` to set variable with key `a` to value `b` + """.trimIndent()) + Runtime.getRuntime().exit(0) + } + else -> { + val potentialFile = File(arg) + println("Potential file/folder as template: ${potentialFile.absolutePath}") + runCatching { + if (potentialFile.exists()) { + println("Adding file/folder as template: ${potentialFile.absolutePath}") + templatesFolders.add(potentialFile) + } + }.onFailure { e -> + println("Unable to use folder $arg as template folder") + e.printStackTrace() + } + } + } + i++ + } +} + +readParameters() + +val envs: MutableMap = envFile ?.let { readEnvs(it.readText(), null) } ?.toMutableMap() ?: mutableMapOf() + +println( + """ + Result environments: + ${envs.toList().joinToString("\n ") { (k, v) -> "$k=$v" }} + Result extensions: + ${extensions ?.joinToString()} + Input folders: + ${templatesFolders.joinToString("\n ") { it.absolutePath }} + Output folder: + ${outputFolder.absolutePath} + """.trimIndent() +) + +fun File.handleTemplate(targetFolder: File, envs: Map) { + println("Handling $absolutePath") + val localEnvs = File(absolutePath, ".env").takeIf { it.exists() } ?.let { + println("Reading .env in ${absolutePath}") + readEnvs(it.readText(), envs) + } ?: envs + println( + """ + Local environments: + ${localEnvs.toList().joinToString("\n ") { (k, v) -> "$k=$v" }} + """.trimIndent() + ) + val newName = name.replaceWithVariables(localEnvs) + println("New name $newName") + when { + !exists() -> return + isFile -> { + val content = useLines { + it.map { it.replaceWithVariables(localEnvs) }.toList() + }.joinToString("\n") + val targetFile = File(targetFolder, newName) + targetFile.writeText(content) + println("Target file: ${targetFile.absolutePath}") + } + else -> { + val folder = File(targetFolder, newName) + println("Target folder: ${folder.absolutePath}") + folder.mkdirs() + listFiles() ?.forEach { fileOrFolder -> + fileOrFolder.handleTemplate(folder, localEnvs) + } + } + } +} + +templatesFolders.forEach { folderOrFile -> + folderOrFile.handleTemplate(outputFolder, envs) +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/events/EventsList.json b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/events/EventsList.json new file mode 100644 index 0000000000..64e758825c --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/events/EventsList.json @@ -0,0 +1,6 @@ +[ + { + "event_name": "activated", + "callback_args": "" + } +] diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/events/events_generator.main.kts b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/events/events_generator.main.kts new file mode 100755 index 0000000000..b10c079ecf --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/events/events_generator.main.kts @@ -0,0 +1,27 @@ +#!/usr/bin/env kotlin +@file:DependsOn("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") + +import kotlinx.serialization.json.* +import java.io.File +import java.lang.Runtime + +val rootFolder = File("../../../../../../../../") +val rfAbsolutePath = rootFolder.absolutePath + +fun generateEvent(eventName: String, callbacks: String) { + val uppercaseEventName = eventName.take(1).uppercase() + eventName.drop(1) + val command = "${rfAbsolutePath}/.templates/generator.kts -s -a \"event_name=$eventName\" -a \"event_name_uppercase=$uppercaseEventName\" -a \"callback_args=$callbacks\" --env \"${rfAbsolutePath}/.templates/events/.env\" -o \"./\" -ex \"kt\" \"${rfAbsolutePath}/.templates/events/\"" + + Runtime.getRuntime().exec(command).waitFor() +} + +val eventsList: JsonArray = Json.parseToJsonElement(File("EventsList.json").readText()).jsonArray + +eventsList.forEach { + generateEvent( + it.jsonObject["event_name"]!!.jsonPrimitive.content, + it.jsonObject["callback"] ?.jsonPrimitive ?.content ?: "" + ) +} + +println(eventsList.toString())