Skip to content

Commit

Permalink
Fix behavior of project to by default ignore the filename option spec…
Browse files Browse the repository at this point in the history
…ified for this plugin in the parent project's gradle properties; This changes can be disabled by gradle.properties in the root project
  • Loading branch information
uzzu committed Nov 26, 2023
1 parent 05a7830 commit b39168c
Show file tree
Hide file tree
Showing 5 changed files with 495 additions and 31 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added

### Changed
- The behavior of project to ignore the filename option specified for this plugin in the parent project's gradle properties by default.
- For example, if `dotenv.filename=.env.staging` is set in the root project, this setting will automatically apply to sub-projects as well. While this follows the correct resolution order of Gradle Properties, it has been a source of confusion for users working with dotenv.
- To disable this default behavior, add `dotenv.filename.ignore.parent=false` to the gradle.properties in the root project.
- A same update has been applied to the specification of template file names. To disable this default behavior, add `dotenv.template.filename.ignore.parent=false` to the gradle.properties in the root project.

### Deprecated

Expand Down
165 changes: 165 additions & 0 deletions plugin/src/main/kotlin/co/uzzu/dotenv/gradle/Configuration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package co.uzzu.dotenv.gradle

import org.gradle.api.Project
import org.slf4j.LoggerFactory
import java.util.Properties

internal interface Configuration {
val filename: String
val templateFilename: String
}

internal interface RootConfiguration : Configuration {
val ignoreParentFilename: Boolean
val ignoreParentTemplateFilename: Boolean
}

@Suppress("ConstPropertyName")
object ConfigurationKey {
const val Filename: String = RootConfigurationKey.Filename
const val TemplateFilename: String = RootConfigurationKey.TemplateFilename
}

@Suppress("ConstPropertyName")
object RootConfigurationKey {
const val IgnoreParentFilename: String = "dotenv.filename.ignore.parent"
const val IgnoreParentTemplateFilename: String = "dotenv.template.filename.ignore.parent"

const val Filename: String = "dotenv.filename"
const val TemplateFilename: String = "dotenv.template.filename"
}

internal object DefaultConfiguration : Configuration {
override val filename: String = DefaultRootConfiguration.filename
override val templateFilename: String = DefaultRootConfiguration.templateFilename
}

internal object DefaultRootConfiguration : RootConfiguration {
override val ignoreParentFilename: Boolean = true
override val ignoreParentTemplateFilename: Boolean = true

override val filename: String = ".env"
override val templateFilename: String = ".env.template"
}

internal class ConfigurationResolver(
private val project: Project,
) {
private val logger = LoggerFactory.getLogger(this::class.java.name)

private val rootConfiguration: RootConfiguration by lazy { createRootConfiguration() }

fun resolve(): Configuration {
return if (project == project.rootProject) {
rootConfiguration
} else {
val gradlePropertiesFromFile = project.gradlePropertiesFromFile()
ConfigurationImpl(
filename = resolveStringFor(
project,
gradlePropertiesFromFile,
ConfigurationKey.Filename,
DefaultRootConfiguration.filename,
rootConfiguration.ignoreParentFilename,
),
templateFilename = resolveStringFor(
project,
gradlePropertiesFromFile,
ConfigurationKey.TemplateFilename,
DefaultRootConfiguration.templateFilename,
rootConfiguration.ignoreParentTemplateFilename,
),
)
}
}

private fun resolveStringFor(
project: Project,
gradlePropertiesFromFile: Properties,
key: String,
defaultValue: String,
ignoreParent: Boolean
): String = if (ignoreParent) {
gradlePropertiesFromFile.getProperty(key, defaultValue)
} else {
project.stringProperty(key, defaultValue)
}

private fun createRootConfiguration(): RootConfiguration =
project.rootProject.let {
RootConfigurationImpl(
ignoreParentFilename = it.boolProperty(
RootConfigurationKey.IgnoreParentFilename,
DefaultRootConfiguration.ignoreParentFilename,
),
ignoreParentTemplateFilename = it.boolProperty(
RootConfigurationKey.IgnoreParentTemplateFilename,
DefaultRootConfiguration.ignoreParentTemplateFilename,
),
filename = it.stringProperty(
RootConfigurationKey.Filename,
DefaultRootConfiguration.filename,
),
templateFilename = it.stringProperty(
RootConfigurationKey.TemplateFilename,
DefaultRootConfiguration.templateFilename,
),
)
}

private fun Project.gradlePropertiesFromFile(): Properties {
val result = Properties()
val gradlePropertiesFile = file(Project.GRADLE_PROPERTIES)
if (gradlePropertiesFile.exists()) {
gradlePropertiesFile.inputStream().use { result.load(it) }
}
return result
}

private fun Project.stringProperty(key: String, defaultValue: String): String =
if (properties.containsKey(key)) {
properties[key] as String
} else {
defaultValue
}

private fun Project.boolProperty(key: String, defaultValue: Boolean): Boolean =
if (properties.containsKey(key)) {
@Suppress("MoveVariableDeclarationIntoWhen", "RedundantSuppression")
val value = properties[key] as String
when (value) {
"true" -> {
true
}

"false" -> {
false
}

else -> {
this@ConfigurationResolver.logger.warn(
buildString {
append("Could not resolve Boolean properties for key $key.")
append(""" Expect should be set "true" or "false", but was "$value". """)
append(" The plugin uses default value $defaultValue.")
}
)
defaultValue
}
}
} else {
defaultValue
}
}

