diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a8399f --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) +!/.mvn/wrapper/maven-wrapper.jar + diff --git a/java-dotenv.iml b/java-dotenv.iml new file mode 100644 index 0000000..e37d889 --- /dev/null +++ b/java-dotenv.iml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e6ceb5a --- /dev/null +++ b/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + io.cdimascio + java-dotenv + 1.0-SNAPSHOT + + + 1.1.61 + io.cdimascio.DotenvKt + 4.12 + + UTF-8 + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + + junit + junit + ${junit.version} + test + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + + compile + compile + + + + test-compile + test-compile + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + true + ${main.class} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + ${project.basedir} + + + + + + \ No newline at end of file diff --git a/src/main/kotlin/io/cdimascio/Dotenv.kt b/src/main/kotlin/io/cdimascio/Dotenv.kt new file mode 100644 index 0000000..67f8a01 --- /dev/null +++ b/src/main/kotlin/io/cdimascio/Dotenv.kt @@ -0,0 +1,107 @@ +package io.cdimascio + +import java.nio.file.Files +import java.nio.file.Paths +import java.util.stream.Collectors +import java.util.stream.Stream + +interface Dotenv { + companion object Factory { + fun configure(): DotenvBuilder = DotenvBuilder() + } + fun get(envVar: String): String? +} + +class DotEnvException : Exception { + constructor(message: String): super(message) + constructor(throwable: Throwable): super(throwable) +} + +class DotenvBuilder internal constructor() { + private var filename = ".env" + private var directoryPath = System.getProperty("user.home") + private var throwIfMissing = true + private var throwIfMalformed = true + + fun withDirectory(path: String = directoryPath): DotenvBuilder { + directoryPath = path + return this + } + + fun ingoreIfMissing(): DotenvBuilder { + throwIfMissing = false + return this + } + + fun ignoreIfMalformed(): DotenvBuilder { + throwIfMalformed = false + return this + } + + fun build(): Dotenv { + val reader = DotEnvReader(directoryPath, filename, throwIfMalformed, throwIfMissing) + val env = reader.read() +// applyEnv(env) + return DotenvImpl(env) + } + +// private fun applyEnv(pairs: List>) { +// val processBuilder = ProcessBuilder() +// val env = processBuilder.environment() +// pairs.forEach { +// println("applying to env ${it.first} ${it.second}") +// env[it.first] = it.second +// } +// +// } +} + +private class DotenvImpl(envVars: List>): Dotenv { + val map = envVars.associateBy({ it.first }, { it.second }) + + override fun get(envVar: String): String? = map[envVar] ?: System.getenv(envVar) +} + +private class DotEnvReader( + val directory: String, + val filename: String = ".env", + val throwIfMalformed: Boolean, + val throwIfMissing: Boolean +) { + private val commentHash = "#" + private val commentSlashes = """//""" + + fun read() = parse() + + private fun parse(): List> { + val isWhiteSpace = { s: String -> """^\s*${'$'}""".toRegex().matches(s) } + val isComment = { s: String -> s.startsWith(commentHash) || s.startsWith(commentSlashes) } + val parseLine = { s: String -> """^\s*([\w.\-]+)\s*(=)\s*(.*)?\s*$""".toRegex().matchEntire(s) } + + val userSpecifiedPath = Paths.get(directory, filename) + val cwd = Paths.get(System.getProperty("user.dir")) + val path = cwd.resolve(userSpecifiedPath) + val lines = + try { Files.lines(path) } + catch (e: Exception) { + if (throwIfMissing) throw DotEnvException(e) + else Stream.empty() + }.collect(Collectors.toList()) + + return lines + .map { it.trim() } + .filter { !isWhiteSpace(it) } + .filter { !isComment(it) } + .mapNotNull { + val match = parseLine(it) + if (match != null) { + val (key, _, value) = match.destructured + Pair(key, value) + } else { + if (throwIfMalformed) throw DotEnvException("Malformed entry: $it") + else null + } + } + } +} + diff --git a/src/test/kotlin/.gitignore b/src/test/kotlin/.gitignore new file mode 100644 index 0000000..5a8399f --- /dev/null +++ b/src/test/kotlin/.gitignore @@ -0,0 +1,88 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) +!/.mvn/wrapper/maven-wrapper.jar + diff --git a/src/test/kotlin/tests.kt b/src/test/kotlin/tests.kt new file mode 100644 index 0000000..c273b88 --- /dev/null +++ b/src/test/kotlin/tests.kt @@ -0,0 +1,61 @@ +package tests + +import io.cdimascio.DotEnvException +import io.cdimascio.Dotenv +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.junit.Test as test + +class DotEnvTest() { + val envVars = mapOf( + "MY_TEST_EV1" to "my test ev 1", + "MY_TEST_EV2" to "my test ev 1" + ) + + @test(expected = DotEnvException::class) fun dotenvMalformed() { + Dotenv + .configure() + .withDirectory("./src/test/resources") + .build() + } + + @test fun dotenvIgnoreMalformed() { + val dotEnv = Dotenv + .configure() + .withDirectory("./src/test/resources") + .ignoreIfMalformed() + .build() + + + envVars.forEach { + val expected = it.value + val actual = dotEnv.get(it.key) + assertEquals(expected, actual) + } + + val expectedHome = System.getProperty("user.home") + val actualHome = dotEnv.get("HOME") + assertEquals(expectedHome, actualHome) + + } + + @test(expected = DotEnvException::class) fun dotenvMissing() { + Dotenv.configure() + .withDirectory("/missing/.env") + .build() + } + + @test fun dotenvIgnoreMissing() { + Dotenv.configure() + .withDirectory("/missing/.env") + .ingoreIfMissing() + .build() + } + + @test fun dotenvAbsolutePath() { + Dotenv.configure() + .withDirectory("/missing/.env") + .ingoreIfMissing() + .build() + } +} \ No newline at end of file diff --git a/src/test/resources/.env b/src/test/resources/.env new file mode 100644 index 0000000..7c86908 --- /dev/null +++ b/src/test/resources/.env @@ -0,0 +1,7 @@ +## Good test EVs +MY_TEST_EV1=my test ev 1 +MY_TEST_EV2=my test ev 1 + + +## Bad EV +MY_TEST_EV3 \ No newline at end of file