GetUpdatedModules.kt
package com.sebastmar.module.report.internal.domain
import com.sebastmar.module.report.configuration.ModulesInterceptor
import com.sebastmar.module.report.info.Module
import com.sebastmar.module.report.info.ModuleType
import com.sebastmar.module.report.info.VersionedFile
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.pathString
import kotlin.io.path.relativeTo
/**
* Interface for retrieving a list of modules that have been changed in the current pull request.
*/
internal interface GetUpdatedModules {
operator fun invoke(): List<Module>
}
/**
* Get the created, modified, and deleted files then groups them into their respective modules.
*
* It processes the files to identify the modules they belong to.
* [ModulesInterceptor] is used to filter or modify the resulting list of modules that'll be displayed by danger.
*/
internal class GetUpdatedModulesImpl(
private val getAllVersionedFiles: GetAllVersionedFiles,
private val getProjectRoot: GetProjectRoot,
private val modulesInterceptor: ModulesInterceptor,
) : GetUpdatedModules {
private val projectRoot: Path by lazy { getProjectRoot() }
override fun invoke(): List<Module> {
return getAllVersionedFiles()
.groupBy(::findCorrectModule)
.map { (module, files) -> module.copy(files = files) }
.let(modulesInterceptor::intercept)
}
/**
* Finds the appropriate module for a specified versioned file within the project structure.
*
* The method traverses the directory hierarchy starting from the file's location,
* checking whether each directory is a recognized module (either a root Gradle module or
* a standard Gradle module). If a matching module is found, it is returned.
*
* @param versionedFile The versioned file for which the containing module needs to be identified.
* @return The module containing the provided file, or an "unknown module" if no match is found.
*/
private fun findCorrectModule(versionedFile: VersionedFile): Module {
val startingPath = projectRoot.resolve(versionedFile.fullPath).parent
return generateSequence(startingPath, Path::getParent).firstNotNullOfOrNull { path ->
when {
path.isRootModule() && startingPath == path -> PROJECT_ROOT_MODULE
path.isStandardModule() -> Module(name = path.relativeTo(projectRoot).pathString.replace("/", ":"))
else -> null
}
} ?: UNKNOWN_MODULE
}
/**
* Checks if the current path represents a standard Gradle module.
*
* A directory is considered a standard module if it contains either a `build.gradle.kts`
* or a `build.gradle` file.
*/
private fun Path.isStandardModule(): Boolean {
return hasBuildGradle() && !hasSettingsGradle()
}
/**
* Checks if the current path represents the root Gradle module.
*
* A directory is considered the root module if it contains either a `settings.gradle.kts`
* or a `settings.gradle` file.
*/
private fun Path.isRootModule(): Boolean {
return hasSettingsGradle()
}
private fun Path.hasBuildGradle(): Boolean {
return resolve("build.gradle.kts").exists() || resolve("build.gradle").exists()
}
private fun Path.hasSettingsGradle(): Boolean {
return resolve("settings.gradle.kts").exists() || resolve("settings.gradle").exists()
}
}
private val PROJECT_ROOT_MODULE = Module("Project's Root", ModuleType.PROJECT_ROOT)
private val UNKNOWN_MODULE = Module("Others", ModuleType.NOT_KNOWN)