private data class ConfigurationImpl(
override val filename: String,
override val templateFilename: String,
) : Configuration

private data class RootConfigurationImpl(
override val ignoreParentFilename: Boolean,
override val ignoreParentTemplateFilename: Boolean,
override val filename: String,
override val templateFilename: String,
) : RootConfiguration
42 changes: 11 additions & 31 deletions plugin/src/main/kotlin/co/uzzu/dotenv/gradle/DotEnvResolver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ internal class DotEnvResolver(project: Project) {

private fun Project.dotenv(): Map<String, String?> {
require(this@dotenv.rootProject == this@DotEnvResolver.rootProject)
val config = ConfigurationResolver(this).resolve()
if (dotenvCache[this] == null) {
val dotenvTemplate = dotenvTemplate()
val dotenvSource = dotenvSource()
val dotenvTemplate = dotenvTemplate(config)
val dotenvSource = dotenvSource(config)
val variables = dotenvTemplate.keys
.union(dotenvSource.keys)
.associateWith { dotenvSource[it] }
Expand All @@ -47,16 +48,16 @@ internal class DotEnvResolver(project: Project) {
return checkNotNull(dotenvCache[project])
}

private fun Project.dotenvTemplate(): Map<String, String> {
val filename = dotenvTemplateFilename()
private fun Project.dotenvTemplate(config: Configuration): Map<String, String> {
val filename = config.templateFilename
.let {
if (it != DEFAULT_TEMPLATE_FILENAME) {
if (it != DefaultConfiguration.templateFilename) {
val templateFile = file(it)
if (!templateFile.exists() || !templateFile.canRead()) {
throw IOException(
buildString {
append("Could not read the dotenv template file specified in the gradle.properties.")
append(" $PROPERTY_TEMPLATE_FILENAME: $it,")
append(" ${ConfigurationKey.TemplateFilename}: $it,")
append(" path: ${templateFile.absolutePath}")
}
)
Expand All @@ -67,16 +68,16 @@ internal class DotEnvResolver(project: Project) {
return readText(filename).let(DotEnvParser::parse)
}

private fun Project.dotenvSource(): Map<String, String> {
val envFilename = dotenvFilename()
private fun Project.dotenvSource(config: Configuration): Map<String, String> {
val envFilename = config.filename
.let {
if (it != DEFAULT_FILENAME) {
if (it != DefaultConfiguration.filename) {
val envFile = file(it)
if (!envFile.exists() || !envFile.canRead()) {
throw IOException(
buildString {
append("Could not read the dotenv file specified in the gradle.properties.")
append(" $PROPERTY_FILENAME: $it,")
append(" ${ConfigurationKey.Filename}: $it,")
append(" path: ${envFile.absolutePath}")
}
)
Expand All @@ -87,20 +88,6 @@ internal class DotEnvResolver(project: Project) {
return readText(envFilename).let(DotEnvParser::parse)
}

private fun Project.dotenvFilename(): String =
if (properties.containsKey(PROPERTY_FILENAME)) {
properties[PROPERTY_FILENAME] as String
} else {
DEFAULT_FILENAME
}

private fun Project.dotenvTemplateFilename(): String =
if (properties.containsKey(PROPERTY_TEMPLATE_FILENAME)) {
properties[PROPERTY_TEMPLATE_FILENAME] as String
} else {
DEFAULT_TEMPLATE_FILENAME
}

private fun Project.readText(filename: String): String {
val file = file(filename)
return if (file.exists()) {
Expand All @@ -109,11 +96,4 @@ internal class DotEnvResolver(project: Project) {
""
}
}

companion object {
private const val DEFAULT_FILENAME = ".env"
private const val DEFAULT_TEMPLATE_FILENAME = ".env.template"
private const val PROPERTY_FILENAME = "dotenv.filename"
private const val PROPERTY_TEMPLATE_FILENAME = "dotenv.template.filename"
}
}
Loading

0 comments on commit b39168c

Please sign in to comment.