Try it on Kotlin Playground https://pl.kotl.in/9oXsIoDNS
or edit the sample belowClick "run" (on the right) to see the preview!
fun main() {
val modules = config.modules.toProjectModuleList(config)
println("This script will create the following ${modules.size} module${if (modules.size == 1) "" else "s"}:")
modules.forEach {
println(
"""|
|Create module [${it.templateName}] ${it.moduleNameParts.joinToString(" ") { it.capitalize() }}
| - destinationDir: ${it.destinationDirPart}
| - packageName: ${it.packageName}
| - gradlePath: ${it.gradlePath}""".trimMargin()
)
}
println("\nDo you want to continue?")
}
//sampleStart
val config = ScriptGradleModuleConfig(
gradleConfig = GradleConfig(
packagePrefix = "com.github.owner.projectname",
templatesDirPath = "templates",
settingsGradleFilename = "settings.gradle.kts",
dependenciesFilename = "buildSrc/src/main/kotlin/Dependencies.kt",
dependenciesPrefix = "module",
),
modules = listOf(LibAndroid("my-lib")),
logLevel = 1,
hasPrompt = true,
writeOnDisk = true,
forceNamingConvention = false,
)
//sampleEnd
//
// ScriptGradleModuleConfig.kt
//
data class ScriptGradleModuleConfig(
val gradleConfig: GradleConfig = GradleConfig("", "", "", "", ""),
val modules: List<GradleComponent> = emptyList(),
val logLevel: Int = -1,
val hasPrompt: Boolean = false,
val writeOnDisk: Boolean = false,
val forceNamingConvention: Boolean = false,
)
data class GradleConfig(
val packagePrefix: String,
val templatesDirPath: String,
val settingsGradleFilename: String,
val dependenciesFilename: String,
val dependenciesPrefix: String,
)
//
// GradleComponent.kt
//
sealed class GradleComponent(
open val gradleName: String,
open val packageNamePart: String?,
open val extra: Map<String, Any> = emptyMap(),
)
// GradleComponents
data class Group(
override val gradleName: String,
val modules: List<GradleComponent>,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
) : GradleComponent(gradleName, packageNamePart, extra)
sealed class GradleModule(
gradleName: String,
packageNamePart: String?,
extra: Map<String, Any>,
open val templateName: String,
) : GradleComponent(gradleName, packageNamePart, extra)
// GradleModules
class App(
override val gradleName: String,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
override val templateName: String = "app",
) : GradleModule(gradleName, packageNamePart, extra, templateName)
data class LibAndroid(
override val gradleName: String,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
override val templateName: String = "lib-android",
) : GradleModule(gradleName, packageNamePart, extra, templateName)
data class LibKotlin(
override val gradleName: String,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
override val templateName: String = "lib-kotlin",
) : GradleModule(gradleName, packageNamePart, extra, templateName)
// Utils
fun String.toPackageName(): String =
toLowerCase().replace("-", "")
//
// ProjectModule.kt
//
data class ProjectModule(
val configModule: GradleModule,
val configGroups: List<Group>,
val config: ScriptGradleModuleConfig,
) {
val templateName: String = configModule.templateName
val moduleNameParts: List<String> =
configGroups.flatMap { it.gradleName.toModuleNameParts() } +
configModule.gradleName.toModuleNameParts()
val gradlePathParts: List<String> =
(configGroups.map { it.gradleName } + configModule.gradleName)
.runningReduce { acc, s ->
if (!config.forceNamingConvention || s.startsWith("$acc-")) s else "$acc-$s"
}
/**
* Full gradle path, e.g., :feature:navigation:core
*/
val gradlePath: String = gradlePathParts.joinToString(":", prefix = ":")
/**
* Full package name, e.g., com.github.owner.projectname.myfeature
*/
val packageName: String = buildString {
val prefix = listOf(config.gradleConfig.packagePrefix)
val parts = (configGroups.map { it.packageNamePart } + configModule.packageNamePart).filterNotNull()
.filter { it.isNotBlank() }
append((prefix + parts).joinToString("."))
}
val destinationDirPart: String = gradlePathParts.joinToString("/")
private fun String.toModuleNameParts(): List<String> = toLowerCase().split("[^a-z0-9]".toRegex())
}
fun List<GradleComponent>.toProjectModuleList(config: ScriptGradleModuleConfig): List<ProjectModule> =
flatMap { it.toProjectModuleList(config) }
fun GradleComponent.toProjectModuleList(config: ScriptGradleModuleConfig): List<ProjectModule> =
when (this) {
is Group -> modules.flatMap {
it.toProjectModuleList(config)
.map { (module, packages) ->
ProjectModule(module, listOf(this) + packages, config)
}
}
is GradleModule -> listOf(ProjectModule(this, emptyList(), config))
}
Click "run" (on the right) to see the preview!
fun main() {
val modules = config.modules.toProjectModuleList(config)
println("This script will create the following ${modules.size} module${if (modules.size == 1) "" else "s"}:")
modules.forEach {
println(
"""|
|Create module [${it.templateName}] ${it.moduleNameParts.joinToString(" ") { it.capitalize() }}
| - destinationDir: ${it.destinationDirPart}
| - packageName: ${it.packageName}
| - gradlePath: ${it.gradlePath}""".trimMargin()
)
}
println("\nDo you want to continue?")
}
//sampleStart
val config = ScriptGradleModuleConfig(
gradleConfig = GradleConfig(
packagePrefix = "com.github.owner.projectname",
templatesDirPath = "templates",
settingsGradleFilename = "settings.gradle.kts",
dependenciesFilename = "buildSrc/src/main/kotlin/Dependencies.kt",
dependenciesPrefix = "module",
),
modules = listOf(
Group(
gradleName = "feature-name",
modules = listOf(
App(
gradleName = "app",
),
LibAndroid(
gradleName = "ui",
),
LibKotlin(
gradleName = "core",
)
)
),
LibAndroid(
gradleName = "my-lib",
)
),
logLevel = 1,
hasPrompt = true,
writeOnDisk = true,
forceNamingConvention = false,
)
//sampleEnd
//
// ScriptGradleModuleConfig.kt
//
data class ScriptGradleModuleConfig(
val gradleConfig: GradleConfig = GradleConfig("", "", "", "", ""),
val modules: List<GradleComponent> = emptyList(),
val logLevel: Int = -1,
val hasPrompt: Boolean = false,
val writeOnDisk: Boolean = false,
val forceNamingConvention: Boolean = false,
)
data class GradleConfig(
val packagePrefix: String,
val templatesDirPath: String,
val settingsGradleFilename: String,
val dependenciesFilename: String,
val dependenciesPrefix: String,
)
//
// GradleComponent.kt
//
sealed class GradleComponent(
open val gradleName: String,
open val packageNamePart: String?,
open val extra: Map<String, Any> = emptyMap(),
)
// GradleComponents
data class Group(
override val gradleName: String,
val modules: List<GradleComponent>,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
) : GradleComponent(gradleName, packageNamePart, extra)
sealed class GradleModule(
gradleName: String,
packageNamePart: String?,
extra: Map<String, Any>,
open val templateName: String,
) : GradleComponent(gradleName, packageNamePart, extra)
// GradleModules
class App(
override val gradleName: String,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
override val templateName: String = "app",
) : GradleModule(gradleName, packageNamePart, extra, templateName)
data class LibAndroid(
override val gradleName: String,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
override val templateName: String = "lib-android",
) : GradleModule(gradleName, packageNamePart, extra, templateName)
data class LibKotlin(
override val gradleName: String,
override val packageNamePart: String? = gradleName.toPackageName(),
override val extra: Map<String, Any> = emptyMap(),
override val templateName: String = "lib-kotlin",
) : GradleModule(gradleName, packageNamePart, extra, templateName)
// Utils
fun String.toPackageName(): String =
toLowerCase().replace("-", "")
//
// ProjectModule.kt
//
data class ProjectModule(
val configModule: GradleModule,
val configGroups: List<Group>,
val config: ScriptGradleModuleConfig,
) {
val templateName: String = configModule.templateName
val moduleNameParts: List<String> =
configGroups.flatMap { it.gradleName.toModuleNameParts() } +
configModule.gradleName.toModuleNameParts()
val gradlePathParts: List<String> =
(configGroups.map { it.gradleName } + configModule.gradleName)
.runningReduce { acc, s ->
if (!config.forceNamingConvention || s.startsWith("$acc-")) s else "$acc-$s"
}
/**
* Full gradle path, e.g., :feature:navigation:core
*/
val gradlePath: String = gradlePathParts.joinToString(":", prefix = ":")
/**
* Full package name, e.g., com.github.owner.projectname.myfeature
*/
val packageName: String = buildString {
val prefix = listOf(config.gradleConfig.packagePrefix)
val parts = (configGroups.map { it.packageNamePart } + configModule.packageNamePart).filterNotNull()
.filter { it.isNotBlank() }
append((prefix + parts).joinToString("."))
}
val destinationDirPart: String = gradlePathParts.joinToString("/")
private fun String.toModuleNameParts(): List<String> = toLowerCase().split("[^a-z0-9]".toRegex())
}
fun List<GradleComponent>.toProjectModuleList(config: ScriptGradleModuleConfig): List<ProjectModule> =
flatMap { it.toProjectModuleList(config) }
fun GradleComponent.toProjectModuleList(config: ScriptGradleModuleConfig): List<ProjectModule> =
when (this) {
is Group -> modules.flatMap {
it.toProjectModuleList(config)
.map { (module, packages) ->
ProjectModule(module, listOf(this) + packages, config)
}
}
is GradleModule -> listOf(ProjectModule(this, emptyList(), config))
}