├── 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 |
--------------------------------------------------------------------------------