From 28372d2495f3a1c8ec6003222d33cc100193fd80 Mon Sep 17 00:00:00 2001 From: Inaki Villar Date: Wed, 26 Aug 2026 10:41:26 -0700 Subject: [PATCH] fix: refactor cli configuration resolution to separate parsing from asse --- .../cli/GenerateProjectRequest.kt | 158 ++++++++++++++++++ .../github/cdsap/projectgenerator/cli/Main.kt | 105 +++--------- .../cli/GenerateProjectsCliTest.kt | 63 +++++++ 3 files changed, 240 insertions(+), 86 deletions(-) create mode 100644 cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectRequest.kt diff --git a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectRequest.kt b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectRequest.kt new file mode 100644 index 00000000..1d09b4b3 --- /dev/null +++ b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectRequest.kt @@ -0,0 +1,158 @@ +package io.github.cdsap.projectgenerator.cli + +import io.github.cdsap.projectgenerator.ProjectGenerator +import io.github.cdsap.projectgenerator.model.ClassesPerModule +import io.github.cdsap.projectgenerator.model.DependencyInjection +import io.github.cdsap.projectgenerator.model.Gradle +import io.github.cdsap.projectgenerator.model.Language +import io.github.cdsap.projectgenerator.model.Shape +import io.github.cdsap.projectgenerator.model.TypeOfStringResources +import io.github.cdsap.projectgenerator.model.TypeProjectRequested +import io.github.cdsap.projectgenerator.model.Versions +import io.github.cdsap.projectgenerator.model.VersionsFile +import io.github.cdsap.projectgenerator.writer.GradleWrapper + +data class GenerateProjectRequest( + val modules: Int, + val shape: Shape, + val language: Language, + val typeOfProjectRequested: TypeProjectRequested, + val classesPerModule: ClassesPerModule, + val versions: Versions, + val typeOfStringResources: TypeOfStringResources, + val layers: Int, + val generateUnitTest: Boolean, + val gradle: Gradle, + val projectRootPath: String, + val develocity: Boolean, + val projectName: String +) { + fun toProjectGenerator(): ProjectGenerator = ProjectGenerator( + modules = modules, + shape = shape, + language = language, + typeOfProjectRequested = typeOfProjectRequested, + classesPerModule = classesPerModule, + versions = versions, + typeOfStringResources = typeOfStringResources, + layers = layers, + generateUnitTest = generateUnitTest, + gradle = GradleWrapper(gradle), + projectRootPath = projectRootPath, + develocity = develocity, + projectName = projectName + ) + + companion object { + fun resolve( + modules: Int, + shape: Shape, + language: Language, + typeOfProjectRequested: TypeProjectRequested, + classesPerModule: ClassesPerModule, + typeOfStringResources: TypeOfStringResources, + layers: Int, + generateUnitTest: Boolean, + cliGradle: String?, + develocityFlag: Boolean, + develocityUrl: String?, + versionsFile: VersionsFile?, + outputDir: String?, + projectName: String?, + dependencyInjection: DependencyInjection, + roomDatabase: Boolean, + kotlinMultiplatformLibrary: Boolean + ): GenerateProjectRequest { + val resolvedProjectName = resolveProjectName( + projectName, + typeOfProjectRequested, + shape, + modules + ) + return GenerateProjectRequest( + modules = modules, + shape = shape, + language = language, + typeOfProjectRequested = typeOfProjectRequested, + classesPerModule = classesPerModule, + versions = resolveVersions( + fileVersions = versionsFile, + dependencyInjection = dependencyInjection, + develocityUrl = develocityUrl, + roomDatabase = roomDatabase, + kotlinMultiplatformLibrary = kotlinMultiplatformLibrary + ), + typeOfStringResources = typeOfStringResources, + layers = layers, + generateUnitTest = generateUnitTest, + gradle = resolveGradle(cliGradle, versionsFile), + projectRootPath = resolveProjectRootPath(outputDir, language, resolvedProjectName), + develocity = resolveDevelocityEnabled(develocityFlag, develocityUrl), + projectName = resolvedProjectName + ) + } + } +} + +internal fun resolveProjectName( + projectName: String?, + typeOfProjectRequested: TypeProjectRequested, + shape: Shape, + modules: Int +): String { + return projectName ?: buildString { + append(typeOfProjectRequested.name.lowercase()) + append(shape.name.lowercase().replaceFirstChar { it.uppercase() }) + append(modules) + append("modules") + } +} + +internal fun resolveDevelocityEnabled(develocity: Boolean, develocityUrl: String?): Boolean { + return develocity || develocityUrl != null +} + +internal fun resolveVersions( + fileVersions: VersionsFile?, + dependencyInjection: DependencyInjection, + develocityUrl: String?, + roomDatabase: Boolean, + kotlinMultiplatformLibrary: Boolean +): Versions { + val versions = if (fileVersions != null) { + fileVersions.resolve() + } else { + Versions() + } + var androidConfig = versions.android + if (roomDatabase) { + androidConfig = androidConfig.copy(roomDatabase = true) + } + if (kotlinMultiplatformLibrary) { + androidConfig = androidConfig.copy(kotlinMultiplatformLibrary = true) + } + val withAndroidFlags = versions.copy(android = androidConfig, di = dependencyInjection) + return if (develocityUrl != null) { + withAndroidFlags.copy(project = withAndroidFlags.project.copy(develocityUrl = develocityUrl)) + } else { + withAndroidFlags + } +} + +internal fun resolveProjectRootPath(outputDir: String?, language: Language, projectName: String): String { + return if (outputDir != null) { + outputDir + } else { + when (language) { + Language.KTS -> "projects_generated/$projectName/project_kts" + Language.GROOVY -> "projects_generated/$projectName/project_groovy" + Language.BOTH -> "projects_generated/$projectName" + } + } +} + +internal fun resolveGradle(cliGradle: String?, versionsFile: VersionsFile?): Gradle { + return cliGradle?.let(Gradle::fromValue) + ?: versionsFile?.gradle + ?: Gradle.latest() +} diff --git a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt index ff86603e..4514b328 100644 --- a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt +++ b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt @@ -8,11 +8,7 @@ import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.choice import com.github.ajalt.clikt.parameters.types.file import com.github.ajalt.clikt.parameters.types.int -import io.github.cdsap.projectgenerator.ProjectGenerator import io.github.cdsap.projectgenerator.model.* -import io.github.cdsap.projectgenerator.writer.GradleWrapper -import java.io.File -import kotlin.text.buildString fun main(args: Array) { ProjectReportCli() @@ -59,7 +55,6 @@ class GenerateProjects : CliktCommand(name = "generate-project") { private val roomDatabase by option("--room-database").flag(default = false) private val kotlinMultiplatformLibrary by option("--android-kotlin-multiplatform-library").flag(default = false) - override fun run() { val typeOfProjectRequested = TypeProjectRequested.valueOf(type.uppercase()) val shape = Shape.valueOf(shape.uppercase()) @@ -70,91 +65,29 @@ class GenerateProjects : CliktCommand(name = "generate-project") { if (typeOfProjectRequested != TypeProjectRequested.ANDROID && kotlinMultiplatformLibrary) { throw UsageError("--android-kotlin-multiplatform-library is only available when --type android.") } - val versionsOverride = versionsFile?.let(VersionsParser::fromFile) - val versions = resolveVersions( - fileVersions = versionsOverride, - dependencyInjection = dependencyInjection, + GenerateProjectRequest.resolve( + modules = modules, + shape = shape, + language = Language.valueOf(language.uppercase()), + typeOfProjectRequested = typeOfProjectRequested, + classesPerModule = ClassesPerModule( + ClassesPerModuleType.valueOf(classesModuleType.uppercase()), + classesModule + ), + typeOfStringResources = TypeOfStringResources.valueOf(typeOfStringResources.uppercase()), + layers = layers, + generateUnitTest = generateUnitTest, + cliGradle = gradle, + develocityFlag = develocity, develocityUrl = develocityUrl, + versionsFile = versionsFile?.let(VersionsParser::fromFile), + outputDir = outputDir, + projectName = projectName, + dependencyInjection = dependencyInjection, roomDatabase = roomDatabase, kotlinMultiplatformLibrary = kotlinMultiplatformLibrary - ) - val develocityEnabled = getDevelocityEnabled(develocity, develocityUrl) - val language = Language.valueOf(language.uppercase()) - val resolvedProjectName = projectName ?: buildString { - append(typeOfProjectRequested.name.lowercase()) - append(shape.name.lowercase().replaceFirstChar { it.uppercase() }) - append(modules) - append("modules") - } - ProjectGenerator( - modules, - shape, - language, - typeOfProjectRequested, - ClassesPerModule(ClassesPerModuleType.valueOf(classesModuleType.uppercase()), classesModule), - versions = versions, - TypeOfStringResources.valueOf(typeOfStringResources.uppercase()), - layers, - generateUnitTest, - GradleWrapper(resolveGradle(gradle, versionsOverride)), - projectRootPath = resolveProjectRootPath(outputDir, language, resolvedProjectName), - develocity = develocityEnabled, - projectName = resolvedProjectName - ).write() - } - - private fun getDevelocityEnabled(develocity: Boolean, develocityUrl: String?): Boolean { - return if (develocity) { - return true - } else { - develocityUrl != null - } - } -} - -internal fun resolveVersions( - fileVersions: VersionsFile?, - dependencyInjection: DependencyInjection, - develocityUrl: String?, - roomDatabase: Boolean, - kotlinMultiplatformLibrary: Boolean -): Versions { - val versions = if (fileVersions != null) { - fileVersions.resolve() - } else { - Versions() + ).toProjectGenerator().write() } - var androidConfig = versions.android - if (roomDatabase) { - androidConfig = androidConfig.copy(roomDatabase = true) - } - if (kotlinMultiplatformLibrary) { - androidConfig = androidConfig.copy(kotlinMultiplatformLibrary = true) - } - val withAndroidFlags = versions.copy(android = androidConfig, di = dependencyInjection) - return if (develocityUrl != null) { - withAndroidFlags.copy(project = withAndroidFlags.project.copy(develocityUrl = develocityUrl)) - } else { - withAndroidFlags - } -} - -internal fun resolveProjectRootPath(outputDir: String?, language: Language, projectName: String): String { - return if (outputDir != null) { - outputDir - } else { - when (language) { - Language.KTS -> "projects_generated/$projectName/project_kts" - Language.GROOVY -> "projects_generated/$projectName/project_groovy" - Language.BOTH -> "projects_generated/$projectName" - } - } -} - -internal fun resolveGradle(cliGradle: String?, versionsFile: VersionsFile?): Gradle { - return cliGradle?.let(Gradle::fromValue) - ?: versionsFile?.gradle - ?: Gradle.latest() } class GenerateYaml : CliktCommand(name = "generate-yaml-versions") { diff --git a/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt b/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt index 4d7c09c2..77fd153c 100644 --- a/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt +++ b/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt @@ -3,13 +3,19 @@ package io.github.cdsap.projectgenerator.cli import com.github.ajalt.clikt.core.UsageError import com.github.ajalt.clikt.core.parse import io.github.cdsap.projectgenerator.model.Android +import io.github.cdsap.projectgenerator.model.ClassesPerModule +import io.github.cdsap.projectgenerator.model.ClassesPerModuleType import io.github.cdsap.projectgenerator.model.DependencyInjection import io.github.cdsap.projectgenerator.model.Gradle import io.github.cdsap.projectgenerator.model.Language import io.github.cdsap.projectgenerator.model.Project +import io.github.cdsap.projectgenerator.model.Shape +import io.github.cdsap.projectgenerator.model.TypeOfStringResources +import io.github.cdsap.projectgenerator.model.TypeProjectRequested import io.github.cdsap.projectgenerator.model.Versions import io.github.cdsap.projectgenerator.model.VersionsFile import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -193,4 +199,61 @@ class GenerateProjectsCliTest { assertEquals("projects_generated/sample", resolved) } + + @Test + fun `develocity url enables develocity when develocity flag is absent`() { + val request = GenerateProjectRequest.resolve( + modules = 6, + shape = Shape.RECTANGLE, + language = Language.KTS, + typeOfProjectRequested = TypeProjectRequested.ANDROID, + classesPerModule = ClassesPerModule(ClassesPerModuleType.FIXED, 10), + typeOfStringResources = TypeOfStringResources.NORMAL, + layers = 5, + generateUnitTest = false, + cliGradle = null, + develocityFlag = false, + develocityUrl = "https://develocity.example", + versionsFile = null, + outputDir = null, + projectName = "named", + dependencyInjection = DependencyInjection.HILT, + roomDatabase = false, + kotlinMultiplatformLibrary = false + ) + + assertTrue(request.develocity) + assertEquals("https://develocity.example", request.versions.project.develocityUrl) + } + + @Test + fun `develocity stays disabled when flag and url are both absent`() { + assertFalse(resolveDevelocityEnabled(develocity = false, develocityUrl = null)) + } + + @Test + fun `resolve builds default project name and nested root path`() { + val request = GenerateProjectRequest.resolve( + modules = 12, + shape = Shape.TRIANGLE, + language = Language.KTS, + typeOfProjectRequested = TypeProjectRequested.JVM, + classesPerModule = ClassesPerModule(ClassesPerModuleType.FIXED, 10), + typeOfStringResources = TypeOfStringResources.NORMAL, + layers = 5, + generateUnitTest = false, + cliGradle = null, + develocityFlag = false, + develocityUrl = null, + versionsFile = null, + outputDir = null, + projectName = null, + dependencyInjection = DependencyInjection.HILT, + roomDatabase = false, + kotlinMultiplatformLibrary = false + ) + + assertEquals("jvmTriangle12modules", request.projectName) + assertEquals("projects_generated/jvmTriangle12modules/project_kts", request.projectRootPath) + } }