├── internal └── app │ ├── project │ ├── testData │ │ ├── empty-project │ │ │ ├── ktor-version.txt │ │ │ └── ktor-module.txt │ │ ├── marlformed-toml-file │ │ │ ├── expect-error.txt │ │ │ ├── ktor-module.txt │ │ │ ├── gradle │ │ │ │ └── libs.versions.toml │ │ │ └── build.gradle.kts │ │ ├── client-dep-simple │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── malformed-build-gradle-kts │ │ │ ├── expect-error.txt │ │ │ ├── ktor-module.txt │ │ │ └── build.gradle.kts │ │ ├── no-ktor-deps-in-toml │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── gradle │ │ │ │ ├── libs.versions.toml │ │ │ │ └── libs.versions.toml.expected │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── dep-is-already-present │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ ├── build.gradle.kts.expected │ │ │ └── gradle │ │ │ │ ├── libs.versions.toml │ │ │ │ └── libs.versions.toml.expected │ │ ├── hardcoded-version-in-bom │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── maven-projects-not-supported │ │ │ ├── ktor-version.txt │ │ │ └── ktor-module.txt │ │ ├── no-ktor-deps-no-versions-toml │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ ├── gradle │ │ │ │ └── libs.versions.toml.expected │ │ │ └── build.gradle.kts.expected │ │ ├── simple-generated-project │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ ├── build.gradle.kts.expected │ │ │ └── gradle │ │ │ │ ├── libs.versions.toml │ │ │ │ └── libs.versions.toml.expected │ │ ├── toml-group-name-key-values │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── gradle │ │ │ │ ├── libs.versions.toml │ │ │ │ └── libs.versions.toml.expected │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── groovy-dsl-projects-not-supported │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ └── build.gradle │ │ ├── hardcoded-version-in-variable │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── ktor-bom-defined-in-ktor-plugin │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── ktor-module-for-test-sources │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── no-ktor-deps-and-versions-in-toml │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── gradle │ │ │ │ ├── libs.versions.toml │ │ │ │ └── libs.versions.toml.expected │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── multi-platform-projects-not-supported │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── hardcoded-version-in-bom-dep-already-exists │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── multi-platform-catalog-projects-not-supported │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── gradle │ │ │ │ └── libs.versions.toml │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── no-new-line-at-the-end-of-build-dep-present │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ ├── build.gradle.kts.expected │ │ │ └── gradle │ │ │ │ ├── libs.versions.toml │ │ │ │ └── libs.versions.toml.expected │ │ ├── version-stored-in-gradle-properties-dep-exist │ │ │ ├── ktor-version.txt │ │ │ ├── gradle.properties │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── version-stored-in-gradle-properties-jvm-suffix │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── gradle.properties │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── version-stored-in-gradle-properties-simple │ │ │ ├── ktor-version.txt │ │ │ ├── gradle.properties │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── hardcoded-version-in-bom-has-serialization-plugin │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── ktor-deps-have-jvm-suffix-and-gradle-plugin-defined │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── versions-catalog-no-serialization-plugin-no-deps-with-toml │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ ├── gradle │ │ │ │ ├── libs.versions.toml │ │ │ │ └── libs.versions.toml.expected │ │ │ └── build.gradle.kts.expected │ │ ├── hardcoded-version-in-bom-require-serialization-has-kotlin │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ ├── hardcoded-version-in-plugin-dependency-exist-no-serialization-plugin │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ │ └── hardcoded-version-in-plugin-has-serialization-plugin-defined-with-id │ │ │ ├── ktor-version.txt │ │ │ ├── ktor-module.txt │ │ │ ├── build.gradle.kts │ │ │ └── build.gradle.kts.expected │ ├── project.go │ └── add_test.go │ ├── jdk │ ├── source.go │ ├── descriptor.go │ ├── RuntimeVersion.java │ ├── provider.go │ ├── verify.go │ ├── download.go │ └── fetch.go │ ├── lang │ ├── grammars │ │ ├── kotlin │ │ │ ├── UnicodeClasses.tokens │ │ │ └── KotlinLexer.tokens │ │ └── toml │ │ │ ├── desc.xml │ │ │ ├── examples │ │ │ ├── fruit.toml │ │ │ ├── test.toml │ │ │ └── hard.toml │ │ │ ├── pom.xml │ │ │ └── TomlParser.g4 │ ├── gradle │ │ ├── utils.go │ │ ├── bom.go │ │ ├── properties.go │ │ ├── modify.go │ │ ├── codegen.go │ │ └── query.go │ ├── edit.go │ ├── kotlin │ │ └── utils.go │ ├── parsers │ │ ├── toml │ │ │ ├── TomlLexer.tokens │ │ │ └── TomlParser.tokens │ │ └── kotlin │ │ │ ├── KotlinLexer.tokens │ │ │ └── KotlinParser.tokens │ ├── README.md │ ├── toml │ │ ├── codegen.go │ │ └── query.go │ └── common.go │ ├── progress │ ├── ansi.go │ ├── percent.go │ └── percent_test.go │ ├── cli │ ├── shell.go │ ├── parser.go │ ├── errors.go │ ├── parser_test.go │ ├── command │ │ ├── generate.go │ │ ├── openapi.go │ │ ├── dev.go │ │ └── complete.go │ ├── jdk.go │ ├── usage.go │ └── processing_test.go │ ├── i18n │ ├── get.go │ └── messages.go │ ├── utils │ ├── response.go │ ├── file.go │ ├── io.go │ ├── diff.go │ ├── logging.go │ ├── set.go │ └── strings.go │ ├── interactive │ ├── model │ │ ├── errors.go │ │ ├── utils.go │ │ └── state.go │ └── draw │ │ ├── styles.go │ │ └── state.go │ ├── config │ ├── persistent.go │ └── env.go │ ├── openapi │ └── download.go │ ├── network │ ├── artifacts_list.go │ ├── common.go │ ├── plugins.go │ ├── settings.go │ ├── artifacts_search.go │ └── project.go │ ├── errors.go │ ├── archive │ ├── zip.go │ └── targz.go │ ├── generate │ └── project.go │ └── ktor │ ├── module_test.go │ └── module.go ├── go.mod ├── snapcraft.yaml ├── packInstaller.ps1 ├── .gitignore ├── README.md └── go.sum /internal/app/project/testData/empty-project/ktor-version.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/marlformed-toml-file/expect-error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/client-dep-simple/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/malformed-build-gradle-kts/expect-error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-in-toml/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/dep-is-already-present/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/empty-project/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-core -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 2.3.13 -------------------------------------------------------------------------------- /internal/app/project/testData/maven-projects-not-supported/ktor-version.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-no-versions-toml/ktor-version.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/simple-generated-project/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/toml-group-name-key-values/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/groovy-dsl-projects-not-supported/ktor-version.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-variable/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-bom-defined-in-ktor-plugin/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-module-for-test-sources/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-and-versions-in-toml/ktor-version.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/dep-is-already-present/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-core -------------------------------------------------------------------------------- /internal/app/project/testData/malformed-build-gradle-kts/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-client-core -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-projects-not-supported/ktor-version.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/client-dep-simple/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-client-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-dep-already-exists/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 2.3.13 -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-module-for-test-sources/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-test-host -------------------------------------------------------------------------------- /internal/app/project/testData/marlformed-toml-file/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-catalog-projects-not-supported/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-in-toml/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/no-new-line-at-the-end-of-build-dep-present/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-dep-exist/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 2.3.5 -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-jvm-suffix/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 2.3.6 -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-simple/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.0 -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-has-serialization-plugin/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 2.3.13 -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-deps-have-jvm-suffix-and-gradle-plugin-defined/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/simple-generated-project/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/toml-group-name-key-values/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-dep-already-exists/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-webjars -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-variable/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-bom-defined-in-ktor-plugin/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/maven-projects-not-supported/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/no-new-line-at-the-end-of-build-dep-present/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-core -------------------------------------------------------------------------------- /internal/app/project/testData/versions-catalog-no-serialization-plugin-no-deps-with-toml/ktor-version.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/app/project/testData/groovy-dsl-projects-not-supported/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-require-serialization-has-kotlin/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 2.3.13 -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-no-versions-toml/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation:3.0.0 -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-dep-exist/gradle.properties: -------------------------------------------------------------------------------- 1 | ktorVersion=2.3.5 -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-simple/gradle.properties: -------------------------------------------------------------------------------- 1 | ktorVersion="3.0.0" -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-projects-not-supported/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-and-versions-in-toml/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation:3.0.0 -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-dependency-exist-no-serialization-plugin/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-has-serialization-plugin-defined-with-id/ktor-version.txt: -------------------------------------------------------------------------------- 1 | 3.0.1 -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-catalog-projects-not-supported/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-dep-exist/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-jvm-suffix/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-simple/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-has-serialization-plugin/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-serialization-kotlinx-json -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-deps-have-jvm-suffix-and-gradle-plugin-defined/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-server-content-negotiation -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-require-serialization-has-kotlin/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-serialization-kotlinx-json -------------------------------------------------------------------------------- /internal/app/project/testData/versions-catalog-no-serialization-plugin-no-deps-with-toml/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-serialization-kotlinx-json:3.0.0 -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-dependency-exist-no-serialization-plugin/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-serialization-kotlinx-json -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-has-serialization-plugin-defined-with-id/ktor-module.txt: -------------------------------------------------------------------------------- 1 | ktor-serialization-kotlinx-json -------------------------------------------------------------------------------- /internal/app/project/testData/marlformed-toml-file/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | ewqewq eqe 2 | wqe 3 | wqew;; 4 | e r 5 | qwrew 6 | e 7 | wq!!32324e 8 | -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-jvm-suffix/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlinVersion=2.0.20 2 | something=abc 3 | ktorVersion=2.3.6 -------------------------------------------------------------------------------- /internal/app/project/testData/malformed-build-gradle-kts/build.gradle.kts: -------------------------------------------------------------------------------- 1 | ewqeqw 2 | ewqeqwe 3 | ewqeqweqw 4 | ewqeqwqwe 5 | 2323 6 | 4232324 7 | 323234;rew'r'"" -------------------------------------------------------------------------------- /internal/app/jdk/source.go: -------------------------------------------------------------------------------- 1 | package jdk 2 | 3 | type Source int 4 | 5 | const ( 6 | FromJavaHome Source = iota 7 | FromConfig 8 | Locally 9 | Downloaded 10 | ) 11 | -------------------------------------------------------------------------------- /internal/app/lang/grammars/kotlin/UnicodeClasses.tokens: -------------------------------------------------------------------------------- 1 | UNICODE_CLASS_LL=1 2 | UNICODE_CLASS_LM=2 3 | UNICODE_CLASS_LO=3 4 | UNICODE_CLASS_LT=4 5 | UNICODE_CLASS_LU=5 6 | UNICODE_CLASS_ND=6 7 | UNICODE_CLASS_NL=7 8 | -------------------------------------------------------------------------------- /internal/app/progress/ansi.go: -------------------------------------------------------------------------------- 1 | package progress 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | func clearCurrentLine(w io.Writer) error { 8 | _, err := io.WriteString(w, "\033[2K\r") 9 | return err 10 | } 11 | -------------------------------------------------------------------------------- /internal/app/project/testData/versions-catalog-no-serialization-plugin-no-deps-with-toml/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-no-versions-toml/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies {} 10 | -------------------------------------------------------------------------------- /internal/app/cli/shell.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import "fmt" 4 | 5 | type ShellError struct { 6 | Shell string 7 | } 8 | 9 | func (se ShellError) Error() string { 10 | return fmt.Sprintf("unrecognized shell %s", se.Shell) 11 | } 12 | -------------------------------------------------------------------------------- /internal/app/lang/gradle/utils.go: -------------------------------------------------------------------------------- 1 | package gradle 2 | 3 | import "strings" 4 | 5 | func PlatformSuffix(artifact string) string { 6 | if strings.HasSuffix(artifact, "-jvm") { 7 | return "-jvm" 8 | } 9 | 10 | return "" 11 | } 12 | -------------------------------------------------------------------------------- /internal/app/project/testData/versions-catalog-no-serialization-plugin-no-deps-with-toml/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin-version = "2.0.21" 3 | 4 | [plugins] 5 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 6 | -------------------------------------------------------------------------------- /internal/app/i18n/get.go: -------------------------------------------------------------------------------- 1 | package i18n 2 | 3 | import "fmt" 4 | 5 | func Get(msg Message, args ...any) string { 6 | format, ok := en[msg] 7 | 8 | if !ok { 9 | return "[No translation]" 10 | } 11 | 12 | return fmt.Sprintf(format, args...) 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-no-versions-toml/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | [versions] 2 | ktor = "3.0.0" 3 | 4 | [libraries] 5 | ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } 6 | -------------------------------------------------------------------------------- /internal/app/lang/edit.go: -------------------------------------------------------------------------------- 1 | package lang 2 | 3 | import ( 4 | "github.com/antlr4-go/antlr/v4" 5 | ) 6 | 7 | func InsertLnAfter(rewriter *antlr.TokenStreamRewriter, token antlr.Token, indent, text string) { 8 | rewriter.InsertAfterDefault(token.GetTokenIndex(), "\n"+indent+text) 9 | } 10 | -------------------------------------------------------------------------------- /internal/app/lang/grammars/toml/desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CSharp;Dart;Go;Java;JavaScript;PHP;Python3;TypeScript;Antlr4ng 4 | 5 | -------------------------------------------------------------------------------- /internal/app/lang/grammars/toml/examples/fruit.toml: -------------------------------------------------------------------------------- 1 | [[fruit.blah]] 2 | name = "apple" 3 | 4 | [fruit.blah.physical] 5 | color = "red" 6 | shape = "round" 7 | 8 | [[fruit.blah]] 9 | name = "banana" 10 | 11 | [fruit.blah.physical] 12 | color = "yellow" 13 | shape = "bent" -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-no-versions-toml/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation(libs.ktor.server.content.negotiation) 11 | } 12 | -------------------------------------------------------------------------------- /internal/app/project/testData/client-dep-simple/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | } 13 | -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-bom-defined-in-ktor-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | } 13 | -------------------------------------------------------------------------------- /internal/app/utils/response.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | ) 7 | 8 | func ContentLength(resp *http.Response) int { 9 | if cl := resp.Header.Get("Content-Length"); cl != "" { 10 | if l, err := strconv.Atoi(cl); err == nil { 11 | return l 12 | } 13 | } 14 | 15 | return 0 16 | } 17 | -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-simple/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion: String by project 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-core:$ktorVersion") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-dep-already-exists/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation("io.ktor:ktor-server-webjars") 11 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 12 | } 13 | -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-jvm-suffix/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion: String by project 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-dep-already-exists/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation("io.ktor:ktor-server-webjars") 11 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 12 | } 13 | -------------------------------------------------------------------------------- /internal/app/lang/gradle/bom.go: -------------------------------------------------------------------------------- 1 | package gradle 2 | 3 | import ( 4 | parser "github.com/ktorio/ktor-cli/internal/app/lang/parsers/kotlin" 5 | ) 6 | 7 | func FindBom(deps []Dep) (parser.IStatementContext, bool) { 8 | for _, dep := range deps { 9 | if dep.IsKtorBom { 10 | return dep.Statement, true 11 | } 12 | } 13 | 14 | return nil, false 15 | } 16 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-require-serialization-has-kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation("io.ktor:ktor-server-webjars") 11 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 12 | } 13 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-variable/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion = "3.0.1" 10 | 11 | dependencies { 12 | implementation("group:artifact:1.2.3") 13 | implementation("io.ktor:ktor-server-core:$ktorVersion") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/versions-catalog-no-serialization-plugin-no-deps-with-toml/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | alias(libs.plugins.kotlin.serialization) 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation(libs.ktor.serialization.kotlinx.json) 12 | } 13 | -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-module-for-test-sources/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | testImplementation("org.jetbrains.kotlin:kotlin-test") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/client-dep-simple/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | implementation("io.ktor:ktor-client-content-negotiation") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation(platform("group:artifact:1.2.3")) 11 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 12 | implementation(platform("group:artifact:1.2.3")) 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-bom-defined-in-ktor-plugin/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | implementation("io.ktor:ktor-server-content-negotiation") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-deps-have-jvm-suffix-and-gradle-plugin-defined/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-core-jvm") 12 | implementation("io.ktor:ktor-server-netty-jvm") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-has-serialization-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | kotlin("plugin.serialization") version "2.0.20" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-dependency-exist-no-serialization-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | implementation("io.ktor:ktor-serialization-kotlinx-json") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-has-serialization-plugin-defined-with-id/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-webjars") 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-dep-exist/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion: String by project 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") 13 | implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-simple/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion: String by project 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-core:$ktorVersion") 13 | implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-dep-exist/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion: String by project 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") 13 | implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/lang/kotlin/utils.go: -------------------------------------------------------------------------------- 1 | package kotlin 2 | 3 | import "strings" 4 | 5 | func GetVarId(ref string) string { 6 | if len(ref) < 2 { 7 | return ref 8 | } 9 | 10 | if strings.HasPrefix(ref, "$") { 11 | return ref[1:] 12 | } 13 | 14 | if len(ref) < 4 { 15 | return ref 16 | } 17 | 18 | if strings.HasPrefix(ref, "${") && strings.HasSuffix(ref, "}") { 19 | return ref[2 : len(ref)-1] 20 | } 21 | 22 | return ref 23 | } 24 | -------------------------------------------------------------------------------- /internal/app/project/testData/version-stored-in-gradle-properties-jvm-suffix/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion: String by project 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") 13 | implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktorVersion") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/utils/file.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | func IsDirEmpty(dir string) bool { 9 | f, err := os.Open(dir) 10 | 11 | if err != nil { 12 | return false 13 | } 14 | 15 | defer f.Close() 16 | 17 | names, err := f.Readdirnames(1) 18 | return err == io.EOF && len(names) == 0 19 | } 20 | 21 | func Exists(path string) bool { 22 | _, err := os.Stat(path) 23 | return err == nil 24 | } 25 | -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-module-for-test-sources/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | testImplementation("org.jetbrains.kotlin:kotlin-test") 13 | testImplementation("io.ktor:ktor-server-test-host") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-variable/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | val ktorVersion = "3.0.1" 10 | 11 | dependencies { 12 | implementation("group:artifact:1.2.3") 13 | implementation("io.ktor:ktor-server-core:$ktorVersion") 14 | implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") 15 | } 16 | -------------------------------------------------------------------------------- /internal/app/jdk/descriptor.go: -------------------------------------------------------------------------------- 1 | package jdk 2 | 3 | import "fmt" 4 | 5 | type Error struct { 6 | *Descriptor 7 | } 8 | 9 | func (e Error) Error() string { 10 | return fmt.Sprintf("jdk error: %s", e.Descriptor) 11 | } 12 | 13 | type Descriptor struct { 14 | Platform string 15 | Arch string 16 | Version string 17 | } 18 | 19 | func (d *Descriptor) String() string { 20 | return fmt.Sprintf("JDK %s for %s (%s)", d.Version, d.Platform, d.Arch) 21 | } 22 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation(platform("group:artifact:1.2.3")) 11 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 12 | implementation("io.ktor:ktor-server-content-negotiation") 13 | implementation(platform("group:artifact:1.2.3")) 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-and-versions-in-toml/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | logback-version = "1.4.14" 5 | 6 | [libraries] 7 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 8 | 9 | [plugins] 10 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 11 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 12 | -------------------------------------------------------------------------------- /internal/app/project/testData/ktor-deps-have-jvm-suffix-and-gradle-plugin-defined/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-core-jvm") 12 | implementation("io.ktor:ktor-server-content-negotiation-jvm") 13 | implementation("io.ktor:ktor-server-netty-jvm") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-in-toml/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 9 | 10 | [plugins] 11 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 12 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 13 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-has-serialization-plugin/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | kotlin("plugin.serialization") version "2.0.20" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | implementation("io.ktor:ktor-serialization-kotlinx-json") 13 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/toml-group-name-key-values/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor-version" } 9 | 10 | [plugins] 11 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 12 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 13 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-dependency-exist-no-serialization-plugin/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | kotlin("plugin.serialization") version "2.0.20" 4 | id("io.ktor.plugin") version "3.0.1" 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-webjars") 13 | implementation("io.ktor:ktor-serialization-kotlinx-json") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-bom-require-serialization-has-kotlin/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | kotlin("plugin.serialization") version "2.0.20" 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("io.ktor:ktor-server-webjars") 12 | implementation("io.ktor:ktor-serialization-kotlinx-json") 13 | implementation(platform("io.ktor:ktor-bom:2.3.13")) 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/utils/io.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "io" 4 | 5 | func TeeReaderAt(r io.ReaderAt, w io.WriterAt) io.ReaderAt { 6 | return &teeReaderAt{r, w} 7 | } 8 | 9 | type teeReaderAt struct { 10 | r io.ReaderAt 11 | w io.WriterAt 12 | } 13 | 14 | func (t *teeReaderAt) ReadAt(p []byte, off int64) (n int, err error) { 15 | n, err = t.r.ReadAt(p, off) 16 | 17 | if n > 0 { 18 | if n, err := t.w.WriteAt(p[:n], off); err != nil { 19 | return n, err 20 | } 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /internal/app/project/testData/hardcoded-version-in-plugin-has-serialization-plugin-defined-with-id/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.0.20" 3 | id("io.ktor.plugin") version "3.0.1" 4 | id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | implementation("io.ktor:ktor-server-webjars") 13 | implementation("io.ktor:ktor-serialization-kotlinx-json") 14 | } 15 | -------------------------------------------------------------------------------- /internal/app/jdk/RuntimeVersion.java: -------------------------------------------------------------------------------- 1 | public class RuntimeVersion { 2 | public static void main(String[] args) { 3 | String version = System.getProperty("java.version"); 4 | if (version.startsWith("1.")) { 5 | version = version.substring(2, 3); 6 | } else { 7 | int dot = version.indexOf("."); 8 | if (dot != -1) { 9 | version = version.substring(0, dot); 10 | } 11 | } 12 | 13 | System.out.print(version); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/app/project/testData/versions-catalog-no-serialization-plugin-no-deps-with-toml/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | [versions] 2 | ktor = "3.0.0" 3 | kotlin-version = "2.0.21" 4 | 5 | [libraries] 6 | ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } 7 | 8 | [plugins] 9 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 10 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin-version" } 11 | -------------------------------------------------------------------------------- /internal/app/utils/diff.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hexops/gotextdiff" 6 | "github.com/hexops/gotextdiff/myers" 7 | "github.com/hexops/gotextdiff/span" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | func GetDiff(fp string, new string) string { 13 | old, err := os.ReadFile(fp) 14 | 15 | if err != nil { 16 | old = []byte{} 17 | } 18 | 19 | edits := myers.ComputeEdits(span.URIFromPath(fp), string(old), new) 20 | return fmt.Sprint(gotextdiff.ToUnified(filepath.Base(fp), filepath.Base(fp)+"~new", string(old), edits)) 21 | } 22 | -------------------------------------------------------------------------------- /internal/app/utils/logging.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "github.com/ktorio/ktor-cli/internal/app/config" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func PrepareGlobalLog(homeDir string) (*os.File, error) { 11 | err := os.Mkdir(config.KtorDir(homeDir), 0755) 12 | 13 | if err != nil && !errors.Is(err, os.ErrExist) { 14 | return nil, err 15 | } 16 | 17 | f, err := os.OpenFile(config.LogPath(homeDir), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) 18 | 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | log.SetOutput(f) 24 | return f, nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/app/lang/gradle/properties.go: -------------------------------------------------------------------------------- 1 | package gradle 2 | 3 | import ( 4 | "github.com/ktorio/ktor-cli/internal/app/lang" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func ParseProps(fp string) map[string]string { 10 | m := make(map[string]string) 11 | 12 | b, err := os.ReadFile(fp) 13 | 14 | if err != nil { 15 | return m 16 | } 17 | 18 | for _, line := range strings.Split(string(b), "\n") { 19 | parts := strings.Split(line, "=") 20 | 21 | if len(parts) == 2 { 22 | m[strings.TrimSpace(parts[0])] = lang.Unquote(strings.TrimSpace(parts[1])) 23 | } 24 | } 25 | 26 | return m 27 | } 28 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-and-versions-in-toml/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | ktor = "3.0.0" 4 | kotlin-version = "2.0.21" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } 9 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 10 | 11 | [plugins] 12 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 13 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-in-toml/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor-version" } 9 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 10 | 11 | [plugins] 12 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 13 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 14 | -------------------------------------------------------------------------------- /internal/app/project/testData/toml-group-name-key-values/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor-version" } 9 | ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor-version" } 10 | 11 | [plugins] 12 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 13 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ktorio/ktor-cli 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.3 6 | 7 | require ( 8 | github.com/antlr4-go/antlr/v4 v4.13.1 9 | github.com/gdamore/tcell/v2 v2.7.4 10 | github.com/hexops/gotextdiff v1.0.3 11 | golang.org/x/term v0.24.0 12 | ) 13 | 14 | require ( 15 | github.com/gdamore/encoding v1.0.1 // indirect 16 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 17 | github.com/mattn/go-runewidth v0.0.16 // indirect 18 | github.com/rivo/uniseg v0.4.7 // indirect 19 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 20 | golang.org/x/sys v0.27.0 // indirect 21 | golang.org/x/text v0.18.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /internal/app/lang/parsers/toml/TomlLexer.tokens: -------------------------------------------------------------------------------- 1 | WS=1 2 | NL=2 3 | COMMENT=3 4 | L_BRACKET=4 5 | DOUBLE_L_BRACKET=5 6 | R_BRACKET=6 7 | DOUBLE_R_BRACKET=7 8 | EQUALS=8 9 | DOT=9 10 | COMMA=10 11 | BASIC_STRING=11 12 | LITERAL_STRING=12 13 | UNQUOTED_KEY=13 14 | VALUE_WS=14 15 | L_BRACE=15 16 | BOOLEAN=16 17 | ML_BASIC_STRING=17 18 | ML_LITERAL_STRING=18 19 | FLOAT=19 20 | INF=20 21 | NAN=21 22 | DEC_INT=22 23 | HEX_INT=23 24 | OCT_INT=24 25 | BIN_INT=25 26 | OFFSET_DATE_TIME=26 27 | LOCAL_DATE_TIME=27 28 | LOCAL_DATE=28 29 | LOCAL_TIME=29 30 | INLINE_TABLE_WS=30 31 | R_BRACE=31 32 | ARRAY_WS=32 33 | '['=4 34 | '[['=5 35 | ']'=6 36 | ']]'=7 37 | '='=8 38 | '.'=9 39 | ','=10 40 | '{'=15 41 | '}'=31 42 | -------------------------------------------------------------------------------- /internal/app/lang/parsers/toml/TomlParser.tokens: -------------------------------------------------------------------------------- 1 | WS=1 2 | NL=2 3 | COMMENT=3 4 | L_BRACKET=4 5 | DOUBLE_L_BRACKET=5 6 | R_BRACKET=6 7 | DOUBLE_R_BRACKET=7 8 | EQUALS=8 9 | DOT=9 10 | COMMA=10 11 | BASIC_STRING=11 12 | LITERAL_STRING=12 13 | UNQUOTED_KEY=13 14 | VALUE_WS=14 15 | L_BRACE=15 16 | BOOLEAN=16 17 | ML_BASIC_STRING=17 18 | ML_LITERAL_STRING=18 19 | FLOAT=19 20 | INF=20 21 | NAN=21 22 | DEC_INT=22 23 | HEX_INT=23 24 | OCT_INT=24 25 | BIN_INT=25 26 | OFFSET_DATE_TIME=26 27 | LOCAL_DATE_TIME=27 28 | LOCAL_DATE=28 29 | LOCAL_TIME=29 30 | INLINE_TABLE_WS=30 31 | R_BRACE=31 32 | ARRAY_WS=32 33 | '['=4 34 | '[['=5 35 | ']'=6 36 | ']]'=7 37 | '='=8 38 | '.'=9 39 | ','=10 40 | '{'=15 41 | '}'=31 42 | -------------------------------------------------------------------------------- /internal/app/lang/grammars/toml/examples/test.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML document. 2 | 3 | title = "TOML Example" 4 | 5 | [owner] 6 | name = "Tom Preston-Werner" 7 | dob = 1979-05-27T07:32:00-08:00 # First class dates 8 | 9 | [database] 10 | server = "192.168.1.1" 11 | ports = [ 8001, 8001, 8002 ] 12 | connection_max = 5000 13 | enabled = true 14 | 15 | [servers] 16 | 17 | # Indentation (tabs and/or spaces) is allowed but not required 18 | [servers.alpha] 19 | ip = "10.0.0.1" 20 | dc = "eqdc10" 21 | 22 | [servers.beta] 23 | ip = "10.0.0.2" 24 | dc = "eqdc10" 25 | 26 | [clients] 27 | data = [ ["gamma", "delta"], [1, 2] ] 28 | 29 | # Line breaks are OK when inside arrays 30 | hosts = [ 31 | "alpha", 32 | "omega" 33 | ] -------------------------------------------------------------------------------- /internal/app/utils/set.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type StringSet map[string]struct{} 9 | 10 | func NewStringSet() StringSet { 11 | return make(StringSet) 12 | } 13 | 14 | func (s *StringSet) Add(v string) { 15 | (*s)[v] = struct{}{} 16 | } 17 | 18 | func (s *StringSet) Entries() (entries []string) { 19 | for k := range *s { 20 | entries = append(entries, k) 21 | } 22 | 23 | return 24 | } 25 | 26 | func (s *StringSet) Single() (v string, err error) { 27 | if len(*s) > 1 { 28 | err = errors.New(fmt.Sprintf("expected at most one entry in the set, got %d", len(*s))) 29 | return 30 | } 31 | for k := range *s { 32 | v = k 33 | return 34 | } 35 | 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /internal/app/interactive/model/errors.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type ErrorKind int 4 | 5 | const ( 6 | TerminalHeightError ErrorKind = iota 7 | UnableFetchPluginsError 8 | ProjectDirNotEmptyError 9 | DirNotExistError 10 | ProjectDirTooLongError 11 | ProjectNameEmptyError 12 | ProjectNameAllowedCharsError 13 | ) 14 | 15 | func (mdl *State) SetError(k ErrorKind, s string) { 16 | mdl.errorMap[k] = s 17 | } 18 | 19 | func (mdl *State) RemoveErrors(ks ...ErrorKind) { 20 | for _, k := range ks { 21 | delete(mdl.errorMap, k) 22 | } 23 | } 24 | 25 | func (mdl *State) GetErrors(ks ...ErrorKind) []string { 26 | var errs []string 27 | for _, k := range ks { 28 | if e, ok := mdl.errorMap[k]; ok { 29 | errs = append(errs, e) 30 | } 31 | } 32 | return errs 33 | } 34 | -------------------------------------------------------------------------------- /internal/app/utils/strings.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "slices" 5 | "strings" 6 | ) 7 | 8 | // CleanProjectName returns project name stripped until the first forbidden symbol 9 | func CleanProjectName(name string) string { 10 | forbiddenChars := map[rune]struct{}{'/': {}, '\\': {}, ':': {}, '<': {}, '>': {}, '"': {}, '?': {}, '*': {}, '|': {}} 11 | lastIndex := -1 12 | 13 | for i, ch := range name { 14 | if _, ok := forbiddenChars[ch]; ok { 15 | lastIndex = i 16 | break 17 | } 18 | } 19 | 20 | if lastIndex == -1 { 21 | return name 22 | } 23 | 24 | return name[:lastIndex] 25 | } 26 | 27 | func GetPackage(website string) string { 28 | parts := strings.Split(website, ".") 29 | slices.Reverse(parts) 30 | return strings.Join(parts, ".") 31 | } 32 | -------------------------------------------------------------------------------- /internal/app/cli/parser.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type Args struct { 8 | Flags []string 9 | Command string 10 | CommandArgs []string 11 | } 12 | 13 | type Options struct { 14 | Verbose bool 15 | } 16 | 17 | func ParseArgs(args []string) *Args { 18 | i := 1 19 | var flags []string 20 | for ; i < len(args); i++ { 21 | arg := args[i] 22 | if !strings.HasPrefix(arg, "-") { 23 | break 24 | } 25 | 26 | flags = append(flags, arg) 27 | } 28 | 29 | command := "" 30 | if i < len(args) { 31 | command = args[i] 32 | } 33 | var commandArgs []string 34 | if i+1 < len(args) { 35 | commandArgs = args[i+1:] 36 | } 37 | 38 | return &Args{ 39 | Flags: flags, 40 | Command: command, 41 | CommandArgs: commandArgs, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/app/lang/README.md: -------------------------------------------------------------------------------- 1 | ## Directories 2 | 3 | The `grammars` directory contains ANTLR4 grammars: 4 | 5 | * Toml from https://github.com/antlr/grammars-v4/tree/master/toml 6 | * Kotlin from https://github.com/Kotlin/kotlin-spec/tree/release/grammar/src/main/antlr 7 | 8 | The `parsers` directory contains generated parsers for Go language. 9 | 10 | ## Generate a parser 11 | 12 | 1. Download ANTLR tool https://www.antlr.org/download/antlr-4.13.2-complete.jar 13 | 2. Execute the following commands to generate a lexer and a parser for the specific language: 14 | ```shell 15 | cd internal/app/lang 16 | java -jar antlr-4.13.2-complete.jar -Dlanguage=Go -o parsers/{lang} grammars/{lang}/{Lang}Lexer.g4 17 | java -jar antlr-4.13.2-complete.jar -Dlanguage=Go -o parsers/{lang} grammars/{lang}/{Lang}Parser.g4 18 | ``` 19 | -------------------------------------------------------------------------------- /internal/app/lang/gradle/modify.go: -------------------------------------------------------------------------------- 1 | package gradle 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app/lang" 6 | ) 7 | 8 | func AddCatalogDep(build *BuildRoot, libKey string) (string, error) { 9 | ktorDep, ok := FindCatalogDepPrefixed(build, "libs.ktor") 10 | 11 | if !ok && build.Repositories.Statement != nil { 12 | build.Rewriter.InsertAfterDefault( 13 | build.Repositories.Statement.GetStop().GetTokenIndex(), 14 | fmt.Sprintf("\n\n"+NewDepsWithKtor(libKey)), 15 | ) 16 | return build.Rewriter.GetTextDefault(), nil 17 | } 18 | 19 | lang.InsertLnAfter( 20 | build.Rewriter, 21 | ktorDep.Statement.GetStop(), 22 | lang.HiddenTokensToLeft(build.Stream, ktorDep.Statement.GetStart().GetTokenIndex()), 23 | CatalogDependency(libKey), 24 | ) 25 | 26 | return build.Rewriter.GetTextDefault(), nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/app/project/testData/marlformed-toml-file/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-in-toml/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/project/testData/dep-is-already-present/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core2) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/project/testData/simple-generated-project/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/project/testData/toml-group-name-key-values/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/project/testData/dep-is-already-present/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core2) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-and-versions-in-toml/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-new-line-at-the-end-of-build-dep-present/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } -------------------------------------------------------------------------------- /internal/app/project/testData/no-new-line-at-the-end-of-build-dep-present/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.netty) 24 | implementation(libs.logback.classic) 25 | implementation(libs.ktor.server.config.yaml) 26 | testImplementation(libs.ktor.server.test.host) 27 | testImplementation(libs.kotlin.test.junit) 28 | } -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-in-toml/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.content.negotiation) 24 | implementation(libs.ktor.server.netty) 25 | implementation(libs.logback.classic) 26 | implementation(libs.ktor.server.config.yaml) 27 | testImplementation(libs.ktor.server.test.host) 28 | testImplementation(libs.kotlin.test.junit) 29 | } 30 | -------------------------------------------------------------------------------- /internal/app/project/testData/simple-generated-project/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.content.negotiation) 24 | implementation(libs.ktor.server.netty) 25 | implementation(libs.logback.classic) 26 | implementation(libs.ktor.server.config.yaml) 27 | testImplementation(libs.ktor.server.test.host) 28 | testImplementation(libs.kotlin.test.junit) 29 | } 30 | -------------------------------------------------------------------------------- /internal/app/project/testData/toml-group-name-key-values/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.content.negotiation) 24 | implementation(libs.ktor.server.netty) 25 | implementation(libs.logback.classic) 26 | implementation(libs.ktor.server.config.yaml) 27 | testImplementation(libs.ktor.server.test.host) 28 | testImplementation(libs.kotlin.test.junit) 29 | } 30 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-ktor-deps-and-versions-in-toml/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | alias(libs.plugins.kotlin.jvm) 4 | alias(libs.plugins.ktor) 5 | } 6 | 7 | group = "com.example.com" 8 | version = "0.0.1" 9 | 10 | application { 11 | mainClass.set("io.ktor.server.netty.EngineMain") 12 | 13 | val isDevelopment: Boolean = project.ext.has("development") 14 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation(libs.ktor.server.core) 23 | implementation(libs.ktor.server.content.negotiation) 24 | implementation(libs.ktor.server.netty) 25 | implementation(libs.logback.classic) 26 | implementation(libs.ktor.server.config.yaml) 27 | testImplementation(libs.ktor.server.test.host) 28 | testImplementation(libs.kotlin.test.junit) 29 | } 30 | -------------------------------------------------------------------------------- /internal/app/interactive/model/utils.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func IsDirEmptyOrAbsent(dir string) bool { 12 | f, err := os.Open(dir) 13 | if err != nil { 14 | return true 15 | } 16 | defer f.Close() 17 | 18 | _, err = f.Readdirnames(1) 19 | if err == io.EOF { 20 | return true 21 | } 22 | return false 23 | } 24 | 25 | func HasNonExistentDirsInPath(p string) (bool, string) { 26 | volume := filepath.VolumeName(p) 27 | rest := strings.TrimPrefix(p, volume) 28 | segments := strings.Split(rest, string(os.PathSeparator)) 29 | for i := 1; i < len(segments); i++ { 30 | if segments[i-1] == "" { 31 | continue 32 | } 33 | d := volume + strings.Join(segments[:i], string(os.PathSeparator)) 34 | if _, err := os.Stat(d); errors.Is(err, os.ErrNotExist) { 35 | return true, d 36 | } 37 | } 38 | 39 | return false, "" 40 | } 41 | -------------------------------------------------------------------------------- /internal/app/project/testData/dep-is-already-present/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core2 = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" } 9 | ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" } 10 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 11 | ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" } 12 | ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" } 13 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" } 14 | 15 | [plugins] 16 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 17 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 18 | -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-catalog-projects-not-supported/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | compose = "1.6.2" 3 | compose-plugin = "1.6.0" 4 | junit = "4.13.2" 5 | kotlin = "2.0.20" 6 | ktor = "3.0.1" 7 | 8 | [libraries] 9 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 10 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 11 | junit = { group = "junit", name = "junit", version.ref = "junit" } 12 | compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } 13 | compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } 14 | ktorClientCore = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } 15 | 16 | [plugins] 17 | jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } 18 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -------------------------------------------------------------------------------- /internal/app/project/testData/simple-generated-project/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" } 9 | ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" } 10 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 11 | ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" } 12 | ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" } 13 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" } 14 | 15 | [plugins] 16 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 17 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 18 | -------------------------------------------------------------------------------- /internal/app/project/testData/dep-is-already-present/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core2 = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" } 9 | ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" } 10 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 11 | ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" } 12 | ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" } 13 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" } 14 | 15 | [plugins] 16 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 17 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 18 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-new-line-at-the-end-of-build-dep-present/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" } 9 | ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" } 10 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 11 | ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" } 12 | ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" } 13 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" } 14 | 15 | [plugins] 16 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 17 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 18 | -------------------------------------------------------------------------------- /internal/app/project/testData/no-new-line-at-the-end-of-build-dep-present/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" } 9 | ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" } 10 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 11 | ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" } 12 | ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" } 13 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" } 14 | 15 | [plugins] 16 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 17 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 18 | -------------------------------------------------------------------------------- /internal/app/project/testData/groovy-dsl-projects-not-supported/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | dependencies { 7 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20" 8 | classpath "io.ktor.plugin:plugin:3.0.1" 9 | } 10 | } 11 | 12 | apply plugin: 'kotlin' 13 | apply plugin: 'application' 14 | apply plugin: 'io.ktor.plugin' 15 | 16 | mainClassName = "ToolsAppKt" 17 | 18 | sourceSets { 19 | main.kotlin.srcDirs = ['src'] 20 | main.resources.srcDirs = ['resources'] 21 | test.kotlin.srcDirs = ['test'] 22 | test.resources.srcDirs = ['testresources'] 23 | } 24 | 25 | repositories { 26 | mavenCentral() 27 | } 28 | 29 | dependencies { 30 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20' 31 | implementation "io.ktor:ktor-server-html-builder" 32 | implementation 'ch.qos.logback:logback-classic:1.5.12' 33 | implementation 'io.ktor:ktor-server-netty-jvm' 34 | implementation 'io.ktor:ktor-client-cio-jvm' 35 | } 36 | 37 | -------------------------------------------------------------------------------- /internal/app/config/persistent.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "os" 7 | ) 8 | 9 | var data map[string]string 10 | 11 | func init() { 12 | data = make(map[string]string) 13 | 14 | homeDir, err := os.UserHomeDir() 15 | if err != nil { 16 | return 17 | } 18 | 19 | f, err := os.Open(ktorConfigPath(homeDir)) 20 | if err != nil { 21 | return 22 | } 23 | 24 | defer f.Close() 25 | 26 | b, err := io.ReadAll(f) 27 | if err != nil { 28 | return 29 | } 30 | 31 | _ = json.Unmarshal(b, &data) 32 | } 33 | 34 | func GetValue(key string) (string, bool) { 35 | v, ok := data[key] 36 | return v, ok 37 | } 38 | 39 | func SetValue(key string, value string) { 40 | data[key] = value 41 | } 42 | 43 | func Commit() error { 44 | homeDir, err := os.UserHomeDir() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | b, err := json.MarshalIndent(data, "", "\t") 50 | if err != nil { 51 | return err 52 | } 53 | 54 | err = os.WriteFile(ktorConfigPath(homeDir), b, 0755) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/app/interactive/draw/styles.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | var searchHighlightColor = tcell.Color172 6 | var mainColor = tcell.Color126 7 | var bgColor = tcell.Color233 8 | var textColor = tcell.ColorWhite 9 | var inputColor = tcell.Color235 10 | var strongTextColor = tcell.Color141 11 | var weakTextColor = tcell.Color139 12 | var errorColor = tcell.Color160 13 | var statusColor = tcell.Color106 14 | var activeColor = tcell.Color189 15 | 16 | var DefaultStyle = tcell.StyleDefault.Background(bgColor) 17 | var inputStyle = DefaultStyle.Background(inputColor).Foreground(textColor) 18 | var buttonStyle = DefaultStyle.Background(mainColor).Foreground(textColor) 19 | var activeTabStyle = DefaultStyle.Foreground(bgColor).Background(activeColor) 20 | var activeStyle = DefaultStyle.Foreground(mainColor).Background(activeColor) 21 | var textStyle = DefaultStyle.Foreground(textColor) 22 | var weakTextStyle = DefaultStyle.Foreground(weakTextColor) 23 | var subTextStyle = DefaultStyle.Foreground(tcell.ColorGrey) 24 | var errorStyle = DefaultStyle.Foreground(errorColor) 25 | -------------------------------------------------------------------------------- /internal/app/project/testData/simple-generated-project/gradle/libs.versions.toml.expected: -------------------------------------------------------------------------------- 1 | 2 | [versions] 3 | kotlin-version = "2.0.21" 4 | ktor-version = "3.0.1" 5 | logback-version = "1.4.14" 6 | 7 | [libraries] 8 | ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" } 9 | ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor-version" } 10 | ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" } 11 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" } 12 | ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" } 13 | ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" } 14 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" } 15 | 16 | [plugins] 17 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } 18 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" } 19 | -------------------------------------------------------------------------------- /internal/app/openapi/download.go: -------------------------------------------------------------------------------- 1 | package openapi 2 | 3 | import ( 4 | "github.com/ktorio/ktor-cli/internal/app" 5 | "github.com/ktorio/ktor-cli/internal/app/i18n" 6 | "github.com/ktorio/ktor-cli/internal/app/network" 7 | "github.com/ktorio/ktor-cli/internal/app/progress" 8 | "github.com/ktorio/ktor-cli/internal/app/utils" 9 | "io" 10 | "net/http" 11 | ) 12 | 13 | func DownloadJar(client *http.Client, jarUrl string) ([]byte, error) { 14 | resp, err := client.Get(jarUrl) 15 | 16 | if err != nil { 17 | return nil, network.ConvertResponseError(err, app.OpenApiDownloadJarError) 18 | } 19 | 20 | defer resp.Body.Close() 21 | 22 | if err = network.CheckResponseStatus(resp, "fetch OpenAPI JAR", app.OpenApiDownloadJarError); err != nil { 23 | return nil, err 24 | } 25 | 26 | reader, progressBar := progress.NewReader( 27 | resp.Body, 28 | i18n.Get(i18n.DownloadingOpenApiJarProgress), 29 | utils.ContentLength(resp), 30 | true, 31 | ) 32 | defer progressBar.Done() 33 | 34 | bodyBytes, err := io.ReadAll(reader) 35 | if err != nil { 36 | return nil, &app.Error{Err: err, Kind: app.OpenApiDownloadJarError} 37 | } 38 | 39 | return bodyBytes, nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/app/network/artifacts_list.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/ktorio/ktor-cli/internal/app" 8 | "github.com/ktorio/ktor-cli/internal/app/config" 9 | "net/http" 10 | ) 11 | 12 | func ListArtifacts(client *http.Client, ktorVersion string) ([]string, error) { 13 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/artifacts/%s/list", config.GenBaseUrl(), ktorVersion), nil) 14 | 15 | if err != nil { 16 | return nil, &app.Error{Err: err, Kind: app.InternalError} 17 | } 18 | 19 | resp, err := client.Do(req) 20 | 21 | if err != nil { 22 | return nil, ConvertResponseError(err, app.ArtifactListError) 23 | } 24 | 25 | defer resp.Body.Close() 26 | 27 | tag := fmt.Sprintf("list artifacts for %s", ktorVersion) 28 | if err = CheckResponseStatus(resp, tag, app.ArtifactListError); err != nil { 29 | return nil, err 30 | } 31 | 32 | dec := json.NewDecoder(resp.Body) 33 | var result []string 34 | err = dec.Decode(&result) 35 | 36 | if err != nil { 37 | return nil, app.Error{Err: errors.New(fmt.Sprintf("%s: %s", tag, err.Error())), Kind: app.ArtifactListError} 38 | } 39 | 40 | return result, nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/app/cli/errors.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import "fmt" 4 | 5 | type ErrorKind int 6 | 7 | type Error struct { 8 | Kind ErrorKind 9 | Err error 10 | } 11 | 12 | func (e Error) Error() string { 13 | return fmt.Sprintf("cli args error: %v", e.Kind) 14 | } 15 | 16 | const ( 17 | NoCommandError ErrorKind = iota 18 | CommandNotFoundError 19 | WrongNumberOfArgumentsError 20 | UnrecognizedFlagsError 21 | UnrecognizedCommandFlagsError 22 | NoArgumentForFlag 23 | ) 24 | 25 | type UnrecognizedFlags []string 26 | 27 | func (f UnrecognizedFlags) Error() string { 28 | return fmt.Sprintf("unrecognized flags: %sv", []string(f)) 29 | } 30 | 31 | type UnrecognizedCommandFlags struct { 32 | Command string 33 | Flags []string 34 | } 35 | 36 | func (e UnrecognizedCommandFlags) Error() string { 37 | return fmt.Sprintf("command %s unrecognized flags: %v", e.Command, e.Flags) 38 | } 39 | 40 | type CommandError struct { 41 | Command Command 42 | } 43 | 44 | func (e CommandError) Error() string { 45 | return fmt.Sprintf("command '%s' error", e.Command) 46 | } 47 | 48 | type FlagError struct { 49 | Flag string 50 | } 51 | 52 | func (e FlagError) Error() string { 53 | return fmt.Sprintf("flag '%s' error", e.Flag) 54 | } 55 | -------------------------------------------------------------------------------- /internal/app/jdk/provider.go: -------------------------------------------------------------------------------- 1 | package jdk 2 | 3 | import ( 4 | "github.com/ktorio/ktor-cli/internal/app" 5 | "github.com/ktorio/ktor-cli/internal/app/config" 6 | "github.com/ktorio/ktor-cli/internal/app/i18n" 7 | "log" 8 | "net/http" 9 | "os" 10 | "runtime" 11 | ) 12 | 13 | func FetchRecommendedJdk(client *http.Client, homeDir string, logger *log.Logger) (string, error) { 14 | jdksDir := config.JdksDir(homeDir) 15 | err := os.MkdirAll(jdksDir, 0755) 16 | 17 | if err != nil { 18 | return "", &app.Error{Err: err, Kind: app.JdksDirError} 19 | } 20 | 21 | logger.Printf(i18n.Get(i18n.FetchingJdk, getRecommendedJdk())) 22 | jdkPath, err := fetch(client, getRecommendedJdk(), config.JdksDir(homeDir), logger) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | config.SetValue("jdk", jdkPath) 28 | _ = config.Commit() 29 | 30 | return jdkPath, err 31 | } 32 | 33 | func getRecommendedJdk() *Descriptor { 34 | platform := runtime.GOOS 35 | 36 | if platform == "darwin" { 37 | platform = "macos" 38 | } 39 | 40 | arch := runtime.GOARCH 41 | switch runtime.GOARCH { 42 | case "amd64": 43 | arch = "x64" 44 | case "arm64": 45 | arch = "aarch64" 46 | } 47 | 48 | return &Descriptor{Platform: platform, Arch: arch, Version: "17"} 49 | } 50 | -------------------------------------------------------------------------------- /internal/app/lang/toml/codegen.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app/ktor" 6 | ) 7 | 8 | func PluginEntry(key, pluginId, version string) string { 9 | return fmt.Sprintf("%s = { id = \"%s\", version.ref = \"%s\" }", key, pluginId, version) 10 | } 11 | 12 | func LibEntryModule(versionKey string, mc ktor.MavenCoords) string { 13 | return fmt.Sprintf("%s = { module = \"%s:%s\", version.ref = \"%s\" }", mc.Artifact, mc.Group, mc.Artifact, versionKey) 14 | } 15 | 16 | func LibEntryGroupName(versionKey string, mc ktor.MavenCoords) string { 17 | return fmt.Sprintf("%s = { group = \"%s\", name = \"%s\", version.ref = \"%s\" }", mc.Artifact, mc.Group, mc.Artifact, versionKey) 18 | } 19 | 20 | func NewTomlWithKtor(mc ktor.MavenCoords) string { 21 | return fmt.Sprintf(`[versions] 22 | ktor = "%s" 23 | 24 | [libraries] 25 | %s = { module = "%s:%s", version.ref = "ktor" } 26 | `, mc.Version, mc.Artifact, mc.Group, mc.Artifact) 27 | } 28 | 29 | func NewLibraryTableWithKtor(mc ktor.MavenCoords) string { 30 | return fmt.Sprintf("[libraries]\n%s = { module = \"%s:%s\", version.ref = \"ktor\" }", mc.Artifact, mc.Group, mc.Artifact) 31 | } 32 | 33 | func VersionEntry(key, version string) string { 34 | return fmt.Sprintf("%s = \"%s\"", key, version) 35 | } 36 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: ktor 2 | base: core24 3 | version: 'dev' 4 | summary: Generates Ktor projects through the command line interface. 5 | website: https://github.com/ktorio/ktor-cli 6 | license: Apache-2.0 7 | description: | 8 | This CLI tool allows generating server-side projects written with Ktor. 9 | Ktor is an asynchronous framework for creating client and server applications, from microservices to multiplatform client apps. 10 | Written in Kotlin from the ground up. 11 | 12 | grade: stable 13 | confinement: strict 14 | adopt-info: ktor 15 | 16 | platforms: 17 | amd64: 18 | arm64: 19 | build-on: [amd64] 20 | build-for: [arm64] 21 | 22 | apps: 23 | ktor: 24 | command: bin/ktor 25 | plugs: 26 | - home 27 | - network 28 | 29 | parts: 30 | ktor: 31 | plugin: nil 32 | source: https://github.com/ktorio/ktor-cli.git 33 | source-type: git 34 | override-build: | 35 | export GOBIN="${CRAFT_PART_INSTALL}/bin" 36 | export CGO_ENABLED=0 37 | go mod download 38 | go install -ldflags="-X main.Version=$(git describe --tags --contains --always --abbrev=7)" ./... 39 | override-pull: | 40 | craftctl default 41 | craftctl set version=$(git describe --tags --contains --always --abbrev=7) 42 | build-snaps: 43 | - go 44 | -------------------------------------------------------------------------------- /internal/app/cli/parser_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestParseArgs(t *testing.T) { 9 | checkParsing(t, []string{}, &Args{}) 10 | checkParsing(t, []string{"ktor"}, &Args{}) 11 | checkParsing(t, []string{"ktor", "-v"}, &Args{Flags: []string{"-v"}}) 12 | checkParsing(t, []string{"ktor", "--version", "--help"}, &Args{Flags: []string{"--version", "--help"}}) 13 | checkParsing(t, []string{"ktor", "command"}, &Args{Command: "command"}) 14 | checkParsing(t, []string{"ktor", "-v", "command"}, &Args{Flags: []string{"-v"}, Command: "command"}) 15 | checkParsing( 16 | t, 17 | []string{"ktor", "command", "arg1", "arg2"}, 18 | &Args{CommandArgs: []string{"arg1", "arg2"}, Command: "command"}, 19 | ) 20 | checkParsing( 21 | t, 22 | []string{"ktor", "-v", "command", "arg1", "arg2"}, 23 | &Args{Flags: []string{"-v"}, CommandArgs: []string{"arg1", "arg2"}, Command: "command"}, 24 | ) 25 | checkParsing( 26 | t, 27 | []string{"ktor", "command", "-v1", "-v2"}, 28 | &Args{Command: "command", CommandArgs: []string{"-v1", "-v2"}}, 29 | ) 30 | } 31 | 32 | func checkParsing(t *testing.T, rawArgs []string, expected *Args) { 33 | args := ParseArgs(rawArgs) 34 | 35 | if !reflect.DeepEqual(ParseArgs(rawArgs), expected) { 36 | t.Fatalf("expected %#v, got %#v", expected, args) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/app/config/env.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | func GenBaseUrl() string { 10 | if e, ok := os.LookupEnv("GEN_BASEURL"); ok && e != "" { 11 | return e 12 | } 13 | 14 | return "https://ktor-plugin.europe-north1-gke.intellij.net" 15 | } 16 | 17 | func CorrettoBaseUrl() string { 18 | if e, ok := os.LookupEnv("CORRETTO_BASEURL"); ok && e != "" { 19 | return e 20 | } 21 | 22 | return "https://corretto.aws" 23 | } 24 | 25 | func OpenApiJarUrl() string { 26 | if e, ok := os.LookupEnv("OPENAPI_JAR_URL"); ok && e != "" { 27 | return e 28 | } 29 | 30 | version := "7.11.0" 31 | 32 | return fmt.Sprintf("https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/%s/openapi-generator-cli-%s.jar", version, version) 33 | } 34 | 35 | func KtorDir(homeDir string) string { 36 | return filepath.Join(homeDir, ".ktor") 37 | } 38 | 39 | func LogPath(homeDir string) string { 40 | return filepath.Join(KtorDir(homeDir), "run.log") 41 | } 42 | 43 | func JdksDir(homeDir string) string { 44 | return filepath.Join(KtorDir(homeDir), "jdks") 45 | } 46 | 47 | func TempDir(homeDir string) string { 48 | return filepath.Join(KtorDir(homeDir), "temp") 49 | } 50 | 51 | func ktorConfigPath(homeDir string) string { 52 | return filepath.Join(KtorDir(homeDir), "config.json") 53 | } 54 | -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-projects-not-supported/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl 3 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 4 | 5 | plugins { 6 | kotlin("multiplatform") version "2.0.20" 7 | } 8 | 9 | repositories { 10 | maven("https://maven.pkg.jetbrains.space/public/p/ktor/eap") 11 | mavenCentral() 12 | google() 13 | } 14 | 15 | kotlin { 16 | @OptIn(ExperimentalWasmDsl::class) 17 | wasmJs { 18 | moduleName = "composeApp" 19 | browser { 20 | commonWebpackConfig { 21 | outputFileName = "composeApp.js" 22 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { 23 | static = (static ?: mutableListOf()).apply { 24 | // Serve sources to debug inside browser 25 | add(project.projectDir.path) 26 | } 27 | } 28 | } 29 | } 30 | binaries.executable() 31 | } 32 | 33 | sourceSets { 34 | commonMain.dependencies { 35 | implementation(platform("io.ktor:ktor-bom:3.0.0")) 36 | implementation("io.ktor:ktor-server-core") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/app/errors.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | ) 9 | 10 | const ( 11 | NetworkError ErrorKind = iota 12 | InternalError 13 | GenServerError 14 | GenServerTimeoutError 15 | UnknownError 16 | ProjectDirError 17 | JdksDirError 18 | ProjectExtractError 19 | JdkExtractError 20 | GradlewChmodError 21 | ExtractRootDirExistError 22 | UnableLocateJdkError 23 | JdkServerError 24 | JdkServerDownloadError 25 | JdkVerificationFailed 26 | ExternalCommandError 27 | OpenApiDownloadJarError 28 | OpenApiExecuteJarError 29 | ArtifactSearchError 30 | ArtifactSearchVersionNotSupportedError 31 | ArtifactListError 32 | BackupCreationError 33 | WriteChangesError 34 | UnrecognizedShellError 35 | NoPermsForFile 36 | ) 37 | 38 | type ErrorKind int 39 | 40 | type Error struct { 41 | Err error 42 | Kind ErrorKind 43 | } 44 | 45 | func (e Error) Error() string { 46 | return fmt.Sprintf("%v: %v", e.Kind, e.Err) 47 | } 48 | 49 | func StatusError(resp *http.Response, endpoint string) error { 50 | bodyBytes, err := io.ReadAll(resp.Body) 51 | 52 | body := "" 53 | if err == nil { 54 | b := string(bodyBytes) 55 | if len(b) > 0 { 56 | body = fmt.Sprintf(": %s", b) 57 | } 58 | } 59 | 60 | return errors.New(fmt.Sprintf("%s: unexpected response status %d from the server%s", endpoint, resp.StatusCode, body)) 61 | } 62 | -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-projects-not-supported/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl 3 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 4 | 5 | plugins { 6 | kotlin("multiplatform") version "2.0.20" 7 | } 8 | 9 | repositories { 10 | maven("https://maven.pkg.jetbrains.space/public/p/ktor/eap") 11 | mavenCentral() 12 | google() 13 | } 14 | 15 | kotlin { 16 | @OptIn(ExperimentalWasmDsl::class) 17 | wasmJs { 18 | moduleName = "composeApp" 19 | browser { 20 | commonWebpackConfig { 21 | outputFileName = "composeApp.js" 22 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { 23 | static = (static ?: mutableListOf()).apply { 24 | // Serve sources to debug inside browser 25 | add(project.projectDir.path) 26 | } 27 | } 28 | } 29 | } 30 | binaries.executable() 31 | } 32 | 33 | sourceSets { 34 | commonMain.dependencies { 35 | implementation(platform("io.ktor:ktor-bom:3.0.0")) 36 | implementation("io.ktor:ktor-server-core") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/app/lang/gradle/codegen.go: -------------------------------------------------------------------------------- 1 | package gradle 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app/ktor" 6 | "github.com/ktorio/ktor-cli/internal/app/lang" 7 | "strings" 8 | ) 9 | 10 | func KotlinPrefixedPlugin(id, version string) string { 11 | return fmt.Sprintf("kotlin(\"%s\") version \"%s\"", id, version) 12 | } 13 | 14 | func CatalogPlugin(catalogKey string) string { 15 | return fmt.Sprintf("alias(libs.plugins.%s)", strings.ReplaceAll(catalogKey, "-", ".")) 16 | } 17 | 18 | func RawDependencyNoVersion(mc ktor.MavenCoords, suffix string) string { 19 | fn := "implementation" 20 | if mc.IsTest { 21 | fn = "testImplementation" 22 | } 23 | 24 | return fmt.Sprintf("%s(%s)", fn, lang.Quote(mc.Group+":"+mc.Artifact+suffix)) 25 | } 26 | 27 | func DependencyWithVersionVar(mc ktor.MavenCoords, version string, suffix string) string { 28 | fn := "implementation" 29 | if mc.IsTest { 30 | fn = "testImplementation" 31 | } 32 | 33 | return fmt.Sprintf("%s(%s)", fn, lang.Quote(mc.Group+":"+mc.Artifact+suffix+":$"+version)) 34 | } 35 | 36 | func CatalogDependency(artifact string) string { 37 | return fmt.Sprintf("implementation(libs.%s)", strings.ReplaceAll(artifact, "-", ".")) 38 | } 39 | 40 | func NewDepsWithKtor(libKey string) string { 41 | return fmt.Sprintf(`dependencies { 42 | implementation(libs.%s) 43 | }`, strings.ReplaceAll(libKey, "-", ".")) 44 | } 45 | -------------------------------------------------------------------------------- /internal/app/jdk/verify.go: -------------------------------------------------------------------------------- 1 | package jdk 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "github.com/ktorio/ktor-cli/internal/app/config" 9 | "github.com/ktorio/ktor-cli/internal/app/i18n" 10 | "io" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | func Verify(client *http.Client, d *Descriptor, r io.Reader, logger *log.Logger) (bool, error) { 16 | if !hasJdkBuild(d) { 17 | return false, errors.New(fmt.Sprintf("cannot verify %s", d)) 18 | } 19 | 20 | ext := "tar.gz" 21 | if d.Platform == "windows" { 22 | ext = "zip" 23 | } 24 | 25 | url := fmt.Sprintf("%s/downloads/latest_sha256/amazon-corretto-%s-%s-%s-jdk.%s", config.CorrettoBaseUrl(), d.Version, d.Arch, d.Platform, ext) 26 | logger.Printf(i18n.Get(i18n.VerifyingJdk, d)) 27 | 28 | resp, err := client.Get(url) 29 | if err != nil { 30 | return false, err 31 | } 32 | 33 | defer resp.Body.Close() 34 | 35 | b, err := io.ReadAll(resp.Body) 36 | 37 | if err != nil { 38 | return false, err 39 | } 40 | 41 | sha, err := hex.DecodeString(string(b)) 42 | 43 | if err != nil { 44 | return false, err 45 | } 46 | 47 | h := sha256.New() 48 | 49 | if _, err := io.Copy(h, r); err != nil { 50 | return false, err 51 | } 52 | 53 | return hashesEqual(sha, h.Sum(nil)), nil 54 | } 55 | 56 | func hashesEqual(source, gen []byte) bool { 57 | for i := 0; i < len(source) && i < len(gen); i++ { 58 | if source[i] != gen[i] { 59 | return false 60 | } 61 | } 62 | if len(source) != len(gen) { 63 | return false 64 | } 65 | 66 | return true 67 | } 68 | -------------------------------------------------------------------------------- /internal/app/network/common.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "errors" 7 | "github.com/ktorio/ktor-cli/internal/app" 8 | "net" 9 | "net/http" 10 | "syscall" 11 | ) 12 | 13 | func ConvertResponseError(err error, serverErrorKind app.ErrorKind) error { 14 | if errors.Is(err, syscall.ECONNREFUSED) || errors.Is(err, syscall.ECONNABORTED) || errors.Is(err, syscall.ECONNRESET) { 15 | return &app.Error{Err: err, Kind: app.NetworkError} 16 | } 17 | 18 | var dnsErr *net.DNSError 19 | if errors.As(err, &dnsErr) { 20 | return &app.Error{Err: err, Kind: app.NetworkError} 21 | } 22 | 23 | if errors.Is(err, context.DeadlineExceeded) { 24 | return &app.Error{Err: err, Kind: app.NetworkError} 25 | } 26 | 27 | var certErr *tls.CertificateVerificationError 28 | if errors.As(err, &certErr) { 29 | return &app.Error{Err: err, Kind: serverErrorKind} 30 | } 31 | 32 | return &app.Error{Err: err, Kind: app.UnknownError} 33 | } 34 | 35 | func CheckResponseStatus(resp *http.Response, endpoint string, serverErrorKind app.ErrorKind) error { 36 | if resp.StatusCode != http.StatusOK { 37 | statusErr := app.StatusError(resp, endpoint) 38 | if resp.StatusCode == http.StatusNotFound || resp.StatusCode >= 500 { 39 | return &app.Error{Err: statusErr, Kind: serverErrorKind} 40 | } 41 | 42 | if resp.StatusCode == http.StatusBadRequest { 43 | return &app.Error{Err: statusErr, Kind: app.InternalError} 44 | } 45 | 46 | return &app.Error{Err: statusErr, Kind: serverErrorKind} 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/app/network/plugins.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/ktorio/ktor-cli/internal/app" 9 | "github.com/ktorio/ktor-cli/internal/app/config" 10 | "net/http" 11 | ) 12 | 13 | type Plugin struct { 14 | Id string `json:"xmlId"` 15 | Name string `json:"name"` 16 | Group string `json:"group"` 17 | Description string `json:"description"` 18 | RequiredPlugins []string `json:"requiredFeatures"` 19 | } 20 | 21 | func FetchPlugins(client *http.Client, ktorVersion string, ctx context.Context) ([]Plugin, error) { 22 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/features/%s", config.GenBaseUrl(), ktorVersion), nil) 23 | 24 | if err != nil { 25 | return nil, &app.Error{Err: err, Kind: app.InternalError} 26 | } 27 | 28 | req.Header.Set("Content-Type", "application/json") 29 | req.Header.Set("User-Agent", ctx.Value("user-agent").(string)) 30 | 31 | resp, err := client.Do(req) 32 | 33 | if err != nil { 34 | return nil, ConvertResponseError(err, app.GenServerError) 35 | } 36 | 37 | defer resp.Body.Close() 38 | 39 | tag := fmt.Sprintf("fetch plugins for %s", ktorVersion) 40 | if err = CheckResponseStatus(resp, tag, app.GenServerError); err != nil { 41 | return nil, err 42 | } 43 | 44 | dec := json.NewDecoder(resp.Body) 45 | var plugins []Plugin 46 | err = dec.Decode(&plugins) 47 | 48 | if err != nil { 49 | return nil, app.Error{Err: errors.New(fmt.Sprintf("%s: %s", tag, err.Error())), Kind: app.GenServerError} 50 | } 51 | 52 | return plugins, nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/app/lang/toml/query.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "github.com/ktorio/ktor-cli/internal/app/ktor" 5 | "strings" 6 | ) 7 | 8 | func FindLib(doc *Document, mc ktor.MavenCoords) (*TableEntry, bool) { 9 | libTable, ok := FindTable(doc, "libraries") 10 | 11 | if !ok { 12 | return nil, false 13 | } 14 | 15 | for _, e := range libTable.Entries { 16 | if e.Kind != ValueMap { 17 | continue 18 | } 19 | 20 | path, ok := e.KeyValue["module"] 21 | 22 | if !ok { 23 | continue 24 | } 25 | 26 | if coords, ok := ktor.ParseMavenCoords(path); ok && mc.RoughlySame(coords) { 27 | return &e, true 28 | } 29 | } 30 | 31 | return nil, false 32 | } 33 | 34 | func FindPlugin(doc *Document, pluginId string) (*TableEntry, bool) { 35 | pluginsTable, ok := FindTable(doc, "plugins") 36 | 37 | if !ok { 38 | return nil, false 39 | } 40 | 41 | for _, e := range pluginsTable.Entries { 42 | if id, ok := e.Get("id"); ok && id == pluginId { 43 | return &e, true 44 | } 45 | } 46 | 47 | return nil, false 48 | } 49 | 50 | func FindVersionPrefixed(doc *Document, prefix string) (*TableEntry, bool) { 51 | versionsTable, ok := FindTable(doc, "versions") 52 | 53 | if !ok { 54 | return nil, false 55 | } 56 | 57 | for _, e := range versionsTable.Entries { 58 | if e.Kind == StringValue && strings.HasPrefix(e.Key, prefix) { 59 | return &e, true 60 | } 61 | } 62 | 63 | return nil, false 64 | } 65 | 66 | func FindTable(doc *Document, name string) (*Table, bool) { 67 | for _, t := range doc.Tables.List { 68 | if t.Name == name { 69 | return &t, true 70 | } 71 | } 72 | 73 | return nil, false 74 | } 75 | -------------------------------------------------------------------------------- /internal/app/network/settings.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/ktorio/ktor-cli/internal/app" 8 | "github.com/ktorio/ktor-cli/internal/app/config" 9 | "net/http" 10 | ) 11 | 12 | func FetchSettings(client *http.Client) (*DefaultSettings, error) { 13 | resp, err := client.Get(fmt.Sprintf("%s/project/settings", config.GenBaseUrl())) 14 | 15 | if err != nil { 16 | return nil, ConvertResponseError(err, app.GenServerError) 17 | } 18 | 19 | defer resp.Body.Close() 20 | 21 | if err = CheckResponseStatus(resp, "fetch settings", app.GenServerError); err != nil { 22 | return nil, err 23 | } 24 | 25 | dec := json.NewDecoder(resp.Body) 26 | var settings DefaultSettings 27 | err = dec.Decode(&settings) 28 | 29 | if err != nil { 30 | return nil, app.Error{Err: errors.New("fetch settings: " + err.Error()), Kind: app.GenServerError} 31 | } 32 | 33 | return &settings, nil 34 | } 35 | 36 | type DefaultSettings struct { 37 | ProjectName StringParam `json:"project_name"` 38 | CompanyWebsite StringParam `json:"company_website"` 39 | Engine EnumParam `json:"engine"` 40 | KtorVersion EnumParam `json:"ktor_version"` 41 | KotlinVersion EnumParam `json:"kotlin_version"` 42 | BuildSystem EnumParam `json:"build_system"` 43 | ConfigType EnumParam `json:"configuration_in"` 44 | } 45 | 46 | type StringParam struct { 47 | DefaultVal string `json:"default"` 48 | } 49 | 50 | type EnumParam struct { 51 | Options []EnumOption `json:"options"` 52 | DefaultId string `json:"default_id"` 53 | } 54 | 55 | type EnumOption struct { 56 | Id string `json:"id"` 57 | Name string `json:"name"` 58 | } 59 | -------------------------------------------------------------------------------- /internal/app/cli/command/generate.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ktorio/ktor-cli/internal/app/cli" 7 | "github.com/ktorio/ktor-cli/internal/app/generate" 8 | "github.com/ktorio/ktor-cli/internal/app/i18n" 9 | "github.com/ktorio/ktor-cli/internal/app/jdk" 10 | "log" 11 | "net/http" 12 | "os" 13 | ) 14 | 15 | func Generate(client *http.Client, projectDir, projectName string, plugins []string, verboseLogger *log.Logger, hasGlobalLog bool, ctx context.Context) { 16 | homeDir, err := os.UserHomeDir() 17 | if err != nil { 18 | fmt.Fprintln(os.Stderr, i18n.Get(i18n.CannotDetermineHomeDir)) 19 | os.Exit(1) 20 | } 21 | 22 | err = generate.Project(client, verboseLogger, projectDir, projectName, plugins, ctx) 23 | 24 | if err != nil { 25 | cli.ExitWithProjectError(err, projectDir, hasGlobalLog, homeDir) 26 | } 27 | 28 | fmt.Printf(i18n.Get(i18n.ProjectCreated, projectName, projectDir)) 29 | 30 | jdkSrc, jdkPath, err := cli.ObtainJdk(client, verboseLogger, homeDir) 31 | 32 | switch jdkSrc { 33 | case jdk.FromJavaHome: 34 | fmt.Printf(i18n.Get(i18n.JDKDetectedJavaHome, jdkPath)) 35 | cli.PrintCommands(projectDir, true, "") 36 | case jdk.FromConfig: 37 | fmt.Printf(i18n.Get(i18n.JdkDetected, jdkPath)) 38 | cli.PrintCommands(projectDir, false, jdkPath) 39 | case jdk.Locally: 40 | fmt.Printf(i18n.Get(i18n.JdkFoundLocally, jdkPath)) 41 | cli.PrintCommands(projectDir, false, jdkPath) 42 | case jdk.Downloaded: 43 | if err != nil { 44 | cli.ExitWithProjectError(err, projectDir, hasGlobalLog, homeDir) 45 | } 46 | fmt.Printf(i18n.Get(i18n.JdkDownloaded, jdkPath)) 47 | cli.PrintCommands(projectDir, false, jdkPath) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-catalog-projects-not-supported/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl 3 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 4 | 5 | plugins { 6 | alias(libs.plugins.kotlinMultiplatform) 7 | 8 | alias(libs.plugins.jetbrainsCompose) 9 | } 10 | 11 | repositories { 12 | maven("https://maven.pkg.jetbrains.space/public/p/ktor/eap") 13 | mavenCentral() 14 | google() 15 | } 16 | 17 | kotlin { 18 | @OptIn(ExperimentalWasmDsl::class) 19 | wasmJs { 20 | moduleName = "composeApp" 21 | browser { 22 | commonWebpackConfig { 23 | outputFileName = "composeApp.js" 24 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { 25 | static = (static ?: mutableListOf()).apply { 26 | // Serve sources to debug inside browser 27 | add(project.projectDir.path) 28 | } 29 | } 30 | } 31 | } 32 | binaries.executable() 33 | } 34 | 35 | sourceSets { 36 | commonMain.dependencies { 37 | implementation(compose.runtime) 38 | implementation(compose.foundation) 39 | implementation(compose.material) 40 | implementation(compose.ui) 41 | implementation(compose.components.resources) 42 | implementation(compose.components.uiToolingPreview) 43 | 44 | implementation(libs.ktorClientCore) 45 | } 46 | } 47 | } 48 | 49 | 50 | 51 | compose.experimental { 52 | web.application {} 53 | } -------------------------------------------------------------------------------- /internal/app/cli/jdk.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/ktorio/ktor-cli/internal/app" 7 | "github.com/ktorio/ktor-cli/internal/app/config" 8 | "github.com/ktorio/ktor-cli/internal/app/i18n" 9 | "github.com/ktorio/ktor-cli/internal/app/jdk" 10 | "log" 11 | "net/http" 12 | "os" 13 | ) 14 | 15 | func DownloadJdk(homeDir string, client *http.Client, logger *log.Logger, attempt int) (string, error) { 16 | jdkPath, err := jdk.FetchRecommendedJdk(client, homeDir, logger) 17 | 18 | if err != nil { 19 | var e *app.Error 20 | 21 | if errors.As(err, &e) && e.Kind == app.JdkVerificationFailed { 22 | fmt.Println(i18n.Get(i18n.JdkVerificationFailed)) 23 | 24 | if attempt >= 2 { 25 | return "", err 26 | } 27 | 28 | return DownloadJdk(homeDir, client, logger, attempt+1) 29 | } 30 | 31 | return "", err 32 | } 33 | 34 | return jdkPath, nil 35 | } 36 | 37 | func ObtainJdk(client *http.Client, verboseLogger *log.Logger, homeDir string) (jdk.Source, string, error) { 38 | if jh, ok := jdk.JavaHome(); ok { 39 | if v, err := jdk.GetJavaMajorVersion(jh, homeDir); err == nil && v >= jdk.MinJavaVersion { 40 | return jdk.FromJavaHome, jh, nil 41 | } 42 | } 43 | 44 | if jdkPath, ok := config.GetValue("jdk"); ok { 45 | if st, err := os.Stat(jdkPath); err == nil && st.IsDir() { 46 | return jdk.FromConfig, jdkPath, nil 47 | } 48 | } 49 | 50 | if jdkPath, ok := jdk.FindLocally(jdk.MinJavaVersion); ok { 51 | config.SetValue("jdk", jdkPath) 52 | _ = config.Commit() 53 | return jdk.Locally, jdkPath, nil 54 | } 55 | 56 | jdkPath, err := DownloadJdk(homeDir, client, verboseLogger, 0) 57 | 58 | if err != nil { 59 | return 0, "", err 60 | } 61 | 62 | return jdk.Downloaded, jdkPath, nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/app/project/testData/multi-platform-catalog-projects-not-supported/build.gradle.kts.expected: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl 3 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 4 | 5 | plugins { 6 | alias(libs.plugins.kotlinMultiplatform) 7 | 8 | alias(libs.plugins.jetbrainsCompose) 9 | } 10 | 11 | repositories { 12 | maven("https://maven.pkg.jetbrains.space/public/p/ktor/eap") 13 | mavenCentral() 14 | google() 15 | } 16 | 17 | kotlin { 18 | @OptIn(ExperimentalWasmDsl::class) 19 | wasmJs { 20 | moduleName = "composeApp" 21 | browser { 22 | commonWebpackConfig { 23 | outputFileName = "composeApp.js" 24 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { 25 | static = (static ?: mutableListOf()).apply { 26 | // Serve sources to debug inside browser 27 | add(project.projectDir.path) 28 | } 29 | } 30 | } 31 | } 32 | binaries.executable() 33 | } 34 | 35 | sourceSets { 36 | commonMain.dependencies { 37 | implementation(compose.runtime) 38 | implementation(compose.foundation) 39 | implementation(compose.material) 40 | implementation(compose.ui) 41 | implementation(compose.components.resources) 42 | implementation(compose.components.uiToolingPreview) 43 | 44 | implementation(libs.ktorClientCore) 45 | } 46 | } 47 | } 48 | 49 | 50 | 51 | compose.experimental { 52 | web.application {} 53 | } -------------------------------------------------------------------------------- /internal/app/lang/grammars/toml/examples/hard.toml: -------------------------------------------------------------------------------- 1 | # Test file for TOML 2 | # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 3 | # This part you'll really hate 4 | 5 | [the] 6 | test_string = "You'll hate me after this - #" # " Annoying, isn't it? 7 | 8 | [the.hard] 9 | test_array = [ "] ", " # "] # ] There you go, parse this! 10 | test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 11 | # You didn't think it'd as easy as chucking out the last #, did you? 12 | another_test_string = " Same thing, but with a string #" 13 | harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 14 | # Things will get harder 15 | 16 | [the.hard."bit#"] 17 | "what?" = "You don't think some user won't do that?" 18 | multi_line_array = [ 19 | "]", 20 | # ] Oh yes I did 21 | ] 22 | 23 | # A key can look like a number 24 | 1 = "first" 25 | # Just a "normal" dotted key for a nested table 26 | -6.674e-11 = '100% a good reason, trust me' 27 | 10.0.0.0 = 'private' # This works. 28 | # Even things that look like dates work as keys 29 | 2000-01-01 = "Some Day" 30 | 31 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 32 | 33 | #[error] if you didn't catch this, your parser is broken 34 | #string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this 35 | #array = [ 36 | # "This might most likely happen in multiline arrays", 37 | # Like here, 38 | # "or here, 39 | # and here" 40 | # ] End of array comment, forgot the # 41 | #number = 3.14 pi <--again forgot the # -------------------------------------------------------------------------------- /internal/app/cli/usage.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app/i18n" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | func WriteUsage(w io.Writer) { 11 | fmt.Fprintf(w, i18n.Get(i18n.ToolSummary)) 12 | fmt.Fprintf(w, i18n.Get(i18n.UsageLine)) 13 | fmt.Fprintln(w, i18n.Get(i18n.OptionsCaption)) 14 | 15 | maxLen := 0 16 | for _, spec := range AllFlagsSpec { 17 | if l := len(formatFlags(&spec)); l > maxLen { 18 | maxLen = l 19 | } 20 | } 21 | 22 | for _, spec := range AllFlagsSpec { 23 | fmt.Fprintf(w, "\t%-*s %s\n", maxLen, formatFlags(&spec), spec.Description) 24 | } 25 | 26 | fmt.Fprintln(w, i18n.Get(i18n.CommandsCaption)) 27 | 28 | maxLen = 0 29 | for command := range AllCommandsSpec { 30 | if command == CompletionCommand { 31 | continue 32 | } 33 | 34 | if l := len(formatCommand(command)); l > maxLen { 35 | maxLen = l 36 | } 37 | } 38 | 39 | for command, spec := range AllCommandsSpec { 40 | if command == CompletionCommand { 41 | continue 42 | } 43 | 44 | fmt.Fprintf(w, "\t%-*s %s\n", maxLen, formatCommand(command), spec.Description) 45 | } 46 | } 47 | func formatCommand(command Command) string { 48 | var opts strings.Builder 49 | 50 | sep := "" 51 | if spec, ok := commandFlagSpec[command]; ok { 52 | for _, s := range spec { 53 | opts.WriteString(sep) 54 | opts.WriteString(formatFlags(&s)) 55 | 56 | if s.hasArg { 57 | opts.WriteString(" ") 58 | } 59 | 60 | sep = ", " 61 | } 62 | } 63 | 64 | if opts.String() != "" { 65 | return fmt.Sprintf("%s [%s] %s", command, opts.String(), formatArgs(AllCommandsSpec[command].args)) 66 | } 67 | 68 | return fmt.Sprintf("%s %s", command, formatArgs(AllCommandsSpec[command].args)) 69 | } 70 | 71 | func formatFlags(spec *flagSpec) string { 72 | return strings.Join(spec.Aliases, ", ") 73 | } 74 | -------------------------------------------------------------------------------- /internal/app/archive/zip.go: -------------------------------------------------------------------------------- 1 | package archive 2 | 3 | import ( 4 | "archive/zip" 5 | "errors" 6 | "github.com/ktorio/ktor-cli/internal/app/i18n" 7 | "github.com/ktorio/ktor-cli/internal/app/utils" 8 | "io" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | func ExtractZip(rt io.ReaderAt, size int64, outDir string, logger *log.Logger) (rootDirs utils.StringSet, err error) { 16 | rootDirs = utils.NewStringSet() 17 | zr, err := zip.NewReader(rt, size) 18 | 19 | if err != nil { 20 | return 21 | } 22 | 23 | var zipErrors []error 24 | for _, zf := range zr.File { 25 | outPath := filepath.Join(outDir, zf.Name) 26 | 27 | if strings.HasSuffix(zf.Name, "/") { 28 | err := os.MkdirAll(outPath, zf.Mode()) 29 | 30 | if err != nil { 31 | zipErrors = append(zipErrors, err) 32 | } 33 | 34 | continue 35 | } 36 | 37 | zipFile, err := zr.Open(zf.Name) 38 | 39 | if err != nil { 40 | zipErrors = append(zipErrors, err) 41 | continue 42 | } 43 | 44 | err = func() error { 45 | defer zipFile.Close() 46 | 47 | if filepath.Dir(zf.Name) != "." { 48 | dir := filepath.Dir(outPath) 49 | logger.Printf(i18n.Get(i18n.CreatingDir, dir)) 50 | 51 | if i := strings.Index(zf.Name, "/"); i != -1 { 52 | rootDirs.Add(filepath.Join(outDir, zf.Name[:i])) 53 | } 54 | 55 | err := os.MkdirAll(dir, 0755) 56 | 57 | if err != nil { 58 | return err 59 | } 60 | } 61 | 62 | out, err := os.Create(outPath) 63 | 64 | if err != nil { 65 | return err 66 | } 67 | 68 | logger.Printf(i18n.Get(i18n.Extracting, zf.Name, outPath)) 69 | if _, err = io.Copy(out, zipFile); err != nil { 70 | return err 71 | } 72 | 73 | return out.Sync() 74 | }() 75 | 76 | if err != nil { 77 | zipErrors = append(zipErrors, err) 78 | } 79 | } 80 | 81 | err = errors.Join(zipErrors...) 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /internal/app/lang/grammars/toml/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | toml 5 | jar 6 | ANTLR toml grammar 7 | 8 | org.antlr.grammars 9 | grammarsv4 10 | 1.0-SNAPSHOT 11 | 12 | 13 | 14 | 15 | org.antlr 16 | antlr4-maven-plugin 17 | ${antlr.version} 18 | 19 | ${basedir} 20 | 21 | TomlLexer.g4 22 | TomlParser.g4 23 | 24 | true 25 | true 26 | 27 | 28 | 29 | 30 | antlr4 31 | 32 | 33 | 34 | 35 | 36 | com.khubla.antlr 37 | antlr4test-maven-plugin 38 | ${antlr4test-maven-plugin.version} 39 | 40 | false 41 | false 42 | document 43 | Toml 44 | 45 | examples/ 46 | 47 | 48 | 49 | 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /internal/app/network/artifacts_search.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/ktorio/ktor-cli/internal/app" 8 | "github.com/ktorio/ktor-cli/internal/app/config" 9 | "net/http" 10 | ) 11 | 12 | type Artifact struct { 13 | Name string `json:"artifact"` 14 | Group string `json:"group"` 15 | IsTest bool `json:"isTest"` 16 | Distance int `json:"distance"` 17 | } 18 | 19 | type NotSupportedKtorVersion struct { 20 | Version string 21 | } 22 | 23 | func (e NotSupportedKtorVersion) Error() string { 24 | return fmt.Sprintf("unsupported Ktor version %s", e.Version) 25 | } 26 | 27 | func SearchArtifacts(client *http.Client, ktorVersion string, searches []string) (map[string][]Artifact, error) { 28 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/artifacts/%s/search", config.GenBaseUrl(), ktorVersion), nil) 29 | 30 | if err != nil { 31 | return nil, &app.Error{Err: err, Kind: app.InternalError} 32 | } 33 | 34 | qVals := req.URL.Query() 35 | 36 | for _, s := range searches { 37 | qVals.Add("q", s) 38 | } 39 | 40 | req.URL.RawQuery = qVals.Encode() 41 | 42 | resp, err := client.Do(req) 43 | 44 | if err != nil { 45 | return nil, ConvertResponseError(err, app.ArtifactSearchError) 46 | } 47 | 48 | defer resp.Body.Close() 49 | 50 | tag := fmt.Sprintf("search artifacts for %s", ktorVersion) 51 | if err = CheckResponseStatus(resp, tag, app.ArtifactSearchError); err != nil { 52 | if resp.StatusCode == http.StatusNotImplemented { 53 | return nil, &app.Error{Err: &NotSupportedKtorVersion{Version: ktorVersion}, Kind: app.ArtifactSearchVersionNotSupportedError} 54 | } 55 | 56 | return nil, err 57 | } 58 | 59 | dec := json.NewDecoder(resp.Body) 60 | result := make(map[string][]Artifact) 61 | err = dec.Decode(&result) 62 | 63 | if err != nil { 64 | return nil, app.Error{Err: errors.New(fmt.Sprintf("%s: %s", tag, err.Error())), Kind: app.ArtifactSearchError} 65 | } 66 | 67 | return result, nil 68 | } 69 | -------------------------------------------------------------------------------- /internal/app/progress/percent.go: -------------------------------------------------------------------------------- 1 | package progress 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app/utils" 6 | "golang.org/x/term" 7 | "io" 8 | "os" 9 | ) 10 | 11 | type Percent struct { 12 | enabled bool 13 | Writer io.Writer 14 | total int 15 | current int 16 | prefix string 17 | } 18 | 19 | func NewReader(r io.Reader, prefix string, total int, enabled bool) (io.Reader, *Percent) { 20 | progressBar := newPercent(prefix, total, enabled) 21 | return io.TeeReader(r, progressBar), progressBar 22 | } 23 | 24 | func NewReaderAt(r io.ReaderAt, prefix string, total int, enabled bool) (io.ReaderAt, *Percent) { 25 | progressBar := newPercent(prefix, total, enabled) 26 | return utils.TeeReaderAt(r, progressBar), progressBar 27 | } 28 | 29 | func newPercent(prefix string, total int, enabled bool) *Percent { 30 | writer := os.Stderr 31 | return &Percent{ 32 | prefix: prefix, 33 | total: total, 34 | enabled: enabled && term.IsTerminal(int(writer.Fd())), 35 | Writer: writer, 36 | } 37 | } 38 | 39 | func (b *Percent) reset() { 40 | b.current = 0 41 | } 42 | 43 | func (b *Percent) Write(p []byte) (n int, err error) { 44 | return b.tick(p) 45 | } 46 | 47 | func (b *Percent) WriteAt(p []byte, _ int64) (n int, err error) { 48 | return b.tick(p) 49 | } 50 | 51 | func (b *Percent) tick(p []byte) (n int, err error) { 52 | if !b.enabled { 53 | return len(p), nil 54 | } 55 | 56 | b.current += len(p) 57 | 58 | err = clearCurrentLine(b.Writer) 59 | if err != nil { 60 | return 0, err 61 | } 62 | 63 | fmt.Fprintf(b.Writer, "%s%d%%", b.prefix, int(float32(b.current)/float32(b.total)*100)) 64 | return len(p), nil 65 | } 66 | 67 | func (b *Percent) Done() (err error) { 68 | if !b.enabled { 69 | return nil 70 | } 71 | 72 | err = clearCurrentLine(b.Writer) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | fmt.Fprintf(b.Writer, "%s100%%\n", b.prefix) 78 | return 79 | } 80 | 81 | func (b *Percent) Stop() (err error) { 82 | if !b.enabled { 83 | return nil 84 | } 85 | 86 | fmt.Fprintln(b.Writer) 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /packInstaller.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$toolPath = ".\build\windows\amd64\ktor.exe", 3 | [string]$outPath = "ktor-installer.msi", 4 | [string]$wixExe = "wix.exe" 5 | ) 6 | 7 | $version = $(git describe --tags --contains --always --abbrev=7) 8 | 9 | if (!($version -match '\d+\.\d+.\d+')) { 10 | Write-Error "Expected version in the format *.*.*, got ${version}" 11 | exit 1 12 | } 13 | 14 | $wixProduct = @" 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | "@ 42 | 43 | $wixProduct | out-file -filepath KtorProduct.wxs 44 | & $wixExe build -arch x64 -o $outPath -ext WixToolset.UI.wixext KtorProduct.wxs 45 | (Get-FileHash -Path $outPath -Algorithm Sha256).Hash | out-file checksum.txt -NoNewline 46 | Remove-Item KtorProduct.wxs -------------------------------------------------------------------------------- /internal/app/jdk/download.go: -------------------------------------------------------------------------------- 1 | package jdk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/ktorio/ktor-cli/internal/app" 7 | "github.com/ktorio/ktor-cli/internal/app/config" 8 | "github.com/ktorio/ktor-cli/internal/app/i18n" 9 | "github.com/ktorio/ktor-cli/internal/app/progress" 10 | "github.com/ktorio/ktor-cli/internal/app/utils" 11 | "io" 12 | "log" 13 | "net/http" 14 | ) 15 | 16 | func DownloadJdk(client *http.Client, d *Descriptor, logger *log.Logger) ([]byte, error) { 17 | if !hasJdkBuild(d) { 18 | return nil, &app.Error{Err: Error{d}, Kind: app.UnableLocateJdkError} 19 | } 20 | 21 | ext := "tar.gz" 22 | if d.Platform == "windows" { 23 | ext = "zip" 24 | } 25 | 26 | url := fmt.Sprintf("%s/downloads/latest/amazon-corretto-%s-%s-%s-jdk.%s", config.CorrettoBaseUrl(), d.Version, d.Arch, d.Platform, ext) 27 | logger.Printf(i18n.Get(i18n.DownloadingJdk, d, url)) 28 | 29 | resp, err := client.Get(url) 30 | if err != nil { 31 | return nil, &app.Error{Err: err, Kind: app.JdkServerError} 32 | } 33 | 34 | defer resp.Body.Close() 35 | 36 | if resp.StatusCode != http.StatusOK { 37 | return nil, &app.Error{ 38 | Err: errors.New(fmt.Sprintf("download jdk: bad status code %d", resp.StatusCode)), 39 | Kind: app.JdkServerError, 40 | } 41 | } 42 | 43 | reader, progressBar := progress.NewReader(resp.Body, i18n.Get(i18n.DownloadingJdkProgress), utils.ContentLength(resp), true) 44 | b, err := io.ReadAll(reader) 45 | 46 | if err != nil { 47 | progressBar.Stop() 48 | return nil, &app.Error{Err: err, Kind: app.JdkServerDownloadError} 49 | } 50 | 51 | progressBar.Done() 52 | return b, nil 53 | } 54 | 55 | func hasJdkBuild(d *Descriptor) bool { 56 | if d.Version != "11" && d.Version != "17" && d.Version != "21" { 57 | return false 58 | } 59 | 60 | switch d.Platform { 61 | case "linux": 62 | if d.Arch == "x64" || d.Arch == "aarch64" { 63 | return true 64 | } 65 | case "windows": 66 | if d.Arch == "x64" { 67 | return true 68 | } 69 | case "macos": 70 | if d.Arch == "x64" || d.Arch == "aarch64" { 71 | return true 72 | } 73 | case "alpine": 74 | if d.Arch == "x64" || d.Arch == "aarch64" { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | -------------------------------------------------------------------------------- /internal/app/archive/targz.go: -------------------------------------------------------------------------------- 1 | package archive 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/gzip" 6 | "errors" 7 | "github.com/ktorio/ktor-cli/internal/app" 8 | "github.com/ktorio/ktor-cli/internal/app/i18n" 9 | "github.com/ktorio/ktor-cli/internal/app/utils" 10 | "io" 11 | "log" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | "strings" 16 | ) 17 | 18 | func ExtractTarGz(r io.Reader, outDir string, logger *log.Logger) (rootDirs utils.StringSet, err error) { 19 | zr, err := gzip.NewReader(r) 20 | rootDirs = utils.NewStringSet() 21 | 22 | if err != nil { 23 | return rootDirs, err 24 | } 25 | 26 | defer zr.Close() 27 | 28 | tr := tar.NewReader(zr) 29 | 30 | var extractErrors []error 31 | for { 32 | header, err := tr.Next() 33 | 34 | if err == io.EOF { 35 | break 36 | } 37 | 38 | if err != nil { 39 | extractErrors = append(extractErrors, err) 40 | continue 41 | } 42 | 43 | err = func() error { 44 | switch header.Typeflag { 45 | case tar.TypeDir: 46 | isRootDir := strings.Count(header.Name, "/") == 1 47 | if isRootDir { 48 | rootDirs.Add(filepath.Join(outDir, header.Name)) 49 | } 50 | logger.Printf(i18n.Get(i18n.CreatingDir, path.Join(outDir, header.Name))) 51 | if err := os.Mkdir(path.Join(outDir, header.Name), os.FileMode(header.Mode&0xfff)); err != nil { 52 | if os.IsExist(err) && isRootDir { 53 | return &app.Error{Err: err, Kind: app.ExtractRootDirExistError} 54 | } 55 | 56 | return err 57 | } 58 | case tar.TypeReg: 59 | fp := path.Join(outDir, header.Name) 60 | outFile, err := os.Create(fp) 61 | 62 | if err != nil { 63 | return err 64 | } 65 | 66 | defer outFile.Close() 67 | logger.Printf(i18n.Get(i18n.Extracting, header.Name, fp)) 68 | if _, err := io.Copy(outFile, tr); err != nil { 69 | return err 70 | } 71 | 72 | err = outFile.Sync() 73 | if err != nil { 74 | return err 75 | } 76 | 77 | err = os.Chmod(fp, os.FileMode(header.Mode&0xfff)) 78 | if err != nil { 79 | return err 80 | } 81 | } 82 | 83 | return nil 84 | }() 85 | 86 | if err != nil { 87 | extractErrors = append(extractErrors, err) 88 | } 89 | 90 | var appError *app.Error 91 | if errors.As(err, &appError) && appError.Kind == app.ExtractRootDirExistError { 92 | break 93 | } 94 | } 95 | 96 | err = errors.Join(extractErrors...) 97 | return 98 | } 99 | -------------------------------------------------------------------------------- /internal/app/network/project.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/ktorio/ktor-cli/internal/app" 9 | "github.com/ktorio/ktor-cli/internal/app/config" 10 | "github.com/ktorio/ktor-cli/internal/app/i18n" 11 | "github.com/ktorio/ktor-cli/internal/app/progress" 12 | "github.com/ktorio/ktor-cli/internal/app/utils" 13 | "io" 14 | "net/http" 15 | ) 16 | 17 | type ProjectPayload struct { 18 | Settings ProjectSettings `json:"settings"` 19 | Plugins []string `json:"features"` 20 | HasSampleCode bool `json:"addDefaultRoutes"` 21 | ConfigType string `json:"configurationOption"` 22 | HasWrapper bool `json:"addWrapper"` 23 | } 24 | 25 | type ProjectSettings struct { 26 | Name string `json:"project_name"` 27 | CompanyWebsite string `json:"company_website"` 28 | Engine string `json:"engine"` 29 | BuildSystem string `json:"build_system"` 30 | KtorVersion string `json:"ktor_version"` 31 | KotlinVersion string `json:"kotlin_version"` 32 | BuildSystemArgs map[BuildSystemArgs]string `json:"build_system_args"` 33 | } 34 | 35 | type BuildSystemArgs string 36 | 37 | const VersionCatalogBuildArg BuildSystemArgs = "version_catalog" 38 | 39 | func NewProject(client *http.Client, payload ProjectPayload, ctx context.Context) ([]byte, error) { 40 | var body bytes.Buffer 41 | err := json.NewEncoder(&body).Encode(payload) 42 | if err != nil { 43 | return nil, &app.Error{Err: err, Kind: app.InternalError} 44 | } 45 | 46 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/project/generate", config.GenBaseUrl()), &body) 47 | 48 | if err != nil { 49 | return nil, &app.Error{Err: err, Kind: app.InternalError} 50 | } 51 | 52 | req.Header.Set("Content-Type", "application/json") 53 | req.Header.Set("User-Agent", ctx.Value("user-agent").(string)) 54 | 55 | resp, err := client.Do(req) 56 | 57 | if err != nil { 58 | return nil, ConvertResponseError(err, app.GenServerError) 59 | } 60 | 61 | defer resp.Body.Close() 62 | 63 | if err = CheckResponseStatus(resp, "new project", app.GenServerError); err != nil { 64 | return nil, err 65 | } 66 | 67 | reader, progressBar := progress.NewReader( 68 | resp.Body, 69 | i18n.Get(i18n.DownloadingProjectArchiveProgress), 70 | utils.ContentLength(resp), 71 | true, 72 | ) 73 | defer progressBar.Done() 74 | 75 | bodyBytes, err := io.ReadAll(reader) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return bodyBytes, nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/app/interactive/draw/state.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | type Range struct { 6 | start, end int 7 | } 8 | 9 | type CursorGlobalInfo struct { 10 | R rune 11 | X, Y int 12 | } 13 | 14 | type State struct { 15 | CursorOffs map[Element]int 16 | ActiveElement Element 17 | PluginsShown bool 18 | tabVisRanges []Range 19 | pluginVisRanges []Range 20 | ActiveTab int 21 | ActivePlugin int 22 | VisibleOffs map[Element]int 23 | InputLens map[Element]int 24 | } 25 | 26 | type Element int 27 | 28 | const ( 29 | ProjectNameInput Element = iota 30 | LocationInput 31 | SearchInput 32 | Tabs 33 | CreateButton 34 | LastElement 35 | ) 36 | 37 | func NewState() *State { 38 | return &State{ 39 | CursorOffs: map[Element]int{ 40 | ProjectNameInput: 0, 41 | LocationInput: 0, 42 | SearchInput: 0, 43 | }, 44 | VisibleOffs: map[Element]int{ 45 | ProjectNameInput: 0, 46 | LocationInput: 0, 47 | SearchInput: 0, 48 | }, 49 | InputLens: map[Element]int{ 50 | ProjectNameInput: 0, 51 | LocationInput: 0, 52 | SearchInput: 0, 53 | }, 54 | } 55 | } 56 | 57 | func ActiveInputOffset(st *State) int { 58 | return st.CursorOffs[st.ActiveElement] 59 | } 60 | 61 | func IsElementActive(st *State, element Element) bool { 62 | return st.ActiveElement == element 63 | } 64 | 65 | func MoveCursor(st *State, inputLen, delta int) { 66 | off := ActiveInputOffset(st) 67 | 68 | off += delta 69 | if off > inputLen { 70 | off = inputLen 71 | } 72 | 73 | if off < 0 { 74 | off = 0 75 | } 76 | 77 | st.CursorOffs[st.ActiveElement] = off 78 | } 79 | 80 | func SwitchTab(st *State, numTabs int, delta int) { 81 | newTab := st.ActiveTab + delta 82 | 83 | if newTab < 0 { 84 | return 85 | } 86 | 87 | if newTab >= numTabs { 88 | return 89 | } 90 | 91 | st.ActiveTab = newTab 92 | st.ActivePlugin = 0 93 | } 94 | 95 | func SwitchElement(st *State, delta int) { 96 | newElement := int(st.ActiveElement) + delta 97 | 98 | if newElement < 0 { 99 | return 100 | } 101 | 102 | if newElement >= int(LastElement) { 103 | return 104 | } 105 | 106 | st.ActiveElement = Element(newElement) 107 | } 108 | 109 | func (st *State) VisOff() int { 110 | return st.VisibleOffs[st.ActiveElement] 111 | } 112 | 113 | func (st *State) CursorPos() int { 114 | return st.CursorOffs[st.ActiveElement] 115 | } 116 | 117 | func (st *State) InputLen() int { 118 | return st.InputLens[st.ActiveElement] 119 | } 120 | 121 | func HideCursorIfNeeded(st *State, scr tcell.Screen) { 122 | if st.ActiveElement != ProjectNameInput && st.ActiveElement != LocationInput && st.ActiveElement != SearchInput { 123 | scr.HideCursor() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /internal/app/jdk/fetch.go: -------------------------------------------------------------------------------- 1 | package jdk 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/ktorio/ktor-cli/internal/app" 7 | "github.com/ktorio/ktor-cli/internal/app/archive" 8 | "github.com/ktorio/ktor-cli/internal/app/i18n" 9 | "github.com/ktorio/ktor-cli/internal/app/progress" 10 | "github.com/ktorio/ktor-cli/internal/app/utils" 11 | "io" 12 | "log" 13 | "net/http" 14 | "os" 15 | "path/filepath" 16 | "runtime" 17 | "sync" 18 | ) 19 | 20 | func fetch(client *http.Client, d *Descriptor, outDir string, logger *log.Logger) (string, error) { 21 | jdkBytes, err := DownloadJdk(client, d, logger) 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | var wg sync.WaitGroup 27 | 28 | var jdkValid bool 29 | var verifyErr error 30 | wg.Add(1) 31 | go func() { 32 | jdkValid, verifyErr = Verify(client, d, bytes.NewReader(jdkBytes), logger) 33 | wg.Done() 34 | }() 35 | 36 | wg.Add(1) 37 | var extractErr error 38 | var extractDir string 39 | go func() { 40 | defer wg.Done() 41 | extractedDirs := utils.NewStringSet() 42 | logger.Printf(i18n.Get(i18n.ExtractingJdkFiles, outDir)) 43 | 44 | if d.Platform == "windows" { 45 | reader, progressBar := progress.NewReaderAt( 46 | bytes.NewReader(jdkBytes), 47 | i18n.Get(i18n.ExtractingJdkProgress), 48 | len(jdkBytes), 49 | logger.Writer() == io.Discard, 50 | ) 51 | defer progressBar.Done() 52 | 53 | extractedDirs, extractErr = archive.ExtractZip(reader, int64(len(jdkBytes)), outDir, logger) 54 | } else { 55 | reader, progressBar := progress.NewReader( 56 | bytes.NewReader(jdkBytes), 57 | i18n.Get(i18n.ExtractingJdkProgress), 58 | len(jdkBytes), 59 | logger.Writer() == io.Discard, 60 | ) 61 | defer progressBar.Done() 62 | 63 | extractedDirs, extractErr = archive.ExtractTarGz(reader, outDir, logger) 64 | } 65 | 66 | if extractErr != nil { 67 | return 68 | } 69 | 70 | dir, extractErr := extractedDirs.Single() 71 | 72 | if extractErr != nil { 73 | return 74 | } 75 | 76 | extractDir = dir 77 | }() 78 | 79 | wg.Wait() 80 | if verifyErr != nil { 81 | if extractErr == nil { 82 | os.RemoveAll(extractDir) 83 | } 84 | 85 | return "", &app.Error{Err: verifyErr, Kind: app.JdkVerificationFailed} 86 | } 87 | 88 | if extractErr != nil { 89 | return "", &app.Error{Err: extractErr, Kind: app.JdkExtractError} 90 | } 91 | 92 | if !jdkValid { 93 | if extractErr == nil { 94 | os.RemoveAll(extractDir) 95 | } 96 | 97 | return "", &app.Error{Err: errors.New("verify jdk: downloaded JDK checksum failed"), Kind: app.JdkVerificationFailed} 98 | } 99 | 100 | if runtime.GOOS == "darwin" { 101 | extractDir = filepath.Join(extractDir, "Contents", "Home") 102 | } 103 | 104 | return extractDir, nil 105 | } 106 | -------------------------------------------------------------------------------- /internal/app/project/project.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app/ktor" 6 | "github.com/ktorio/ktor-cli/internal/app/lang/gradle" 7 | "github.com/ktorio/ktor-cli/internal/app/lang/toml" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func SearchKtorVersion(projectDir string, build *gradle.BuildRoot, tomlDoc *toml.Document, tomlSuccessParsed bool) (version string, found bool) { 13 | found = true 14 | 15 | for _, p := range build.Plugins.List { 16 | if p.Prefix == "id" && p.Id == "io.ktor.plugin" { 17 | version = p.Version 18 | return 19 | } 20 | } 21 | 22 | for _, d := range build.Dependencies.List { 23 | if d.IsKtorBom { 24 | if mc, ok := ktor.ParseMavenCoords(d.PlatformPath); ok && mc.Version != "" { 25 | version = mc.Version 26 | break 27 | } 28 | } else if d.Kind == gradle.HardcodedDep { 29 | if mc, ok := ktor.ParseMavenCoords(d.Path); ok && mc.Group == ktor.MavenGroup && mc.Version != "" { 30 | version = mc.Version 31 | break 32 | } 33 | } 34 | } 35 | 36 | if version != "" && !strings.HasPrefix(version, "$") { 37 | return 38 | } else { 39 | props := gradle.ParseProps(filepath.Join(projectDir, "gradle.properties")) 40 | 41 | for _, v := range build.TopLevelVars { 42 | if v.Id == strings.TrimPrefix(version, "$") { 43 | if v.IsDelegate && v.Delegate == "project" { 44 | if val, ok := props[strings.TrimPrefix(version, "$")]; ok { 45 | version = val 46 | break 47 | } 48 | } else { 49 | version = v.StringVal 50 | break 51 | } 52 | } 53 | } 54 | 55 | if version != "" { 56 | return 57 | } 58 | } 59 | 60 | if tomlSuccessParsed { 61 | if t, ok := toml.FindTable(tomlDoc, "versions"); ok { 62 | for _, te := range t.Entries { 63 | if strings.HasPrefix(te.Key, "ktor") && te.Kind == toml.StringValue && te.String != "" { 64 | version = te.String 65 | return 66 | } 67 | } 68 | } 69 | } 70 | 71 | found = false 72 | return 73 | } 74 | 75 | func IsKmp(build *gradle.BuildRoot, tomlDoc *toml.Document, validToml bool) bool { 76 | for _, p := range build.Plugins.List { 77 | if (p.Prefix == "kotlin" && p.Id == "multiplatform") || (p.Prefix == "id" && p.Id == "org.jetbrains.kotlin.multiplatform") { 78 | return true 79 | } 80 | } 81 | 82 | if validToml { 83 | plugin, ok := toml.FindPlugin(tomlDoc, ktor.KmpPluginId) 84 | 85 | if !ok { 86 | return false 87 | } 88 | 89 | for _, p := range build.Plugins.List { 90 | if p.Prefix == "kotlin" && p.Id == "multiplatform" { 91 | return true 92 | } 93 | 94 | if p.Prefix == "alias" && p.Id == fmt.Sprintf("libs.plugins.%s", plugin.Key) { 95 | return true 96 | } 97 | } 98 | } 99 | 100 | return false 101 | } 102 | -------------------------------------------------------------------------------- /internal/app/lang/gradle/query.go: -------------------------------------------------------------------------------- 1 | package gradle 2 | 3 | import ( 4 | "github.com/ktorio/ktor-cli/internal/app/ktor" 5 | "strings" 6 | ) 7 | 8 | func FindKtorPlugin(plugins []Plugin) (*Plugin, bool) { 9 | for _, p := range plugins { 10 | if p.Prefix == "id" && p.Id == "io.ktor.plugin" { 11 | return &p, true 12 | } 13 | } 14 | 15 | return nil, false 16 | } 17 | 18 | func FindKotlinPlugin(plugins []Plugin) (*Plugin, bool) { 19 | for _, p := range plugins { 20 | if p.Prefix == "kotlin" && p.Id == "jvm" { 21 | return &p, true 22 | } 23 | } 24 | 25 | return nil, false 26 | } 27 | 28 | func HasSerializationPlugin(plugins []Plugin) bool { 29 | for _, p := range plugins { 30 | if (p.Prefix == "kotlin" && p.Id == ktor.SerPluginKotlinId) || (p.Prefix == "id" && p.Id == ktor.SerPluginId) { 31 | return true 32 | } 33 | } 34 | 35 | return false 36 | } 37 | 38 | func FindKtorDep(deps []Dep, preferTest bool) (*Dep, bool) { 39 | var lastKtorDep *Dep 40 | for _, dep := range deps { 41 | if dep.Kind == HardcodedDep { 42 | mc, ok := ktor.ParseMavenCoords(dep.Path) 43 | 44 | if !ok { 45 | continue 46 | } 47 | 48 | if mc.Group == "io.ktor" { 49 | lastKtorDep = &dep 50 | 51 | if !preferTest { 52 | return &dep, true 53 | } 54 | } 55 | 56 | if preferTest && dep.IsTest { 57 | return &dep, true 58 | } 59 | } 60 | 61 | if dep.Kind == VersionCatalogDep { 62 | if strings.HasPrefix(dep.Path, "libs.ktor") { 63 | return &dep, true 64 | } 65 | } 66 | } 67 | 68 | if lastKtorDep != nil { 69 | return lastKtorDep, true 70 | } 71 | 72 | return nil, false 73 | } 74 | 75 | func FindCatalogDep(build *BuildRoot, catalogKey string) (*Dep, bool) { 76 | for _, dep := range build.Dependencies.List { 77 | if dep.Kind == VersionCatalogDep && dep.Path == "libs."+strings.ReplaceAll(catalogKey, "-", ".") { 78 | return &dep, true 79 | } 80 | } 81 | 82 | return nil, false 83 | } 84 | 85 | func FindCatalogDepPrefixed(build *BuildRoot, prefix string) (*Dep, bool) { 86 | for _, dep := range build.Dependencies.List { 87 | if dep.Kind == VersionCatalogDep && strings.HasPrefix(dep.Path, prefix) { 88 | return &dep, true 89 | } 90 | } 91 | 92 | return nil, false 93 | } 94 | 95 | func FindDepFunc(deps []Dep, pred func(ktor.MavenCoords) bool) (*Dep, *ktor.MavenCoords, bool) { 96 | for _, dep := range deps { 97 | if coords, ok := ktor.ParseMavenCoords(dep.Path); ok && pred(coords) { 98 | return &dep, &coords, true 99 | } 100 | } 101 | 102 | return nil, nil, false 103 | } 104 | 105 | func FindVarDecl(decls []VarDecl, pred func(*VarDecl) bool) (*VarDecl, bool) { 106 | for _, vd := range decls { 107 | if pred(&vd) { 108 | return &vd, true 109 | } 110 | } 111 | 112 | return nil, false 113 | } 114 | -------------------------------------------------------------------------------- /internal/app/lang/common.go: -------------------------------------------------------------------------------- 1 | package lang 2 | 3 | import ( 4 | "fmt" 5 | "github.com/antlr4-go/antlr/v4" 6 | "strings" 7 | ) 8 | 9 | type SyntaxError struct { 10 | Line, Col int 11 | Msg string 12 | } 13 | 14 | func StringifySyntaxErrors(errors []SyntaxError) string { 15 | var sb strings.Builder 16 | 17 | sep := "" 18 | for _, e := range errors { 19 | sb.WriteString(sep) 20 | sb.WriteString(fmt.Sprintf("line %d:%d %s", e.Line, e.Col, e.Msg)) 21 | sep = "\n" 22 | } 23 | 24 | return fmt.Sprintf("syntax error[s]:\n%s", sb.String()) 25 | } 26 | 27 | type ErrorListener struct { 28 | *antlr.DefaultErrorListener 29 | Errors []SyntaxError 30 | } 31 | 32 | func NewErrorListener() *ErrorListener { 33 | return &ErrorListener{DefaultErrorListener: antlr.NewDefaultErrorListener()} 34 | } 35 | 36 | func (d *ErrorListener) SyntaxError(_ antlr.Recognizer, _ interface{}, line, col int, msg string, _ antlr.RecognitionException) { 37 | d.Errors = append(d.Errors, SyntaxError{Line: line, Col: col, Msg: msg}) 38 | } 39 | 40 | var DefaultIndent = strings.Repeat(" ", 4) 41 | 42 | func Quote(s string) string { 43 | if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") { 44 | return s 45 | } 46 | 47 | return "\"" + s + "\"" 48 | } 49 | 50 | func Unquote(s string) string { 51 | if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) { 52 | runes := []rune(s) 53 | return string(runes[1 : len(runes)-1]) 54 | } 55 | return s 56 | } 57 | 58 | func FindChild[T any](tree antlr.Tree) (T, bool) { 59 | var zero T 60 | 61 | for ; tree.GetChildCount() > 0; tree = tree.GetChild(0) { 62 | if ch, ok := tree.(T); ok { 63 | return ch, true 64 | } 65 | } 66 | 67 | return zero, false 68 | } 69 | 70 | func HiddenTokensToLeft(stream *antlr.CommonTokenStream, tokenIndex int) string { 71 | indent := "" 72 | for _, t := range stream.GetHiddenTokensToLeft(tokenIndex, antlr.TokenHiddenChannel) { 73 | indent += t.GetText() 74 | } 75 | return indent 76 | } 77 | 78 | // ToIndentedStringTree is useful for debugging 79 | // 80 | // goland:noinspection GoUnusedFunction 81 | func ToIndentedStringTree(tree antlr.Tree, ruleNames []string, level int) string { 82 | if tree == nil { 83 | return "" 84 | } 85 | 86 | indent := "" 87 | for i := 0; i < level; i++ { 88 | indent += " " 89 | } 90 | 91 | switch t := tree.(type) { 92 | case antlr.TerminalNode: 93 | token := t.GetSymbol() 94 | return fmt.Sprintf("%sTOKEN: %s\n", indent, token.GetText()) 95 | case antlr.RuleNode: 96 | ruleName := ruleNames[t.GetRuleContext().GetRuleIndex()] 97 | result := fmt.Sprintf("%sRULE: %s\n", indent, ruleName) 98 | for i := 0; i < t.GetChildCount(); i++ { 99 | result += ToIndentedStringTree(t.GetChild(i), ruleNames, level+1) 100 | } 101 | return result 102 | default: 103 | return fmt.Sprintf("%sUNKNOWN NODE\n", indent) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /internal/app/i18n/messages.go: -------------------------------------------------------------------------------- 1 | package i18n 2 | 3 | type Message int 4 | 5 | const ( 6 | CannotDetermineHomeDir Message = iota 7 | CannotDetermineProjectDirOfProject 8 | CannotDetermineProjectDir 9 | ErrorInitLogFile 10 | VersionInfo 11 | LogHint 12 | ProjectCreated 13 | JDKDetectedJavaHome 14 | JdkDetected 15 | JdkFoundLocally 16 | JdkDownloaded 17 | JdkVerificationFailed 18 | GenServerError 19 | GenServerTimeoutError 20 | NetworkError 21 | InternalError 22 | ProjectDirExistAndNotEmpty 23 | NoPermsCreateProjectDir 24 | ProjectExtractError 25 | JdkExtractError 26 | DirAlreadyExist 27 | UnableExtractJdk 28 | UnableDownloadJdk 29 | JdkServerError 30 | JdkServerDownloadError 31 | ChecksumVerificationFailed 32 | UnableMakeFileExec 33 | UnexpectedError 34 | UnexpectedErrorWithArg 35 | UnableCreateStoreJdkDir 36 | UnrecognizedFlagsError 37 | NoCommandError 38 | CommandNotFoundError 39 | CommandArgumentsError 40 | ToRunProject 41 | JavaHomeJdkIdeaInstruction 42 | ToolSummary 43 | OptionsCaption 44 | CommandsCaption 45 | VerifyingJdk 46 | CreatingDir 47 | Extracting 48 | RequestGenServer 49 | ExtractingArchiveToDir 50 | ExtractProjectArchive 51 | MakeFileExec 52 | UsageLine 53 | TermHeightSmall 54 | SelectedPluginsCount 55 | ProjectNameCaption 56 | LocationCaption 57 | SearchPluginsCaption 58 | CreateProjectButton 59 | NoPluginsFound 60 | DirNotEmptyError 61 | DirNotExist 62 | ProjectDirLong 63 | DownloadingJdk 64 | DownloadingJdkProgress 65 | ExtractingJdkFiles 66 | ExtractingJdkProgress 67 | ByeMessage 68 | UnableFetchPluginsError 69 | FetchingJdk 70 | DownloadingProjectArchiveProgress 71 | ProjectNameRequired 72 | ProjectNameAllowedChars 73 | DownloadOpenAPIJarError 74 | OpenApiExecuteJarError 75 | ExternalCommandError 76 | OpenApiSpecNotExist 77 | CreateOpenApiJar 78 | ExecutingCommand 79 | FlagRequiresArgument 80 | DownloadingOpenApiJarProgress 81 | NewCommandDescr 82 | VersionCommandDescr 83 | HelpCommandDescr 84 | OpenApiCommandDescr 85 | AddCommandDescr 86 | CompletionCommandDescr 87 | VerboseOptionDescr 88 | OutputDirOptionDescr 89 | ProjectDirOptionDescr 90 | ProjectCreatedIn 91 | AddKtorModulesToKmpError 92 | AddKtorModulesToMavenError 93 | AddKtorModulesToGradleGroovyError 94 | UnableToFindBuildGradleKts 95 | DetectedKtorVersion 96 | UseLatestKtorVersion 97 | UnableToRecognizeKtorModule 98 | KtorModuleAmbiguity 99 | SimilarModuleQuestion 100 | ChosenKtorModule 101 | ChangesWarningBlock 102 | ApplyChangesQuestion 103 | ChangesApplied 104 | NoChanges 105 | UnrecoverableErrorBlock 106 | SearchKtorModulesError 107 | ListKtorModulesError 108 | BackupCreationError 109 | WriteChangesError 110 | UnrecognizedShellError 111 | NoPermsForFile 112 | UnrecognizedCommandFlagsError 113 | ProjectAddMessage 114 | UnsupportedKtorVersionError 115 | DevCommandDescr 116 | GradleWrapperNotExistErr 117 | KtorGradlePluginNotFound 118 | StartingCommandMsg 119 | ErrorExecutingCommandMsg 120 | ) 121 | -------------------------------------------------------------------------------- /internal/app/generate/project.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "github.com/ktorio/ktor-cli/internal/app" 8 | "github.com/ktorio/ktor-cli/internal/app/archive" 9 | "github.com/ktorio/ktor-cli/internal/app/i18n" 10 | "github.com/ktorio/ktor-cli/internal/app/network" 11 | "github.com/ktorio/ktor-cli/internal/app/progress" 12 | "github.com/ktorio/ktor-cli/internal/app/utils" 13 | "io" 14 | "log" 15 | "net/http" 16 | "os" 17 | "path" 18 | ) 19 | 20 | // Project Returns *app.Error on error 21 | func Project(client *http.Client, logger *log.Logger, projectDir, project string, plugins []string, ctx context.Context) error { 22 | err := os.MkdirAll(projectDir, 0755) 23 | logger.Printf(i18n.Get(i18n.CreatingDir, projectDir)) 24 | 25 | if _, err = os.Stat(projectDir); errors.Is(err, os.ErrNotExist) { 26 | if err != nil { 27 | var pe *os.PathError 28 | errors.As(err, &pe) 29 | 30 | if errors.Is(pe.Err, os.ErrExist) || errors.Is(pe.Err, os.ErrPermission) { 31 | return &app.Error{Err: err, Kind: app.ProjectDirError} 32 | } 33 | 34 | return &app.Error{Err: err, Kind: app.UnknownError} 35 | } 36 | } else if !utils.IsDirEmpty(projectDir) { 37 | return &app.Error{Err: &os.PathError{Err: os.ErrExist, Path: projectDir}, Kind: app.ProjectDirError} 38 | } 39 | 40 | logger.Printf(i18n.Get(i18n.CreatingDir, projectDir)) 41 | 42 | settings, err := network.FetchSettings(client) 43 | 44 | if err != nil { 45 | return err 46 | } 47 | 48 | logger.Println(i18n.Get(i18n.RequestGenServer)) 49 | projectPayload := network.ProjectPayload{ 50 | Settings: network.ProjectSettings{ 51 | Name: project, 52 | CompanyWebsite: "com.example.com", 53 | Engine: settings.Engine.DefaultId, 54 | BuildSystem: settings.BuildSystem.DefaultId, 55 | KtorVersion: settings.KtorVersion.DefaultId, 56 | KotlinVersion: settings.KotlinVersion.DefaultId, 57 | BuildSystemArgs: map[network.BuildSystemArgs]string{ 58 | network.VersionCatalogBuildArg: "", 59 | }, 60 | }, 61 | Plugins: plugins, 62 | HasSampleCode: true, 63 | ConfigType: settings.ConfigType.DefaultId, 64 | HasWrapper: true, 65 | } 66 | 67 | zipBytes, err := network.NewProject(client, projectPayload, ctx) 68 | 69 | if err != nil { 70 | if os.IsTimeout(err) { 71 | return &app.Error{Err: err, Kind: app.GenServerTimeoutError} 72 | } 73 | 74 | return &app.Error{Err: err, Kind: app.GenServerError} 75 | } 76 | 77 | logger.Printf(i18n.Get(i18n.ExtractingArchiveToDir, projectDir)) 78 | 79 | reader, progressBar := progress.NewReaderAt( 80 | bytes.NewReader(zipBytes), 81 | i18n.Get(i18n.ExtractProjectArchive), 82 | len(zipBytes), 83 | logger.Writer() == io.Discard, 84 | ) 85 | defer progressBar.Done() 86 | 87 | _, err = archive.ExtractZip(reader, int64(len(zipBytes)), projectDir, logger) 88 | 89 | if err != nil { 90 | return &app.Error{Err: err, Kind: app.ProjectExtractError} 91 | } 92 | 93 | gradlewPath := path.Join(projectDir, "gradlew") 94 | logger.Printf(i18n.Get(i18n.MakeFileExec, gradlewPath)) 95 | err = os.Chmod(gradlewPath, 0764) 96 | 97 | if err != nil { 98 | return &app.Error{Err: err, Kind: app.GradlewChmodError} 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /internal/app/cli/command/openapi.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/ktorio/ktor-cli/internal/app" 7 | "github.com/ktorio/ktor-cli/internal/app/cli" 8 | "github.com/ktorio/ktor-cli/internal/app/config" 9 | "github.com/ktorio/ktor-cli/internal/app/i18n" 10 | "github.com/ktorio/ktor-cli/internal/app/jdk" 11 | "github.com/ktorio/ktor-cli/internal/app/network" 12 | "github.com/ktorio/ktor-cli/internal/app/openapi" 13 | "github.com/ktorio/ktor-cli/internal/app/utils" 14 | "log" 15 | "net/http" 16 | "os" 17 | "os/exec" 18 | "path/filepath" 19 | "strings" 20 | "syscall" 21 | ) 22 | 23 | func OpenApi(client *http.Client, specPath string, projectName, projectDir string, homeDir string, logger *log.Logger) error { 24 | err := os.MkdirAll(projectDir, 0755) 25 | logger.Printf(i18n.Get(i18n.CreatingDir, projectDir)) 26 | 27 | var pe *os.PathError 28 | 29 | if errors.As(err, &pe) && (errors.Is(pe.Err, syscall.EROFS) || errors.Is(pe.Err, syscall.EPERM)) { 30 | return &app.Error{Err: err, Kind: app.ProjectDirError} 31 | } 32 | 33 | if _, err := os.Stat(projectDir); errors.Is(err, os.ErrNotExist) { 34 | return &app.Error{Err: err, Kind: app.UnknownError} 35 | } else if !utils.IsDirEmpty(projectDir) { 36 | return &app.Error{Err: &os.PathError{Err: os.ErrExist, Path: projectDir}, Kind: app.ProjectDirError} 37 | } 38 | 39 | jarName := filepath.Base(config.OpenApiJarUrl()) 40 | jarPath := filepath.Join(config.TempDir(homeDir), jarName) 41 | 42 | if _, err := os.Stat(jarPath); errors.Is(err, os.ErrNotExist) { 43 | jarBytes, err := openapi.DownloadJar(client, config.OpenApiJarUrl()) 44 | 45 | if err != nil { 46 | return err 47 | } 48 | 49 | f, err := os.Create(jarPath) 50 | logger.Printf(i18n.Get(i18n.CreateOpenApiJar, jarPath)) 51 | 52 | if err != nil { 53 | return &app.Error{Err: err, Kind: app.OpenApiDownloadJarError} 54 | } 55 | 56 | defer f.Close() 57 | 58 | _, err = f.Write(jarBytes) 59 | 60 | if err != nil { 61 | return &app.Error{Err: err, Kind: app.OpenApiDownloadJarError} 62 | } 63 | } 64 | 65 | src, jdkPath, err := cli.ObtainJdk(client, logger, homeDir) 66 | 67 | if err != nil { 68 | return err 69 | } 70 | 71 | if src == jdk.Downloaded { 72 | fmt.Printf(i18n.Get(i18n.JdkDownloaded, jdkPath)) 73 | } 74 | 75 | settings, err := network.FetchSettings(client) 76 | 77 | if err != nil { 78 | return err 79 | } 80 | 81 | javaExec := filepath.Join(jdkPath, "bin", "java") 82 | 83 | c := []string{javaExec, "-jar", jarPath, "generate", "-g", "kotlin-server", "-i", specPath, 84 | "--artifact-id", projectName, "--package-name", utils.GetPackage(settings.CompanyWebsite.DefaultVal), "-o", projectDir} 85 | 86 | logger.Printf(i18n.Get(i18n.ExecutingCommand, strings.Join(c, " "))) 87 | 88 | cmd := exec.Command(javaExec, c[1:]...) 89 | 90 | stdout, err := cmd.Output() 91 | 92 | var ee *exec.ExitError 93 | if errors.As(err, &ee) { 94 | msg := string(ee.Stderr) 95 | 96 | if strings.Contains(msg, "Unable to access jarfile") { 97 | return &app.Error{Err: err, Kind: app.OpenApiExecuteJarError} 98 | } 99 | 100 | return &app.Error{Err: errors.New(msg), Kind: app.ExternalCommandError} 101 | } 102 | 103 | if err != nil { 104 | return &app.Error{Err: err, Kind: app.UnknownError} 105 | } 106 | 107 | logger.Println(string(stdout)) 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /internal/app/lang/grammars/toml/TomlParser.g4: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false 21 | // $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging 22 | 23 | parser grammar TomlParser; 24 | 25 | options { 26 | tokenVocab = TomlLexer; 27 | } 28 | 29 | document 30 | : expression (NL expression)* EOF 31 | ; 32 | 33 | expression 34 | : key_value comment 35 | | table comment 36 | | comment 37 | ; 38 | 39 | comment 40 | : COMMENT? 41 | ; 42 | 43 | key_value 44 | : key EQUALS value 45 | ; 46 | 47 | key 48 | : simple_key 49 | | dotted_key 50 | ; 51 | 52 | simple_key 53 | : quoted_key 54 | | unquoted_key 55 | ; 56 | 57 | unquoted_key 58 | : UNQUOTED_KEY 59 | ; 60 | 61 | quoted_key 62 | : BASIC_STRING 63 | | LITERAL_STRING 64 | ; 65 | 66 | dotted_key 67 | : simple_key (DOT simple_key)+ 68 | ; 69 | 70 | value 71 | : string 72 | | integer 73 | | floating_point 74 | | bool_ 75 | | date_time 76 | | array_ 77 | | inline_table 78 | ; 79 | 80 | string 81 | : BASIC_STRING 82 | | ML_BASIC_STRING 83 | | LITERAL_STRING 84 | | ML_LITERAL_STRING 85 | ; 86 | 87 | integer 88 | : DEC_INT 89 | | HEX_INT 90 | | OCT_INT 91 | | BIN_INT 92 | ; 93 | 94 | floating_point 95 | : FLOAT 96 | | INF 97 | | NAN 98 | ; 99 | 100 | bool_ 101 | : BOOLEAN 102 | ; 103 | 104 | date_time 105 | : OFFSET_DATE_TIME 106 | | LOCAL_DATE_TIME 107 | | LOCAL_DATE 108 | | LOCAL_TIME 109 | ; 110 | 111 | array_ 112 | : L_BRACKET array_values? comment_or_nl R_BRACKET 113 | ; 114 | 115 | array_values 116 | : (comment_or_nl value nl_or_comment COMMA array_values comment_or_nl) 117 | | comment_or_nl value nl_or_comment COMMA? 118 | ; 119 | 120 | comment_or_nl 121 | : (COMMENT? NL)* 122 | ; 123 | 124 | nl_or_comment 125 | : (NL COMMENT?)* 126 | ; 127 | 128 | table 129 | : standard_table 130 | | array_table 131 | ; 132 | 133 | standard_table 134 | : L_BRACKET key R_BRACKET 135 | ; 136 | 137 | inline_table 138 | : L_BRACE inline_table_keyvals R_BRACE 139 | ; 140 | 141 | inline_table_keyvals 142 | : inline_table_keyvals_non_empty? 143 | ; 144 | 145 | inline_table_keyvals_non_empty 146 | : key EQUALS value (COMMA inline_table_keyvals_non_empty)? 147 | ; 148 | 149 | array_table 150 | : DOUBLE_L_BRACKET key DOUBLE_R_BRACKET 151 | ; -------------------------------------------------------------------------------- /internal/app/cli/command/dev.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/ktorio/ktor-cli/internal/app" 7 | "github.com/ktorio/ktor-cli/internal/app/cli" 8 | "github.com/ktorio/ktor-cli/internal/app/i18n" 9 | "github.com/ktorio/ktor-cli/internal/app/project" 10 | "github.com/ktorio/ktor-cli/internal/app/utils" 11 | "log" 12 | "net/http" 13 | "os" 14 | "os/exec" 15 | "os/signal" 16 | "path/filepath" 17 | "runtime" 18 | ) 19 | 20 | func Dev(projectDir string, client *http.Client, verboseLogger *log.Logger, hasGlobalLog bool, homeDir string) { 21 | wrapper := "./gradlew" 22 | if runtime.GOOS == "windows" { 23 | wrapper = ".\\gradlew.bat" 24 | } 25 | 26 | wrapperPath := filepath.Join(projectDir, wrapper) 27 | 28 | if !utils.Exists(wrapperPath) { 29 | fmt.Printf(i18n.Get(i18n.GradleWrapperNotExistErr, wrapper, projectDir)) 30 | os.Exit(1) 31 | } 32 | 33 | runTask, buildTask, guessed := project.GuessGradleTasks(projectDir) 34 | 35 | if !guessed { 36 | fmt.Printf(i18n.Get(i18n.KtorGradlePluginNotFound, project.DevModeSincePluginVersion, projectDir)) 37 | os.Exit(1) 38 | } 39 | 40 | _, jdkPath, err := cli.ObtainJdk(client, verboseLogger, homeDir) 41 | 42 | if err != nil { 43 | cli.ExitWithError(err, hasGlobalLog, homeDir) 44 | } 45 | 46 | env := os.Environ() 47 | env = append(env, fmt.Sprintf("JAVA_HOME=%s", jdkPath)) 48 | 49 | buildCmd := exec.Command(wrapper, buildTask, "--continuous") 50 | buildCmd.Env = env 51 | buildCmd.Dir = projectDir 52 | buildCmd.Stderr = os.Stderr 53 | 54 | verboseLogger.Printf(i18n.Get(i18n.StartingCommandMsg, "build", jdkPath, buildCmd.String())) 55 | 56 | err = buildCmd.Start() 57 | if err != nil { 58 | fmt.Fprintf(os.Stderr, i18n.Get(i18n.ErrorExecutingCommandMsg, "build", buildCmd)) 59 | 60 | var pe *os.PathError 61 | if errors.As(err, &pe) { 62 | if errors.Is(pe.Err, os.ErrPermission) { 63 | err = &app.Error{Err: err, Kind: app.NoPermsForFile} 64 | } 65 | } 66 | 67 | cli.ExitWithError(err, hasGlobalLog, homeDir) 68 | } 69 | 70 | runCmd := exec.Command(wrapper, runTask, "-Pio.ktor.development=true") 71 | runCmd.Env = env 72 | runCmd.Dir = projectDir 73 | runCmd.Stdout = os.Stdout 74 | runCmd.Stderr = os.Stderr 75 | 76 | verboseLogger.Printf(i18n.Get(i18n.StartingCommandMsg, "run", jdkPath, runCmd.String())) 77 | 78 | err = runCmd.Start() 79 | 80 | if err != nil { 81 | fmt.Fprintf(os.Stderr, i18n.Get(i18n.ErrorExecutingCommandMsg, "run", runCmd)) 82 | 83 | var pe *os.PathError 84 | if errors.As(err, &pe) { 85 | if errors.Is(pe.Err, os.ErrPermission) { 86 | err = &app.Error{Err: err, Kind: app.NoPermsForFile} 87 | } 88 | } 89 | 90 | cli.ExitWithError(err, hasGlobalLog, homeDir) 91 | } 92 | 93 | doneChan := make(chan error) 94 | 95 | go func() { 96 | err = buildCmd.Wait() 97 | doneChan <- err 98 | }() 99 | 100 | go func() { 101 | err = runCmd.Wait() 102 | doneChan <- err 103 | }() 104 | 105 | interruptChan := make(chan os.Signal, 1) 106 | signal.Notify(interruptChan, os.Interrupt) 107 | 108 | go func() { 109 | for range interruptChan { 110 | // Send SIGINT to both child processes to exit them properly 111 | _ = buildCmd.Process.Signal(os.Interrupt) 112 | _ = runCmd.Process.Signal(os.Interrupt) 113 | os.Exit(1) 114 | } 115 | }() 116 | 117 | err = <-doneChan 118 | 119 | // One of the processes can be still alive 120 | _ = buildCmd.Process.Signal(os.Interrupt) 121 | _ = runCmd.Process.Signal(os.Interrupt) 122 | 123 | var exitErr *exec.ExitError 124 | if errors.As(err, &exitErr) { 125 | os.Exit(exitErr.ExitCode()) 126 | } else if err != nil { 127 | os.Exit(1) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /internal/app/ktor/module_test.go: -------------------------------------------------------------------------------- 1 | package ktor 2 | 3 | import ( 4 | "github.com/ktorio/ktor-cli/internal/app/network" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type testCase struct { 10 | artifacts []network.Artifact 11 | expMc MavenCoords 12 | expResult ModuleResult 13 | expCandidates []MavenCoords 14 | } 15 | 16 | func TestFindModule(t *testing.T) { 17 | var cases = []testCase{ 18 | { 19 | artifacts: []network.Artifact{artifactOf("server-sse", 0)}, 20 | expMc: MavenCoords{Artifact: "ktor-server-sse", Group: MavenGroup}, expResult: ModuleFound, 21 | }, 22 | { 23 | artifacts: []network.Artifact{artifactOf("client-js", 0)}, 24 | expMc: MavenCoords{Artifact: "ktor-client-js", Group: MavenGroup}, expResult: ModuleFound, 25 | }, 26 | { 27 | artifacts: []network.Artifact{artifactOf("server-test-host", 0)}, 28 | expMc: MavenCoords{Artifact: "ktor-server-test-host", Group: MavenGroup}, expResult: ModuleFound, 29 | }, 30 | { 31 | artifacts: []network.Artifact{testArtifactOf("client-mock", 0)}, 32 | expMc: MavenCoords{Artifact: "ktor-client-mock", Group: MavenGroup, IsTest: true}, expResult: ModuleFound, 33 | }, 34 | { 35 | artifacts: []network.Artifact{ 36 | artifactOf("client-content-negotiation", 0), 37 | artifactOf("server-content-negotiation", 0), 38 | }, 39 | expMc: MavenCoords{}, expResult: ModuleAmbiguity, expCandidates: []MavenCoords{ 40 | {Artifact: "ktor-server-content-negotiation", Group: MavenGroup}, 41 | {Artifact: "ktor-client-content-negotiation", Group: MavenGroup}, 42 | }, 43 | }, 44 | { 45 | artifacts: []network.Artifact{ 46 | artifactOf("client-core", 0), 47 | artifactOf("server-core", 0), 48 | }, 49 | expMc: MavenCoords{}, expResult: ModuleAmbiguity, expCandidates: []MavenCoords{ 50 | {Artifact: "ktor-server-core", Group: MavenGroup}, 51 | {Artifact: "ktor-client-core", Group: MavenGroup}, 52 | }, 53 | }, 54 | { 55 | artifacts: []network.Artifact{ 56 | artifactOf("client-core", 0), 57 | artifactOf("server-pore", 1), 58 | }, 59 | expMc: MavenCoords{Artifact: "ktor-client-core", Group: MavenGroup}, expResult: ModuleFound, 60 | }, 61 | { 62 | artifacts: []network.Artifact{}, 63 | expResult: ModuleNotFound, 64 | }, 65 | { 66 | artifacts: []network.Artifact{artifactOf("server-freemarker", 1)}, 67 | expResult: SimilarModulesFound, expCandidates: []MavenCoords{ 68 | {Artifact: "ktor-server-freemarker", Group: MavenGroup}, 69 | }, 70 | }, 71 | } 72 | 73 | for _, c := range cases { 74 | mc, result, candidates := FindModule(c.artifacts) 75 | 76 | if result != c.expResult { 77 | t.Errorf("expected result to be %v, got %v", c.expResult, result) 78 | } 79 | 80 | if mc.Artifact != c.expMc.Artifact { 81 | t.Errorf("expected Maven artifact name to be %s, got %s", c.expMc.Artifact, mc.Artifact) 82 | } 83 | 84 | if mc.Group != c.expMc.Group { 85 | t.Errorf("expected Maven group to be %s, got %s", c.expMc.Group, mc.Group) 86 | } 87 | 88 | if mc.IsTest != c.expMc.IsTest { 89 | t.Errorf("expected artifact's test=%v, got %v", c.expMc.IsTest, mc.IsTest) 90 | } 91 | 92 | if !reflect.DeepEqual(candidates, c.expCandidates) { 93 | t.Errorf("expected candidates to be %v, got %v", c.expCandidates, candidates) 94 | } 95 | } 96 | } 97 | 98 | func artifactOf(name string, distance int) network.Artifact { 99 | return network.Artifact{ 100 | Name: "ktor-" + name, 101 | Group: MavenGroup, 102 | IsTest: false, 103 | Distance: distance, 104 | } 105 | } 106 | 107 | func testArtifactOf(name string, distance int) network.Artifact { 108 | return network.Artifact{ 109 | Name: "ktor-" + name, 110 | Group: MavenGroup, 111 | IsTest: true, 112 | Distance: distance, 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /internal/app/ktor/module.go: -------------------------------------------------------------------------------- 1 | package ktor 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app/network" 6 | "slices" 7 | "strings" 8 | ) 9 | 10 | const SerPluginId = "org.jetbrains.kotlin.plugin.serialization" 11 | const SerPluginKotlinId = "plugin.serialization" 12 | const KotlinJvmPluginId = "org.jetbrains.kotlin.jvm" 13 | const KmpPluginId = "org.jetbrains.kotlin.multiplatform" 14 | const MavenGroup = "io.ktor" 15 | 16 | type MavenCoords struct { 17 | Artifact, Group, Version string 18 | IsTest bool 19 | } 20 | 21 | type GradlePlugin struct { 22 | Id string 23 | IsSerialization bool 24 | } 25 | 26 | type ModuleResult int 27 | 28 | const ( 29 | ModuleNotFound ModuleResult = iota 30 | ModuleAmbiguity 31 | SimilarModulesFound 32 | ModuleFound 33 | ) 34 | 35 | func ParseMavenCoords(s string) (MavenCoords, bool) { 36 | parts := strings.Split(s, ":") 37 | 38 | if len(parts) == 1 { 39 | return MavenCoords{Artifact: parts[0]}, true 40 | } 41 | 42 | if len(parts) == 2 { 43 | return MavenCoords{Group: parts[0], Artifact: parts[1]}, true 44 | } 45 | 46 | if len(parts) == 3 { 47 | return MavenCoords{Group: parts[0], Artifact: parts[1], Version: parts[2]}, true 48 | } 49 | 50 | return MavenCoords{}, false 51 | } 52 | 53 | func (mc *MavenCoords) String() string { 54 | return fmt.Sprintf("%s:%s", mc.Group, mc.Artifact) 55 | } 56 | 57 | func (mc *MavenCoords) RoughlySame(other MavenCoords) bool { 58 | if mc.Group != other.Group { 59 | return false 60 | } 61 | 62 | if strings.HasPrefix(mc.Artifact, other.Artifact) || strings.HasPrefix(other.Artifact, mc.Artifact) { 63 | return true 64 | } 65 | 66 | return false 67 | } 68 | 69 | func DependentPlugins(mc MavenCoords) []GradlePlugin { 70 | var plugs []GradlePlugin 71 | 72 | if strings.HasPrefix(mc.Artifact, "ktor-serialization-kotlinx") { 73 | plugs = append(plugs, GradlePlugin{Id: "org.jetbrains.kotlin.plugin.serialization", IsSerialization: true}) 74 | } 75 | 76 | return plugs 77 | } 78 | 79 | func FindModule(artifacts []network.Artifact) (coords MavenCoords, result ModuleResult, candidates []MavenCoords) { 80 | if len(artifacts) == 0 { 81 | result = ModuleNotFound 82 | } else { 83 | var exactArtifacts []network.Artifact 84 | var similarArtifacts []network.Artifact 85 | for _, a := range artifacts { 86 | if a.Distance == 0 { 87 | exactArtifacts = append(exactArtifacts, a) 88 | } else { 89 | similarArtifacts = append(similarArtifacts, a) 90 | } 91 | } 92 | 93 | if len(exactArtifacts) == 1 { 94 | result = ModuleFound 95 | coords = MavenCoords{ 96 | Artifact: exactArtifacts[0].Name, 97 | Group: exactArtifacts[0].Group, 98 | IsTest: exactArtifacts[0].IsTest, 99 | } 100 | } else if len(exactArtifacts) > 0 { 101 | result = ModuleAmbiguity 102 | sortArtifacts(exactArtifacts) 103 | 104 | for _, a := range exactArtifacts { 105 | candidates = append(candidates, MavenCoords{ 106 | Artifact: a.Name, 107 | Group: a.Group, 108 | IsTest: a.IsTest, 109 | }) 110 | } 111 | } else { 112 | sortArtifacts(similarArtifacts) 113 | result = SimilarModulesFound 114 | 115 | for _, a := range similarArtifacts { 116 | candidates = append(candidates, MavenCoords{ 117 | Artifact: a.Name, 118 | Group: a.Group, 119 | IsTest: a.IsTest, 120 | }) 121 | } 122 | } 123 | } 124 | 125 | return 126 | } 127 | 128 | var prefixes = []string{"ktor-server-", "ktor-client-", "ktor-"} 129 | 130 | func sortArtifacts(artifacts []network.Artifact) { 131 | slices.SortFunc(artifacts, func(x, y network.Artifact) int { 132 | if x.Distance == y.Distance { 133 | var xIndex, yIndex int 134 | for i, pr := range prefixes { 135 | if strings.HasPrefix(x.Name, pr) { 136 | xIndex = i 137 | break 138 | } 139 | } 140 | 141 | for i, pr := range prefixes { 142 | if strings.HasPrefix(y.Name, pr) { 143 | yIndex = i 144 | break 145 | } 146 | } 147 | 148 | return xIndex - yIndex 149 | } else { 150 | return x.Distance - y.Distance 151 | } 152 | }) 153 | } 154 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/intellij,gradle,kotlin 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,gradle,kotlin 3 | 4 | ### Intellij ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | .idea 15 | 16 | # AWS User-specific 17 | .idea/**/aws.xml 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | # .idea/artifacts 40 | # .idea/compiler.xml 41 | # .idea/jarRepositories.xml 42 | # .idea/modules.xml 43 | # .idea/*.iml 44 | # .idea/modules 45 | # *.iml 46 | # *.ipr 47 | 48 | # CMake 49 | cmake-build-*/ 50 | 51 | # Mongo Explorer plugin 52 | .idea/**/mongoSettings.xml 53 | 54 | # File-based project format 55 | *.iws 56 | 57 | # IntelliJ 58 | out/ 59 | 60 | # mpeltonen/sbt-idea plugin 61 | .idea_modules/ 62 | 63 | # JIRA plugin 64 | atlassian-ide-plugin.xml 65 | 66 | # Cursive Clojure plugin 67 | .idea/replstate.xml 68 | 69 | # Crashlytics plugin (for Android Studio and IntelliJ) 70 | com_crashlytics_export_strings.xml 71 | crashlytics.properties 72 | crashlytics-build.properties 73 | fabric.properties 74 | 75 | # Editor-based Rest Client 76 | .idea/httpRequests 77 | 78 | # Android studio 3.1+ serialized cache file 79 | .idea/caches/build_file_checksums.ser 80 | 81 | ### Intellij Patch ### 82 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 83 | 84 | # *.iml 85 | # modules.xml 86 | # .idea/misc.xml 87 | # *.ipr 88 | 89 | # Sonarlint plugin 90 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 91 | .idea/**/sonarlint/ 92 | 93 | # SonarQube Plugin 94 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 95 | .idea/**/sonarIssues.xml 96 | 97 | # Markdown Navigator plugin 98 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 99 | .idea/**/markdown-navigator.xml 100 | .idea/**/markdown-navigator-enh.xml 101 | .idea/**/markdown-navigator/ 102 | 103 | # Cache file creation bug 104 | # See https://youtrack.jetbrains.com/issue/JBR-2257 105 | .idea/$CACHE_FILE$ 106 | 107 | # CodeStream plugin 108 | # https://plugins.jetbrains.com/plugin/12206-codestream 109 | .idea/codestream.xml 110 | 111 | ### Kotlin ### 112 | # Compiled class file 113 | *.class 114 | 115 | # Log file 116 | *.log 117 | 118 | # BlueJ files 119 | *.ctxt 120 | 121 | # Mobile Tools for Java (J2ME) 122 | .mtj.tmp/ 123 | 124 | # Package Files # 125 | *.jar 126 | *.war 127 | *.nar 128 | *.ear 129 | *.zip 130 | *.tar.gz 131 | *.rar 132 | 133 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 134 | hs_err_pid* 135 | 136 | ### Gradle ### 137 | .gradle 138 | build/ 139 | 140 | # Ignore Gradle GUI config 141 | gradle-app.setting 142 | 143 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 144 | !gradle-wrapper.jar 145 | 146 | # Cache of project 147 | .gradletasknamecache 148 | 149 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 150 | # gradle/wrapper/gradle-wrapper.properties 151 | 152 | ### Gradle Patch ### 153 | **/build/ 154 | 155 | # Eclipse Gradle plugin generated files 156 | # Eclipse Core 157 | .project 158 | # JDT-specific (Eclipse Java Development Tools) 159 | .classpath 160 | 161 | # End of https://www.toptal.com/developers/gitignore/api/intellij,gradle,kotlin 162 | 163 | sandbox -------------------------------------------------------------------------------- /internal/app/interactive/model/state.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/ktorio/ktor-cli/internal/app" 7 | "github.com/ktorio/ktor-cli/internal/app/i18n" 8 | "github.com/ktorio/ktor-cli/internal/app/network" 9 | "os" 10 | "path/filepath" 11 | "unicode" 12 | ) 13 | 14 | const maxFilenameLen = 255 15 | 16 | type IdSet map[string]struct{} 17 | 18 | type State struct { 19 | Running bool 20 | errorMap map[ErrorKind]string 21 | StatusLine string 22 | Search string 23 | PluginsFetched bool 24 | Groups []string 25 | PluginsByGroup map[string][]network.Plugin 26 | AddedPlugins IdSet 27 | IndirectPlugins map[string]IdSet 28 | PluginDeps map[string][]string 29 | AllPluginsByGroup map[string][]network.Plugin 30 | AllSortedGroups []string 31 | ShouldFetchPlugins bool 32 | Result 33 | } 34 | 35 | type Result struct { 36 | ProjectName string 37 | ProjectDir string 38 | Plugins []string 39 | Quit bool 40 | } 41 | 42 | func NewState() *State { 43 | return &State{ 44 | Running: true, 45 | PluginsByGroup: make(map[string][]network.Plugin), 46 | IndirectPlugins: make(map[string]IdSet), 47 | AddedPlugins: make(IdSet), 48 | errorMap: make(map[ErrorKind]string), 49 | ShouldFetchPlugins: true, 50 | } 51 | } 52 | 53 | func InsertRune(input string, pos int, r rune) string { 54 | if pos < 0 { 55 | return input 56 | } 57 | 58 | if input == "" { 59 | return fmt.Sprintf("%c", r) 60 | } 61 | 62 | runes := []rune(input) 63 | if pos >= len(runes) { 64 | return string(append([]rune(input), r)) 65 | } 66 | 67 | var result []rune 68 | for _, run := range runes[:pos] { 69 | result = append(result, run) 70 | } 71 | result = append(result, r) 72 | for _, run := range runes[pos:] { 73 | result = append(result, run) 74 | } 75 | 76 | return string(result) 77 | } 78 | 79 | func DeleteChar(input string, pos int) string { 80 | if pos >= len(input) || pos < 0 { 81 | return input 82 | } 83 | 84 | runes := []rune(input) 85 | 86 | var result []rune 87 | for _, r := range runes[:pos] { 88 | result = append(result, r) 89 | } 90 | 91 | for _, r := range runes[pos+1:] { 92 | result = append(result, r) 93 | } 94 | 95 | return string(result) 96 | } 97 | 98 | func CheckProjectSettings(mdl *State) bool { 99 | mdl.RemoveErrors(ProjectDirNotEmptyError, DirNotExistError, ProjectDirTooLongError, ProjectNameEmptyError, ProjectNameAllowedCharsError) 100 | hasError := false 101 | 102 | if len(mdl.ProjectName) == 0 { 103 | hasError = true 104 | mdl.SetError(ProjectNameEmptyError, i18n.Get(i18n.ProjectNameRequired)) 105 | } 106 | 107 | if !IsDirEmptyOrAbsent(mdl.GetProjectPath()) { 108 | hasError = true 109 | mdl.SetError(ProjectDirNotEmptyError, fmt.Sprintf(i18n.Get(i18n.DirNotEmptyError, mdl.GetProjectPath()))) 110 | } 111 | 112 | if ok, p := HasNonExistentDirsInPath(mdl.GetProjectPath()); ok { 113 | hasError = true 114 | mdl.SetError(DirNotExistError, fmt.Sprintf(i18n.Get(i18n.DirNotExist, p))) 115 | } 116 | 117 | if len(filepath.Base(mdl.GetProjectPath())) > maxFilenameLen { 118 | hasError = true 119 | mdl.SetError(ProjectDirTooLongError, i18n.Get(i18n.ProjectDirLong)) 120 | } 121 | 122 | for _, r := range mdl.ProjectName { 123 | isLatin := (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') 124 | if !isLatin && !unicode.IsDigit(r) && r != '.' && r != '_' && r != '-' { 125 | hasError = true 126 | mdl.SetError(ProjectNameAllowedCharsError, i18n.Get(i18n.ProjectNameAllowedChars)) 127 | break 128 | } 129 | } 130 | 131 | return hasError 132 | } 133 | 134 | func InitProjectDir(mdl *State) { 135 | wd, err := os.Getwd() 136 | 137 | if err != nil { 138 | mdl.ProjectDir = "." 139 | return 140 | } 141 | 142 | mdl.ProjectDir = wd 143 | } 144 | 145 | func (mdl *State) GetProjectPath() string { 146 | return filepath.Join(mdl.ProjectDir, mdl.ProjectName) 147 | } 148 | 149 | func FindVacantProjectName(mdl *State) (string, error) { 150 | for i := 1; i < 1000; i++ { 151 | newName := fmt.Sprintf("%s%d", mdl.ProjectName, i) 152 | if _, err := os.Stat(filepath.Join(mdl.ProjectDir, newName)); errors.Is(err, os.ErrNotExist) { 153 | return newName, nil 154 | } 155 | } 156 | 157 | return "", &app.Error{Err: errors.New("cannot find vacant project name"), Kind: app.UnknownError} 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ktor CLI 2 | The `ktor` tool allows generating [Ktor](https://ktor.io/) applications through the command line interface. 3 | 4 | For a web interface, visit https://start.ktor.io. 5 | 6 | ## Install 7 | 8 | ### Linux and macOS 9 | 10 | The tool can be installed via Homebrew: 11 | ```shell 12 | brew install ktor 13 | ``` 14 | 15 | ### Windows 16 | The tool can be installed via WinGet: 17 | ```shell 18 | winget install JetBrains.KtorCLI 19 | ``` 20 | 21 | ## Prerequisites 22 | To build the tool, the `go` compiler needs to be installed first. You can find the [installation guide](https://go.dev/doc/install) on the official website. 23 | 24 | 25 | ## Build 26 | To build an executable, issue the following command in the root directory of the repository: 27 | ```shell 28 | go build github.com/ktorio/ktor-cli/cmd/ktor 29 | ``` 30 | 31 | If the build is successful, the `ktor` executable should appear in the current directory. 32 | Also, the `go` command can be issued through Docker using an [official Go image](https://hub.docker.com/_/golang): 33 | ```shell 34 | docker run --rm -v "$PWD":/usr/src/build -w /usr/src/build golang:1.21 git config --global --add safe.directory . && go build -v github.com/ktorio/ktor-cli/cmd/ktor 35 | ``` 36 | 37 | ## Run 38 | To run the tool without making an intermediate build, execute the following command: 39 | ```shell 40 | go run github.com/ktorio/ktor-cli/cmd/ktor # followed by CLI args 41 | ``` 42 | 43 | Effectively, the `go run github.com/ktorio/ktor-cli/cmd/ktor` line can replace the `ktor` executable in the below commands. 44 | 45 | 46 | ## Create a project 47 | 48 | To create a new Ktor project, pass a project name to the `ktor new` command: 49 | 50 | ``` 51 | ktor new ktor-sample 52 | ``` 53 | 54 | The `-v` option can be used to enable verbose output: 55 | ```shell 56 | ktor -v new ktor-project 57 | ``` 58 | 59 | ## Create a project in an interactive mode 60 | 61 | To create a new project in the interactive mode, simply use the `new` command without a project name: 62 | 63 | ```shell 64 | ktor new 65 | ``` 66 | 67 | ## Generate a project from an OpenAPI specification 68 | 69 | To generate a project in the current directory from a given [OpenAPI specification](https://swagger.io/specification/), use the `openapi` command: 70 | ```shell 71 | ktor openapi petstore.yaml 72 | ``` 73 | 74 | You can specify a different output directory with the `-o` or `--output` flag: 75 | ```shell 76 | ktor openapi -o path/to/project petstore.yaml 77 | ``` 78 | 79 | ## Add a Ktor dependency to an existing project 80 | 81 | To add a Ktor dependency to a Gradle project in the current working directory, use the `add` command: 82 | ```shell 83 | ktor add server-core 84 | ``` 85 | Use the `-p` or `--project` option to specify a path to the project directory: 86 | ```shell 87 | ktor add -p /path/to/project server-core 88 | ``` 89 | 90 | You can add multiple modules with a single command: 91 | ```shell 92 | ktor add -p /path/to/project server-core client-core json 93 | ``` 94 | 95 | Currently, Ktor dependencies can only be added to **non-multiplatform** Gradle projects using Kotlin DSL. 96 | 97 | ## Run a Ktor project in development mode 98 | 99 | The `dev` command executes the `run` Gradle task while continuously rebuilding source files upon changes. 100 | This allows uninterrupted development of a Ktor server application without requiring a restart to observe recent changes. 101 | For the `dev` command to function correctly, ensure the Ktor Gradle plugin is applied to your project. 102 | 103 | To run the application in development mode from a project in the current working directory, use the following command: 104 | ```shell 105 | ktor dev 106 | ``` 107 | 108 | To specify a path to the project directory, use the `-p` or `--project` option: 109 | ```shell 110 | ktor dev --project /path/to/project 111 | ``` 112 | 113 | ## Get the version 114 | 115 | To get the version of the tool, use the `--version` flag or the `version` command: 116 | ```shell 117 | ktor --version 118 | ktor version 119 | ``` 120 | 121 | ## Get the usage info 122 | 123 | To get the help page about the tool usage, use the `--help` flag or the `help` command: 124 | ```shell 125 | ktor --help 126 | ktor help 127 | ``` 128 | 129 | ## HTTP proxy 130 | 131 | To use a proxy server while making requests to the generation server, set the `HTTPS_PROXY` environment variable. Here is an example: 132 | ```shell 133 | HTTPS_PROXY=http://localhost:3128 ktor new ktor-project 134 | ``` -------------------------------------------------------------------------------- /internal/app/progress/percent_test.go: -------------------------------------------------------------------------------- 1 | package progress 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | "unicode" 8 | ) 9 | 10 | func TestWrites(t *testing.T) { 11 | var b strings.Builder 12 | checkAllWrites(t, &Percent{prefix: "___", total: 5, enabled: true, Writer: &b}, &b, []testCase{ 13 | {writeLen: 1, expected: withClearLine("___20%")}, 14 | {writeLen: 1, expected: withClearLine("___40%")}, 15 | {writeLen: 1, expected: withClearLine("___60%")}, 16 | {writeLen: 1, expected: withClearLine("___80%")}, 17 | {writeLen: 1, expected: withClearLine("___100%")}, 18 | }) 19 | 20 | checkAllWrites(t, &Percent{prefix: "", total: 3, enabled: true, Writer: &b}, &b, []testCase{ 21 | {writeLen: 1, expected: withClearLine("33%")}, 22 | {writeLen: 1, expected: withClearLine("66%")}, 23 | {writeLen: 1, expected: withClearLine("100%")}, 24 | }) 25 | 26 | checkAllWrites(t, &Percent{prefix: "", total: 3, enabled: true, Writer: &b}, &b, []testCase{ 27 | {writeLen: 1, expected: withClearLine("33%")}, 28 | {writeLen: 1, expected: withClearLine("66%")}, 29 | {writeLen: 1, expected: withClearLine("100%")}, 30 | {writeLen: -1, expected: withClearLine("100%\n")}, 31 | }) 32 | 33 | checkAllWrites(t, &Percent{prefix: "", total: 10, enabled: true, Writer: &b}, &b, []testCase{ 34 | {writeLen: 3, expected: withClearLine("30%")}, 35 | {writeLen: 3, expected: withClearLine("60%")}, 36 | {writeLen: 3, expected: withClearLine("90%")}, 37 | {writeLen: -1, expected: withClearLine("100%\n")}, 38 | }) 39 | 40 | checkAllWrites(t, &Percent{prefix: "", total: 10, enabled: false, Writer: &b}, &b, []testCase{ 41 | {writeLen: 3, expected: ""}, 42 | {writeLen: 3, expected: ""}, 43 | {writeLen: 3, expected: ""}, 44 | {writeLen: -1, expected: ""}, 45 | }) 46 | 47 | checkAllWrites(t, &Percent{prefix: "___", total: 5, enabled: true, Writer: &b}, &b, []testCase{ 48 | {writeLen: 1, offset: 0, expected: withClearLine("___20%")}, 49 | {writeLen: 1, offset: 1, expected: withClearLine("___40%")}, 50 | {writeLen: 1, offset: 2, expected: withClearLine("___60%")}, 51 | {writeLen: 1, offset: 3, expected: withClearLine("___80%")}, 52 | {writeLen: 1, offset: 4, expected: withClearLine("___100%")}, 53 | }) 54 | 55 | checkAllWrites(t, &Percent{prefix: "___", total: 100, enabled: true, Writer: &b}, &b, []testCase{ 56 | {writeLen: 20, offset: 0, expected: withClearLine("___20%")}, 57 | {writeLen: 20, offset: 40, expected: withClearLine("___40%")}, 58 | {writeLen: 20, offset: 80, expected: withClearLine("___60%")}, 59 | {writeLen: 20, offset: 20, expected: withClearLine("___80%")}, 60 | {writeLen: 20, offset: 60, expected: withClearLine("___100%")}, 61 | }) 62 | 63 | checkAllWrites(t, &Percent{prefix: "", total: 3, enabled: true, Writer: &b}, &b, []testCase{ 64 | {writeLen: 1, offset: 0, expected: withClearLine("33%")}, 65 | {writeLen: 1, offset: 1, expected: withClearLine("66%")}, 66 | {writeLen: 1, offset: 2, expected: withClearLine("100%")}, 67 | {writeLen: -1, offset: 0, expected: withClearLine("100%\n")}, 68 | }) 69 | } 70 | 71 | type testCase struct { 72 | writeLen int 73 | offset int64 74 | expected string 75 | } 76 | 77 | func checkAllWrites(t *testing.T, p *Percent, b *strings.Builder, cases []testCase) { 78 | for _, test := range cases { 79 | var err error 80 | if test.writeLen == -1 { 81 | err = p.Done() 82 | } else { 83 | _, err = p.Write(make([]byte, test.writeLen)) 84 | } 85 | 86 | assert(t, test.expected, err, b) 87 | 88 | b.Reset() 89 | } 90 | 91 | p.reset() 92 | 93 | for _, test := range cases { 94 | var err error 95 | if test.writeLen == -1 { 96 | err = p.Done() 97 | } else { 98 | _, err = p.WriteAt(make([]byte, test.writeLen), test.offset) 99 | } 100 | 101 | assert(t, test.expected, err, b) 102 | 103 | b.Reset() 104 | } 105 | } 106 | 107 | func assert(t *testing.T, expected string, err error, b *strings.Builder) { 108 | if err != nil { 109 | t.Fatalf("unexpected error %v", err) 110 | } 111 | 112 | if expected != b.String() { 113 | t.Fatalf("expected %s, got %s", esc(expected), esc(b.String())) 114 | } 115 | } 116 | 117 | func withClearLine(s string) string { 118 | return fmt.Sprintf("\u001B[2K\r%s", s) 119 | } 120 | 121 | func esc(s string) string { 122 | var b strings.Builder 123 | for _, r := range s { 124 | if !unicode.IsGraphic(r) { 125 | b.WriteString(fmt.Sprintf("\\u00%02d", r)) 126 | } else { 127 | b.WriteRune(r) 128 | } 129 | } 130 | return b.String() 131 | } 132 | -------------------------------------------------------------------------------- /internal/app/cli/processing_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestProcessArgs(t *testing.T) { 10 | checkProcessError(t, []string{"ktor", "-f"}, &Error{Err: UnrecognizedFlags{"-f"}, Kind: UnrecognizedFlagsError}) 11 | checkProcessError(t, []string{"ktor", "-a", "-b", "new", "proj"}, &Error{Err: UnrecognizedFlags{"-a", "-b"}, Kind: UnrecognizedFlagsError}) 12 | checkProcessError(t, []string{"ktor"}, &Error{Err: errors.New("command expected"), Kind: NoCommandError}) 13 | checkProcessError(t, []string{"ktor", "nonexistent"}, &Error{Err: CommandError{Command: "nonexistent"}, Kind: CommandNotFoundError}) 14 | checkProcessError(t, []string{"ktor", "new", "a", "b"}, &Error{Err: CommandError{Command: "new"}, Kind: WrongNumberOfArgumentsError}) 15 | checkProcessError( 16 | t, 17 | []string{"ktor", "openapi", "-o", "file.yml"}, 18 | &Error{Err: CommandError{Command: "openapi"}, Kind: WrongNumberOfArgumentsError}, 19 | ) 20 | checkProcessError( 21 | t, 22 | []string{"ktor", "openapi", "file.yml", "-o", "dir"}, 23 | &Error{Err: CommandError{Command: "openapi"}, Kind: WrongNumberOfArgumentsError}, 24 | ) 25 | checkProcessError( 26 | t, 27 | []string{"ktor", "openapi", "-o"}, 28 | &Error{Err: FlagError{Flag: "-o"}, Kind: NoArgumentForFlag}, 29 | ) 30 | checkProcessError( 31 | t, 32 | []string{"ktor", "openapi", "--output"}, 33 | &Error{Err: FlagError{Flag: "--output"}, Kind: NoArgumentForFlag}, 34 | ) 35 | checkProcessError( 36 | t, 37 | []string{"ktor", "openapi", "--output="}, 38 | &Error{Err: FlagError{Flag: "--output"}, Kind: NoArgumentForFlag}, 39 | ) 40 | checkProcessError( 41 | t, 42 | []string{"ktor", "add", "-z", "client-core"}, 43 | &Error{Err: UnrecognizedCommandFlags{Command: "add", Flags: []string{"-z"}}, Kind: UnrecognizedCommandFlagsError}, 44 | ) 45 | 46 | checkProcessing(t, []string{"ktor", "--version"}, &Input{Command: VersionCommand}) 47 | checkProcessing(t, []string{"ktor", "-V"}, &Input{Command: VersionCommand}) 48 | checkProcessing(t, []string{"ktor", "-h"}, &Input{Command: Help}) 49 | checkProcessing(t, []string{"ktor", "--help"}, &Input{Command: HelpCommand}) 50 | checkProcessing(t, []string{"ktor", "--version", "--help"}, &Input{Command: VersionCommand}) 51 | checkProcessing(t, []string{"ktor", "--help", "--version"}, &Input{Command: HelpCommand}) 52 | checkProcessing(t, []string{"ktor", "--version", "new"}, &Input{Command: VersionCommand}) 53 | checkProcessing(t, []string{"ktor", "--help", "new", "some"}, &Input{Command: HelpCommand}) 54 | checkProcessing(t, []string{"ktor", "add", "mod1", "mod2"}, &Input{Command: AddCommand, CommandArgs: []string{"mod1", "mod2"}, CommandOptions: map[Flag]string{}}) 55 | checkProcessing(t, []string{"ktor", "new", "some"}, &Input{Command: NewCommand, CommandArgs: []string{"some"}, CommandOptions: map[Flag]string{}}) 56 | checkProcessing(t, []string{"ktor", "-v", "new", "some"}, &Input{Command: NewCommand, CommandArgs: []string{"some"}, Verbose: true, CommandOptions: map[Flag]string{}}) 57 | checkProcessing( 58 | t, 59 | []string{"ktor", "openapi", "-o", "dir", "file.yml"}, 60 | &Input{Command: OpenAPI, CommandArgs: []string{"file.yml"}, CommandOptions: map[Flag]string{OutDir: "dir"}}, 61 | ) 62 | checkProcessing( 63 | t, 64 | []string{"ktor", "-v", "openapi", "-o", "dir", "file.yml"}, 65 | &Input{Command: OpenAPI, CommandArgs: []string{"file.yml"}, CommandOptions: map[Flag]string{OutDir: "dir"}, Verbose: true}, 66 | ) 67 | checkProcessing( 68 | t, 69 | []string{"ktor", "openapi", "--output=dir", "file.yml"}, 70 | &Input{Command: OpenAPI, CommandArgs: []string{"file.yml"}, CommandOptions: map[Flag]string{OutDir: "dir"}}, 71 | ) 72 | checkProcessing( 73 | t, 74 | []string{"ktor", "dev", "-p", "path/to/project"}, 75 | &Input{Command: DevCommand, CommandArgs: []string{}, CommandOptions: map[Flag]string{ProjectDir: "path/to/project"}}, 76 | ) 77 | } 78 | 79 | func checkProcessing(t *testing.T, args []string, expected *Input) { 80 | input, err := ProcessArgs(ParseArgs(args)) 81 | 82 | if err != nil { 83 | t.Fatalf("unexpected error %#v", err) 84 | } 85 | 86 | if !reflect.DeepEqual(input, expected) { 87 | t.Fatalf("expected %#v, got %#v", expected, input) 88 | } 89 | } 90 | 91 | func checkProcessError(t *testing.T, args []string, expected *Error) { 92 | _, err := ProcessArgs(ParseArgs(args)) 93 | 94 | if err == nil { 95 | t.Fatalf("expected error, got none") 96 | } 97 | 98 | if !reflect.DeepEqual(err, expected) { 99 | t.Fatalf("expected error %#v, got %#v", expected, err) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /internal/app/cli/command/complete.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ktorio/ktor-cli/internal/app" 6 | "github.com/ktorio/ktor-cli/internal/app/cli" 7 | "strings" 8 | ) 9 | 10 | func Complete(allModules []string, shell string) (string, error) { 11 | switch shell { 12 | case "zsh": 13 | err, s := completeZsh(allModules) 14 | return err, s 15 | case "bash": 16 | err, s := completeBash(allModules) 17 | return err, s 18 | case "fish": 19 | err, s := completeFish(allModules) 20 | return err, s 21 | default: 22 | return "", &app.Error{Err: &cli.ShellError{Shell: shell}, Kind: app.UnrecognizedShellError} 23 | } 24 | } 25 | 26 | func completeFish(allModules []string) (string, error) { 27 | var commands []string 28 | for c, s := range cli.AllCommandsSpec { 29 | if c == cli.CompletionCommand { 30 | continue 31 | } 32 | 33 | commands = append(commands, fmt.Sprintf("complete -c ktor -n \"__fish_use_subcommand\" -f -a \"%s\" -d \"%s\"", c, s.Description)) 34 | } 35 | 36 | newCommand := fmt.Sprintf("complete -c ktor -n \"__fish_seen_subcommand_from new\" -a \"(commandline -ot)\" -d \"[project-name]\"") 37 | 38 | var modules []string 39 | for _, m := range allModules { 40 | modules = append(modules, fmt.Sprintf("complete -c ktor -n \"__fish_seen_subcommand_from add\" -f -a \"%s\"", stripKtorPrefix(m))) 41 | } 42 | 43 | s := fmt.Sprintf(` 44 | %s 45 | %s 46 | %s 47 | `, strings.Join(commands, "\n"), newCommand, strings.Join(modules, "\n")) 48 | 49 | return s, nil 50 | } 51 | 52 | func completeBash(allModules []string) (string, error) { 53 | var commands []string 54 | for c := range cli.AllCommandsSpec { 55 | if c == cli.CompletionCommand { 56 | continue 57 | } 58 | 59 | commands = append(commands, string(c)) 60 | } 61 | 62 | for _, s := range cli.AllFlagsSpec { 63 | for _, a := range s.Aliases { 64 | commands = append(commands, a) 65 | } 66 | } 67 | 68 | var modules []string 69 | for _, m := range allModules { 70 | modules = append(modules, stripKtorPrefix(m)) 71 | } 72 | 73 | s := fmt.Sprintf(` 74 | _ktor_complete() { 75 | local cur prev subcommands modules 76 | cur="${COMP_WORDS[COMP_CWORD]}" 77 | prev="${COMP_WORDS[COMP_CWORD-1]}" 78 | 79 | subcommands="%s" 80 | modules="%s" 81 | 82 | if [[ $COMP_CWORD -eq 1 ]]; then 83 | COMPREPLY=($(compgen -W "$subcommands" -- "$cur")) 84 | else 85 | case "${COMP_WORDS[1]}" in 86 | new) 87 | if [[ $COMP_CWORD -eq 2 ]]; then 88 | COMPREPLY=() 89 | fi 90 | ;; 91 | add) 92 | COMPREPLY=($(compgen -W "$modules" -- "$cur")) 93 | ;; 94 | *) 95 | COMPREPLY=() 96 | ;; 97 | esac 98 | fi 99 | } 100 | 101 | complete -F _ktor_complete ktor 102 | `, strings.Join(commands, " "), strings.Join(modules, " ")) 103 | 104 | return s, nil 105 | } 106 | 107 | func completeZsh(allModules []string) (string, error) { 108 | var commands []string 109 | for c, s := range cli.AllCommandsSpec { 110 | if c == cli.CompletionCommand { 111 | continue 112 | } 113 | 114 | commands = append(commands, fmt.Sprintf("\"%s:%s\"", string(c), s.Description)) 115 | } 116 | 117 | for _, s := range cli.AllFlagsSpec { 118 | for _, a := range s.Aliases { 119 | commands = append(commands, fmt.Sprintf("\"%s:%s\"", a, s.Description)) 120 | } 121 | } 122 | 123 | var modules []string 124 | for _, m := range allModules { 125 | modules = append(modules, `"`+stripKtorPrefix(m)+`"`) 126 | } 127 | 128 | s := fmt.Sprintf(` 129 | _ktor_subcommands=( 130 | %s 131 | ) 132 | 133 | _ktor_add_modules=( 134 | %s 135 | ) 136 | 137 | # Completion function for the ktor command 138 | _ktor() { 139 | local context state line 140 | _arguments -C \ 141 | '1: :->subcommands' \ 142 | '*::arg:->args' 143 | 144 | case $state in 145 | subcommands) 146 | _describe -t commands "Ktor commands" _ktor_subcommands 147 | ;; 148 | args) 149 | case ${line[1]} in 150 | new) 151 | _arguments '*::[project-name]:_files' 152 | ;; 153 | add) 154 | _describe -t modules "Ktor modules" _ktor_add_modules 155 | ;; 156 | esac 157 | esac 158 | } 159 | 160 | # Load the completion function 161 | compdef _ktor ktor 162 | `, strings.Join(commands, "\n"), strings.Join(modules, "\n")) 163 | 164 | return s, nil 165 | } 166 | 167 | func stripKtorPrefix(module string) string { 168 | return strings.TrimPrefix(module, "ktor-") 169 | } 170 | -------------------------------------------------------------------------------- /internal/app/project/add_test.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "errors" 5 | "github.com/ktorio/ktor-cli/internal/app/ktor" 6 | "github.com/ktorio/ktor-cli/internal/app/lang" 7 | "github.com/ktorio/ktor-cli/internal/app/lang/gradle" 8 | "github.com/ktorio/ktor-cli/internal/app/lang/toml" 9 | "github.com/ktorio/ktor-cli/internal/app/utils" 10 | "io/fs" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | "slices" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | func TestAddProjectDependencies(t *testing.T) { 20 | testDir := filepath.Join("internal", "app", "cli", "command", "testData") 21 | 22 | if _, err := os.Stat(testDir); errors.Is(err, os.ErrNotExist) { 23 | testDir = "testData" 24 | } 25 | 26 | entries, err := os.ReadDir(testDir) 27 | 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | for _, e := range entries { 33 | if !e.IsDir() { 34 | continue 35 | } 36 | 37 | projDir := filepath.Join(testDir, e.Name()) 38 | 39 | b, err := os.ReadFile(filepath.Join(projDir, "ktor-module.txt")) 40 | if err != nil { 41 | t.Fatalf("Expected ktor-module.txt file in the %s", projDir) 42 | } 43 | 44 | buildPath := filepath.Join(projDir, "build.gradle.kts") 45 | buildRoot, buildErr, buildSyntaxErrors := gradle.ParseBuildFile(buildPath) 46 | 47 | tomlPath, tomlFound := toml.FindVersionsPath(projDir) 48 | tomlSuccessParsed := false 49 | var tomlDoc *toml.Document 50 | 51 | if tomlFound { 52 | tomlDoc, err, _ = toml.ParseCatalogToml(tomlPath) 53 | 54 | if err == nil { 55 | tomlSuccessParsed = true 56 | } 57 | } 58 | 59 | if e.Name() == "multi-platform-catalog-projects-not-supported" || e.Name() == "multi-platform-projects-not-supported" { 60 | if IsKmp(buildRoot, tomlDoc, tomlSuccessParsed) { 61 | continue 62 | } else { 63 | log.Fatalf("%s: expected multiplatform project to be unsupported", e.Name()) 64 | } 65 | } 66 | 67 | if versionBytes, err := os.ReadFile(filepath.Join(projDir, "ktor-version.txt")); err == nil && buildErr == nil && utils.Exists(buildPath) { 68 | ktorVersion := strings.TrimSpace(string(versionBytes)) 69 | actualVersion, ok := SearchKtorVersion(projDir, buildRoot, tomlDoc, tomlSuccessParsed) 70 | 71 | if len(ktorVersion) != 0 && !ok { 72 | t.Fatalf("%s: expected Ktor version to be %s, found nothing", e.Name(), ktorVersion) 73 | } 74 | 75 | if actualVersion != ktorVersion { 76 | t.Fatalf("%s: expected Ktor version to be %s, got %s", e.Name(), ktorVersion, actualVersion) 77 | } 78 | } 79 | 80 | parts := strings.Split(strings.TrimSpace(string(b)), ":") 81 | version := "" 82 | artifact := parts[0] 83 | if len(parts) > 1 { 84 | version = parts[1] 85 | } 86 | 87 | mc := ktor.MavenCoords{Artifact: artifact, Group: "io.ktor", Version: version, IsTest: artifact == "ktor-server-test-host"} 88 | depPlugins := ktor.DependentPlugins(mc) 89 | var serPlugin *ktor.GradlePlugin 90 | if len(depPlugins) > 0 { 91 | serPlugin = &depPlugins[0] 92 | } 93 | 94 | if buildErr == nil && utils.Exists(buildPath) { 95 | files, err := AddKtorModule(mc, buildRoot, tomlDoc, tomlSuccessParsed, serPlugin, buildPath, tomlPath, projDir) 96 | 97 | if len(buildSyntaxErrors) > 0 && !utils.Exists(filepath.Join(projDir, "expect-error.txt")) { 98 | t.Fatalf("%s: unexpected syntax errors\n%s", e.Name(), lang.StringifySyntaxErrors(buildSyntaxErrors)) 99 | } 100 | 101 | err = filepath.WalkDir(projDir, func(p string, d fs.DirEntry, err error) error { 102 | if err != nil { 103 | return err 104 | } 105 | 106 | if !strings.HasSuffix(p, ".expected") { 107 | return nil 108 | } 109 | 110 | srcPath := strings.TrimSuffix(p, ".expected") 111 | 112 | srcBytes, err := os.ReadFile(srcPath) 113 | 114 | if err != nil { 115 | srcBytes = []byte{} 116 | } 117 | 118 | expBytes, err := os.ReadFile(p) 119 | 120 | if err != nil { 121 | return err 122 | } 123 | 124 | fc := findFileContent(files, srcPath) 125 | if slices.Equal(srcBytes, expBytes) && fc == nil { 126 | return nil 127 | } 128 | 129 | expContent := "" 130 | if fc != nil { 131 | expContent = fc.Content 132 | } 133 | 134 | if string(expBytes) != expContent { 135 | rel, err := filepath.Rel(filepath.Dir(projDir), srcPath) 136 | 137 | if err != nil { 138 | return err 139 | } 140 | 141 | t.Fatalf("File %s has unexpected content:\n%s", rel, utils.GetDiff(p, expContent)) 142 | } 143 | 144 | return nil 145 | }) 146 | 147 | if err != nil { 148 | t.Fatal(err) 149 | } 150 | } 151 | } 152 | } 153 | 154 | func findFileContent(files []FileContent, fp string) *FileContent { 155 | for _, fc := range files { 156 | if fc.Path == fp { 157 | return &fc 158 | } 159 | } 160 | 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /internal/app/lang/grammars/kotlin/KotlinLexer.tokens: -------------------------------------------------------------------------------- 1 | ShebangLine=1 2 | DelimitedComment=2 3 | LineComment=3 4 | WS=4 5 | NL=5 6 | RESERVED=6 7 | DOT=7 8 | COMMA=8 9 | LPAREN=9 10 | RPAREN=10 11 | LSQUARE=11 12 | RSQUARE=12 13 | LCURL=13 14 | RCURL=14 15 | MULT=15 16 | MOD=16 17 | DIV=17 18 | ADD=18 19 | SUB=19 20 | INCR=20 21 | DECR=21 22 | CONJ=22 23 | DISJ=23 24 | EXCL_WS=24 25 | EXCL_NO_WS=25 26 | COLON=26 27 | SEMICOLON=27 28 | ASSIGNMENT=28 29 | ADD_ASSIGNMENT=29 30 | SUB_ASSIGNMENT=30 31 | MULT_ASSIGNMENT=31 32 | DIV_ASSIGNMENT=32 33 | MOD_ASSIGNMENT=33 34 | ARROW=34 35 | DOUBLE_ARROW=35 36 | RANGE=36 37 | RANGE_UNTIL=37 38 | COLONCOLON=38 39 | DOUBLE_SEMICOLON=39 40 | HASH=40 41 | AT_NO_WS=41 42 | AT_POST_WS=42 43 | AT_PRE_WS=43 44 | AT_BOTH_WS=44 45 | QUEST_WS=45 46 | QUEST_NO_WS=46 47 | LANGLE=47 48 | RANGLE=48 49 | LE=49 50 | GE=50 51 | EXCL_EQ=51 52 | EXCL_EQEQ=52 53 | AS_SAFE=53 54 | EQEQ=54 55 | EQEQEQ=55 56 | SINGLE_QUOTE=56 57 | AMP=57 58 | RETURN_AT=58 59 | CONTINUE_AT=59 60 | BREAK_AT=60 61 | THIS_AT=61 62 | SUPER_AT=62 63 | FILE=63 64 | FIELD=64 65 | PROPERTY=65 66 | GET=66 67 | SET=67 68 | RECEIVER=68 69 | PARAM=69 70 | SETPARAM=70 71 | DELEGATE=71 72 | PACKAGE=72 73 | IMPORT=73 74 | CLASS=74 75 | INTERFACE=75 76 | FUN=76 77 | OBJECT=77 78 | VAL=78 79 | VAR=79 80 | TYPE_ALIAS=80 81 | CONSTRUCTOR=81 82 | BY=82 83 | COMPANION=83 84 | INIT=84 85 | THIS=85 86 | SUPER=86 87 | TYPEOF=87 88 | WHERE=88 89 | IF=89 90 | ELSE=90 91 | WHEN=91 92 | TRY=92 93 | CATCH=93 94 | FINALLY=94 95 | FOR=95 96 | DO=96 97 | WHILE=97 98 | THROW=98 99 | RETURN=99 100 | CONTINUE=100 101 | BREAK=101 102 | AS=102 103 | IS=103 104 | IN=104 105 | NOT_IS=105 106 | NOT_IN=106 107 | OUT=107 108 | DYNAMIC=108 109 | PUBLIC=109 110 | PRIVATE=110 111 | PROTECTED=111 112 | INTERNAL=112 113 | ENUM=113 114 | SEALED=114 115 | ANNOTATION=115 116 | DATA=116 117 | INNER=117 118 | VALUE=118 119 | TAILREC=119 120 | OPERATOR=120 121 | INLINE=121 122 | INFIX=122 123 | EXTERNAL=123 124 | SUSPEND=124 125 | OVERRIDE=125 126 | ABSTRACT=126 127 | FINAL=127 128 | OPEN=128 129 | CONST=129 130 | LATEINIT=130 131 | VARARG=131 132 | NOINLINE=132 133 | CROSSINLINE=133 134 | REIFIED=134 135 | EXPECT=135 136 | ACTUAL=136 137 | RealLiteral=137 138 | FloatLiteral=138 139 | DoubleLiteral=139 140 | IntegerLiteral=140 141 | HexLiteral=141 142 | BinLiteral=142 143 | UnsignedLiteral=143 144 | LongLiteral=144 145 | BooleanLiteral=145 146 | NullLiteral=146 147 | CharacterLiteral=147 148 | Identifier=148 149 | IdentifierOrSoftKey=149 150 | FieldIdentifier=150 151 | QUOTE_OPEN=151 152 | TRIPLE_QUOTE_OPEN=152 153 | UNICODE_CLASS_LL=153 154 | UNICODE_CLASS_LM=154 155 | UNICODE_CLASS_LO=155 156 | UNICODE_CLASS_LT=156 157 | UNICODE_CLASS_LU=157 158 | UNICODE_CLASS_ND=158 159 | UNICODE_CLASS_NL=159 160 | QUOTE_CLOSE=160 161 | LineStrRef=161 162 | LineStrText=162 163 | LineStrEscapedChar=163 164 | LineStrExprStart=164 165 | TRIPLE_QUOTE_CLOSE=165 166 | MultiLineStringQuote=166 167 | MultiLineStrRef=167 168 | MultiLineStrText=168 169 | MultiLineStrExprStart=169 170 | Inside_Comment=170 171 | Inside_WS=171 172 | Inside_NL=172 173 | ErrorCharacter=173 174 | '...'=6 175 | '.'=7 176 | ','=8 177 | '('=9 178 | ')'=10 179 | '['=11 180 | ']'=12 181 | '{'=13 182 | '}'=14 183 | '*'=15 184 | '%'=16 185 | '/'=17 186 | '+'=18 187 | '-'=19 188 | '++'=20 189 | '--'=21 190 | '&&'=22 191 | '||'=23 192 | '!'=25 193 | ':'=26 194 | ';'=27 195 | '='=28 196 | '+='=29 197 | '-='=30 198 | '*='=31 199 | '/='=32 200 | '%='=33 201 | '->'=34 202 | '=>'=35 203 | '..'=36 204 | '..<'=37 205 | '::'=38 206 | ';;'=39 207 | '#'=40 208 | '@'=41 209 | '?'=46 210 | '<'=47 211 | '>'=48 212 | '<='=49 213 | '>='=50 214 | '!='=51 215 | '!=='=52 216 | 'as?'=53 217 | '=='=54 218 | '==='=55 219 | '\''=56 220 | '&'=57 221 | 'file'=63 222 | 'field'=64 223 | 'property'=65 224 | 'get'=66 225 | 'set'=67 226 | 'receiver'=68 227 | 'param'=69 228 | 'setparam'=70 229 | 'delegate'=71 230 | 'package'=72 231 | 'import'=73 232 | 'class'=74 233 | 'interface'=75 234 | 'fun'=76 235 | 'object'=77 236 | 'val'=78 237 | 'var'=79 238 | 'typealias'=80 239 | 'constructor'=81 240 | 'by'=82 241 | 'companion'=83 242 | 'init'=84 243 | 'this'=85 244 | 'super'=86 245 | 'typeof'=87 246 | 'where'=88 247 | 'if'=89 248 | 'else'=90 249 | 'when'=91 250 | 'try'=92 251 | 'catch'=93 252 | 'finally'=94 253 | 'for'=95 254 | 'do'=96 255 | 'while'=97 256 | 'throw'=98 257 | 'return'=99 258 | 'continue'=100 259 | 'break'=101 260 | 'as'=102 261 | 'is'=103 262 | 'in'=104 263 | 'out'=107 264 | 'dynamic'=108 265 | 'public'=109 266 | 'private'=110 267 | 'protected'=111 268 | 'internal'=112 269 | 'enum'=113 270 | 'sealed'=114 271 | 'annotation'=115 272 | 'data'=116 273 | 'inner'=117 274 | 'value'=118 275 | 'tailrec'=119 276 | 'operator'=120 277 | 'inline'=121 278 | 'infix'=122 279 | 'external'=123 280 | 'suspend'=124 281 | 'override'=125 282 | 'abstract'=126 283 | 'final'=127 284 | 'open'=128 285 | 'const'=129 286 | 'lateinit'=130 287 | 'vararg'=131 288 | 'noinline'=132 289 | 'crossinline'=133 290 | 'reified'=134 291 | 'expect'=135 292 | 'actual'=136 293 | 'null'=146 294 | '"""'=152 295 | -------------------------------------------------------------------------------- /internal/app/lang/parsers/kotlin/KotlinLexer.tokens: -------------------------------------------------------------------------------- 1 | ShebangLine=1 2 | DelimitedComment=2 3 | LineComment=3 4 | WS=4 5 | NL=5 6 | RESERVED=6 7 | DOT=7 8 | COMMA=8 9 | LPAREN=9 10 | RPAREN=10 11 | LSQUARE=11 12 | RSQUARE=12 13 | LCURL=13 14 | RCURL=14 15 | MULT=15 16 | MOD=16 17 | DIV=17 18 | ADD=18 19 | SUB=19 20 | INCR=20 21 | DECR=21 22 | CONJ=22 23 | DISJ=23 24 | EXCL_WS=24 25 | EXCL_NO_WS=25 26 | COLON=26 27 | SEMICOLON=27 28 | ASSIGNMENT=28 29 | ADD_ASSIGNMENT=29 30 | SUB_ASSIGNMENT=30 31 | MULT_ASSIGNMENT=31 32 | DIV_ASSIGNMENT=32 33 | MOD_ASSIGNMENT=33 34 | ARROW=34 35 | DOUBLE_ARROW=35 36 | RANGE=36 37 | RANGE_UNTIL=37 38 | COLONCOLON=38 39 | DOUBLE_SEMICOLON=39 40 | HASH=40 41 | AT_NO_WS=41 42 | AT_POST_WS=42 43 | AT_PRE_WS=43 44 | AT_BOTH_WS=44 45 | QUEST_WS=45 46 | QUEST_NO_WS=46 47 | LANGLE=47 48 | RANGLE=48 49 | LE=49 50 | GE=50 51 | EXCL_EQ=51 52 | EXCL_EQEQ=52 53 | AS_SAFE=53 54 | EQEQ=54 55 | EQEQEQ=55 56 | SINGLE_QUOTE=56 57 | AMP=57 58 | RETURN_AT=58 59 | CONTINUE_AT=59 60 | BREAK_AT=60 61 | THIS_AT=61 62 | SUPER_AT=62 63 | FILE=63 64 | FIELD=64 65 | PROPERTY=65 66 | GET=66 67 | SET=67 68 | RECEIVER=68 69 | PARAM=69 70 | SETPARAM=70 71 | DELEGATE=71 72 | PACKAGE=72 73 | IMPORT=73 74 | CLASS=74 75 | INTERFACE=75 76 | FUN=76 77 | OBJECT=77 78 | VAL=78 79 | VAR=79 80 | TYPE_ALIAS=80 81 | CONSTRUCTOR=81 82 | BY=82 83 | COMPANION=83 84 | INIT=84 85 | THIS=85 86 | SUPER=86 87 | TYPEOF=87 88 | WHERE=88 89 | IF=89 90 | ELSE=90 91 | WHEN=91 92 | TRY=92 93 | CATCH=93 94 | FINALLY=94 95 | FOR=95 96 | DO=96 97 | WHILE=97 98 | THROW=98 99 | RETURN=99 100 | CONTINUE=100 101 | BREAK=101 102 | AS=102 103 | IS=103 104 | IN=104 105 | NOT_IS=105 106 | NOT_IN=106 107 | OUT=107 108 | DYNAMIC=108 109 | PUBLIC=109 110 | PRIVATE=110 111 | PROTECTED=111 112 | INTERNAL=112 113 | ENUM=113 114 | SEALED=114 115 | ANNOTATION=115 116 | DATA=116 117 | INNER=117 118 | VALUE=118 119 | TAILREC=119 120 | OPERATOR=120 121 | INLINE=121 122 | INFIX=122 123 | EXTERNAL=123 124 | SUSPEND=124 125 | OVERRIDE=125 126 | ABSTRACT=126 127 | FINAL=127 128 | OPEN=128 129 | CONST=129 130 | LATEINIT=130 131 | VARARG=131 132 | NOINLINE=132 133 | CROSSINLINE=133 134 | REIFIED=134 135 | EXPECT=135 136 | ACTUAL=136 137 | RealLiteral=137 138 | FloatLiteral=138 139 | DoubleLiteral=139 140 | IntegerLiteral=140 141 | HexLiteral=141 142 | BinLiteral=142 143 | UnsignedLiteral=143 144 | LongLiteral=144 145 | BooleanLiteral=145 146 | NullLiteral=146 147 | CharacterLiteral=147 148 | Identifier=148 149 | IdentifierOrSoftKey=149 150 | FieldIdentifier=150 151 | QUOTE_OPEN=151 152 | TRIPLE_QUOTE_OPEN=152 153 | UNICODE_CLASS_LL=153 154 | UNICODE_CLASS_LM=154 155 | UNICODE_CLASS_LO=155 156 | UNICODE_CLASS_LT=156 157 | UNICODE_CLASS_LU=157 158 | UNICODE_CLASS_ND=158 159 | UNICODE_CLASS_NL=159 160 | QUOTE_CLOSE=160 161 | LineStrRef=161 162 | LineStrText=162 163 | LineStrEscapedChar=163 164 | LineStrExprStart=164 165 | TRIPLE_QUOTE_CLOSE=165 166 | MultiLineStringQuote=166 167 | MultiLineStrRef=167 168 | MultiLineStrText=168 169 | MultiLineStrExprStart=169 170 | Inside_Comment=170 171 | Inside_WS=171 172 | Inside_NL=172 173 | ErrorCharacter=173 174 | '...'=6 175 | '.'=7 176 | ','=8 177 | '('=9 178 | ')'=10 179 | '['=11 180 | ']'=12 181 | '{'=13 182 | '}'=14 183 | '*'=15 184 | '%'=16 185 | '/'=17 186 | '+'=18 187 | '-'=19 188 | '++'=20 189 | '--'=21 190 | '&&'=22 191 | '||'=23 192 | '!'=25 193 | ':'=26 194 | ';'=27 195 | '='=28 196 | '+='=29 197 | '-='=30 198 | '*='=31 199 | '/='=32 200 | '%='=33 201 | '->'=34 202 | '=>'=35 203 | '..'=36 204 | '..<'=37 205 | '::'=38 206 | ';;'=39 207 | '#'=40 208 | '@'=41 209 | '?'=46 210 | '<'=47 211 | '>'=48 212 | '<='=49 213 | '>='=50 214 | '!='=51 215 | '!=='=52 216 | 'as?'=53 217 | '=='=54 218 | '==='=55 219 | '\''=56 220 | '&'=57 221 | 'file'=63 222 | 'field'=64 223 | 'property'=65 224 | 'get'=66 225 | 'set'=67 226 | 'receiver'=68 227 | 'param'=69 228 | 'setparam'=70 229 | 'delegate'=71 230 | 'package'=72 231 | 'import'=73 232 | 'class'=74 233 | 'interface'=75 234 | 'fun'=76 235 | 'object'=77 236 | 'val'=78 237 | 'var'=79 238 | 'typealias'=80 239 | 'constructor'=81 240 | 'by'=82 241 | 'companion'=83 242 | 'init'=84 243 | 'this'=85 244 | 'super'=86 245 | 'typeof'=87 246 | 'where'=88 247 | 'if'=89 248 | 'else'=90 249 | 'when'=91 250 | 'try'=92 251 | 'catch'=93 252 | 'finally'=94 253 | 'for'=95 254 | 'do'=96 255 | 'while'=97 256 | 'throw'=98 257 | 'return'=99 258 | 'continue'=100 259 | 'break'=101 260 | 'as'=102 261 | 'is'=103 262 | 'in'=104 263 | 'out'=107 264 | 'dynamic'=108 265 | 'public'=109 266 | 'private'=110 267 | 'protected'=111 268 | 'internal'=112 269 | 'enum'=113 270 | 'sealed'=114 271 | 'annotation'=115 272 | 'data'=116 273 | 'inner'=117 274 | 'value'=118 275 | 'tailrec'=119 276 | 'operator'=120 277 | 'inline'=121 278 | 'infix'=122 279 | 'external'=123 280 | 'suspend'=124 281 | 'override'=125 282 | 'abstract'=126 283 | 'final'=127 284 | 'open'=128 285 | 'const'=129 286 | 'lateinit'=130 287 | 'vararg'=131 288 | 'noinline'=132 289 | 'crossinline'=133 290 | 'reified'=134 291 | 'expect'=135 292 | 'actual'=136 293 | 'null'=146 294 | '"""'=152 295 | -------------------------------------------------------------------------------- /internal/app/lang/parsers/kotlin/KotlinParser.tokens: -------------------------------------------------------------------------------- 1 | ShebangLine=1 2 | DelimitedComment=2 3 | LineComment=3 4 | WS=4 5 | NL=5 6 | RESERVED=6 7 | DOT=7 8 | COMMA=8 9 | LPAREN=9 10 | RPAREN=10 11 | LSQUARE=11 12 | RSQUARE=12 13 | LCURL=13 14 | RCURL=14 15 | MULT=15 16 | MOD=16 17 | DIV=17 18 | ADD=18 19 | SUB=19 20 | INCR=20 21 | DECR=21 22 | CONJ=22 23 | DISJ=23 24 | EXCL_WS=24 25 | EXCL_NO_WS=25 26 | COLON=26 27 | SEMICOLON=27 28 | ASSIGNMENT=28 29 | ADD_ASSIGNMENT=29 30 | SUB_ASSIGNMENT=30 31 | MULT_ASSIGNMENT=31 32 | DIV_ASSIGNMENT=32 33 | MOD_ASSIGNMENT=33 34 | ARROW=34 35 | DOUBLE_ARROW=35 36 | RANGE=36 37 | RANGE_UNTIL=37 38 | COLONCOLON=38 39 | DOUBLE_SEMICOLON=39 40 | HASH=40 41 | AT_NO_WS=41 42 | AT_POST_WS=42 43 | AT_PRE_WS=43 44 | AT_BOTH_WS=44 45 | QUEST_WS=45 46 | QUEST_NO_WS=46 47 | LANGLE=47 48 | RANGLE=48 49 | LE=49 50 | GE=50 51 | EXCL_EQ=51 52 | EXCL_EQEQ=52 53 | AS_SAFE=53 54 | EQEQ=54 55 | EQEQEQ=55 56 | SINGLE_QUOTE=56 57 | AMP=57 58 | RETURN_AT=58 59 | CONTINUE_AT=59 60 | BREAK_AT=60 61 | THIS_AT=61 62 | SUPER_AT=62 63 | FILE=63 64 | FIELD=64 65 | PROPERTY=65 66 | GET=66 67 | SET=67 68 | RECEIVER=68 69 | PARAM=69 70 | SETPARAM=70 71 | DELEGATE=71 72 | PACKAGE=72 73 | IMPORT=73 74 | CLASS=74 75 | INTERFACE=75 76 | FUN=76 77 | OBJECT=77 78 | VAL=78 79 | VAR=79 80 | TYPE_ALIAS=80 81 | CONSTRUCTOR=81 82 | BY=82 83 | COMPANION=83 84 | INIT=84 85 | THIS=85 86 | SUPER=86 87 | TYPEOF=87 88 | WHERE=88 89 | IF=89 90 | ELSE=90 91 | WHEN=91 92 | TRY=92 93 | CATCH=93 94 | FINALLY=94 95 | FOR=95 96 | DO=96 97 | WHILE=97 98 | THROW=98 99 | RETURN=99 100 | CONTINUE=100 101 | BREAK=101 102 | AS=102 103 | IS=103 104 | IN=104 105 | NOT_IS=105 106 | NOT_IN=106 107 | OUT=107 108 | DYNAMIC=108 109 | PUBLIC=109 110 | PRIVATE=110 111 | PROTECTED=111 112 | INTERNAL=112 113 | ENUM=113 114 | SEALED=114 115 | ANNOTATION=115 116 | DATA=116 117 | INNER=117 118 | VALUE=118 119 | TAILREC=119 120 | OPERATOR=120 121 | INLINE=121 122 | INFIX=122 123 | EXTERNAL=123 124 | SUSPEND=124 125 | OVERRIDE=125 126 | ABSTRACT=126 127 | FINAL=127 128 | OPEN=128 129 | CONST=129 130 | LATEINIT=130 131 | VARARG=131 132 | NOINLINE=132 133 | CROSSINLINE=133 134 | REIFIED=134 135 | EXPECT=135 136 | ACTUAL=136 137 | RealLiteral=137 138 | FloatLiteral=138 139 | DoubleLiteral=139 140 | IntegerLiteral=140 141 | HexLiteral=141 142 | BinLiteral=142 143 | UnsignedLiteral=143 144 | LongLiteral=144 145 | BooleanLiteral=145 146 | NullLiteral=146 147 | CharacterLiteral=147 148 | Identifier=148 149 | IdentifierOrSoftKey=149 150 | FieldIdentifier=150 151 | QUOTE_OPEN=151 152 | TRIPLE_QUOTE_OPEN=152 153 | UNICODE_CLASS_LL=153 154 | UNICODE_CLASS_LM=154 155 | UNICODE_CLASS_LO=155 156 | UNICODE_CLASS_LT=156 157 | UNICODE_CLASS_LU=157 158 | UNICODE_CLASS_ND=158 159 | UNICODE_CLASS_NL=159 160 | QUOTE_CLOSE=160 161 | LineStrRef=161 162 | LineStrText=162 163 | LineStrEscapedChar=163 164 | LineStrExprStart=164 165 | TRIPLE_QUOTE_CLOSE=165 166 | MultiLineStringQuote=166 167 | MultiLineStrRef=167 168 | MultiLineStrText=168 169 | MultiLineStrExprStart=169 170 | Inside_Comment=170 171 | Inside_WS=171 172 | Inside_NL=172 173 | ErrorCharacter=173 174 | '...'=6 175 | '.'=7 176 | ','=8 177 | '('=9 178 | ')'=10 179 | '['=11 180 | ']'=12 181 | '{'=13 182 | '}'=14 183 | '*'=15 184 | '%'=16 185 | '/'=17 186 | '+'=18 187 | '-'=19 188 | '++'=20 189 | '--'=21 190 | '&&'=22 191 | '||'=23 192 | '!'=25 193 | ':'=26 194 | ';'=27 195 | '='=28 196 | '+='=29 197 | '-='=30 198 | '*='=31 199 | '/='=32 200 | '%='=33 201 | '->'=34 202 | '=>'=35 203 | '..'=36 204 | '..<'=37 205 | '::'=38 206 | ';;'=39 207 | '#'=40 208 | '@'=41 209 | '?'=46 210 | '<'=47 211 | '>'=48 212 | '<='=49 213 | '>='=50 214 | '!='=51 215 | '!=='=52 216 | 'as?'=53 217 | '=='=54 218 | '==='=55 219 | '\''=56 220 | '&'=57 221 | 'file'=63 222 | 'field'=64 223 | 'property'=65 224 | 'get'=66 225 | 'set'=67 226 | 'receiver'=68 227 | 'param'=69 228 | 'setparam'=70 229 | 'delegate'=71 230 | 'package'=72 231 | 'import'=73 232 | 'class'=74 233 | 'interface'=75 234 | 'fun'=76 235 | 'object'=77 236 | 'val'=78 237 | 'var'=79 238 | 'typealias'=80 239 | 'constructor'=81 240 | 'by'=82 241 | 'companion'=83 242 | 'init'=84 243 | 'this'=85 244 | 'super'=86 245 | 'typeof'=87 246 | 'where'=88 247 | 'if'=89 248 | 'else'=90 249 | 'when'=91 250 | 'try'=92 251 | 'catch'=93 252 | 'finally'=94 253 | 'for'=95 254 | 'do'=96 255 | 'while'=97 256 | 'throw'=98 257 | 'return'=99 258 | 'continue'=100 259 | 'break'=101 260 | 'as'=102 261 | 'is'=103 262 | 'in'=104 263 | 'out'=107 264 | 'dynamic'=108 265 | 'public'=109 266 | 'private'=110 267 | 'protected'=111 268 | 'internal'=112 269 | 'enum'=113 270 | 'sealed'=114 271 | 'annotation'=115 272 | 'data'=116 273 | 'inner'=117 274 | 'value'=118 275 | 'tailrec'=119 276 | 'operator'=120 277 | 'inline'=121 278 | 'infix'=122 279 | 'external'=123 280 | 'suspend'=124 281 | 'override'=125 282 | 'abstract'=126 283 | 'final'=127 284 | 'open'=128 285 | 'const'=129 286 | 'lateinit'=130 287 | 'vararg'=131 288 | 'noinline'=132 289 | 'crossinline'=133 290 | 'reified'=134 291 | 'expect'=135 292 | 'actual'=136 293 | 'null'=146 294 | '"""'=152 295 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= 2 | github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= 3 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 4 | github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= 5 | github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= 6 | github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= 7 | github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= 8 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 9 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 10 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 11 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 12 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 13 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 14 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 15 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 16 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 17 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 18 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 19 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 20 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 21 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 22 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= 23 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= 24 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 25 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 26 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 27 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 28 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 29 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 30 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 31 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 32 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 34 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 40 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 41 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 42 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 43 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 44 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 45 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 46 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 47 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 48 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 49 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 50 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 51 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 52 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 53 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 54 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 55 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 56 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 57 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 58 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 59 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 60 | --------------------------------------------------------------------------------