diff --git a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/NameMappings.kt b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/NameMappings.kt index 334ffdf..fa0d1cf 100644 --- a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/NameMappings.kt +++ b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/NameMappings.kt @@ -3,11 +3,19 @@ package io.github.cdsap.projectgenerator /** * Global lookup for layer and module names. When not provided, defaults * to the conventional names `layer_` and the original module id. + * + * Prefer computing maps with [ProjectNameMappingFactory] and applying them + * via [configure] rather than mutating these properties ad hoc. */ object NameMappings { var layerNames: Map = emptyMap() var moduleNames: Map = emptyMap() + fun configure(maps: ProjectNameMaps) { + layerNames = maps.layerNames + moduleNames = maps.moduleNames + } + fun layerName(layer: Int): String = layerNames[layer] ?: "layer_${layer}" fun moduleName(id: String): String = moduleNames[id] ?: id diff --git a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt index 0589c56..257bc04 100644 --- a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt +++ b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt @@ -28,15 +28,6 @@ class ProjectGenerator( println("Creating project $projectName in $projectRootPath") println("Calculating layer Distribution") - // Generate name mappings for layers and modules - NameMappings.layerNames = (0..layers).associateWith { index -> - if (index == layers) { - "app" - } else { - layerNames.getOrNull(index) ?: "layer_$index" - } - } - val distributions = LayerDistribution(modules, layers).get(shape) println("Generating Project Dependency Graph") val nodes = ProjectGraphGenerator( @@ -46,15 +37,14 @@ class ProjectGenerator( classesPerModule ).generate() - NameMappings.moduleNames = nodes - .sortedBy { it.id.substringAfterLast("_").toInt() } - .mapIndexed { index, node -> - if (node.layer == layers) { - node.id to "app" - } else { - node.id to generateModuleName(index) - } - }.toMap() + NameMappings.configure( + ProjectNameMappingFactory.create( + layers = layers, + nodes = nodes, + layerNames = layerNames, + moduleNameParts = moduleNameParts + ) + ) val projectLanguageAttributes = getProjectLanguageAttributes() ProjectWriter( @@ -90,15 +80,4 @@ class ProjectGenerator( ) } } - - private fun generateModuleName(index: Int): String { - var remaining = index - val base = moduleNameParts.size - val parts = mutableListOf() - do { - parts.add(moduleNameParts[remaining % base]) - remaining /= base - } while (remaining > 0) - return parts.joinToString("-") - } } diff --git a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectNameMappingFactory.kt b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectNameMappingFactory.kt new file mode 100644 index 0000000..1a19fc6 --- /dev/null +++ b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectNameMappingFactory.kt @@ -0,0 +1,56 @@ +package io.github.cdsap.projectgenerator + +import io.github.cdsap.projectgenerator.model.ProjectGraph + +/** + * Immutable layer and module name maps produced by [ProjectNameMappingFactory]. + */ +data class ProjectNameMaps( + val layerNames: Map, + val moduleNames: Map +) + +/** + * Builds layer and module name maps from generation inputs without mutating + * the global [NameMappings] singleton. + */ +object ProjectNameMappingFactory { + + fun create( + layers: Int, + nodes: List, + layerNames: List, + moduleNameParts: List + ): ProjectNameMaps { + val layerMap = (0..layers).associateWith { index -> + if (index == layers) { + "app" + } else { + layerNames.getOrNull(index) ?: "layer_$index" + } + } + + val moduleMap = nodes + .sortedBy { it.id.substringAfterLast("_").toInt() } + .mapIndexed { index, node -> + if (node.layer == layers) { + node.id to "app" + } else { + node.id to generateModuleName(index, moduleNameParts) + } + }.toMap() + + return ProjectNameMaps(layerMap, moduleMap) + } + + private fun generateModuleName(index: Int, moduleNameParts: List): String { + var remaining = index + val base = moduleNameParts.size + val parts = mutableListOf() + do { + parts.add(moduleNameParts[remaining % base]) + remaining /= base + } while (remaining > 0) + return parts.joinToString("-") + } +} diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorTest.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorTest.kt index 04ea9c7..2383328 100644 --- a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorTest.kt +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorTest.kt @@ -72,4 +72,27 @@ class ProjectGeneratorTest { assert(File("$tempDir/project_kts/graph.dot").exists()) assert(File("$tempDir/project_groovy/graph.dot").exists()) } + + @Test + fun `projectGenerator configures NameMappings before writing project files`() { + val layers = 2 + ProjectGenerator( + modules = 6, + shape = Shape.RECTANGLE, + language = Language.KTS, + classesPerModule = ClassesPerModule(ClassesPerModuleType.FIXED, 10), + layers = layers, + layerNames = listOf("platform"), + moduleNameParts = listOf("alpha", "beta"), + projectRootPath = tempDir.toString(), + projectName = "named_project" + ).write() + + assert(NameMappings.layerName(0) == "platform") + assert(NameMappings.layerName(1) == "layer_1") + assert(NameMappings.layerName(layers) == "app") + assert(NameMappings.moduleNames.values.contains("app")) + assert(NameMappings.moduleNames.values.any { it == "alpha" || it == "beta" || it.contains("-") }) + assert(File("$tempDir/settings.gradle.kts").readText().contains(":app:app")) + } } diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectNameMappingFactoryTest.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectNameMappingFactoryTest.kt new file mode 100644 index 0000000..4acaac0 --- /dev/null +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectNameMappingFactoryTest.kt @@ -0,0 +1,98 @@ +package io.github.cdsap.projectgenerator + +import io.github.cdsap.projectgenerator.model.ProjectGraph +import io.github.cdsap.projectgenerator.model.TypeProject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class ProjectNameMappingFactoryTest { + + @Test + fun `maps app layer to app and honors custom and fallback layer names`() { + val maps = ProjectNameMappingFactory.create( + layers = 3, + nodes = emptyList(), + layerNames = listOf("core", "feature"), + moduleNameParts = listOf("push", "contact") + ) + + assertEquals("core", maps.layerNames[0]) + assertEquals("feature", maps.layerNames[1]) + assertEquals("layer_2", maps.layerNames[2]) + assertEquals("app", maps.layerNames[3]) + } + + @Test + fun `generates module names from module name parts in sequence`() { + val nodes = listOf( + node("module_0_1", layer = 0), + node("module_0_2", layer = 0), + node("module_1_3", layer = 1), + node("module_2_4", layer = 2) + ) + val parts = listOf("push", "contact", "login") + + val maps = ProjectNameMappingFactory.create( + layers = 2, + nodes = nodes, + layerNames = listOf("core", "feature"), + moduleNameParts = parts + ) + + assertEquals("push", maps.moduleNames["module_0_1"]) + assertEquals("contact", maps.moduleNames["module_0_2"]) + assertEquals("login", maps.moduleNames["module_1_3"]) + assertEquals("app", maps.moduleNames["module_2_4"]) + } + + @Test + fun `module names wrap with hyphenated parts beyond the alphabet size`() { + val nodes = (1..4).map { index -> + node("module_0_$index", layer = 0) + } + val parts = listOf("alpha", "beta") + + val maps = ProjectNameMappingFactory.create( + layers = 1, + nodes = nodes, + layerNames = listOf("core"), + moduleNameParts = parts + ) + + assertEquals("alpha", maps.moduleNames["module_0_1"]) + assertEquals("beta", maps.moduleNames["module_0_2"]) + assertEquals("alpha-beta", maps.moduleNames["module_0_3"]) + assertEquals("beta-beta", maps.moduleNames["module_0_4"]) + } + + @Test + fun `configure applies computed maps to NameMappings`() { + val previousLayers = NameMappings.layerNames + val previousModules = NameMappings.moduleNames + try { + val maps = ProjectNameMappingFactory.create( + layers = 1, + nodes = listOf(node("module_0_1", layer = 0), node("module_1_2", layer = 1)), + layerNames = listOf("domain"), + moduleNameParts = listOf("push") + ) + NameMappings.configure(maps) + + assertEquals("domain", NameMappings.layerName(0)) + assertEquals("app", NameMappings.layerName(1)) + assertEquals("push", NameMappings.moduleName("module_0_1")) + assertEquals("app", NameMappings.moduleName("module_1_2")) + } finally { + NameMappings.layerNames = previousLayers + NameMappings.moduleNames = previousModules + } + } + + private fun node(id: String, layer: Int) = ProjectGraph( + id = id, + layer = layer, + nodes = emptyList(), + type = TypeProject.ANDROID_LIB, + classes = 1 + ) +}