This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 04a150935f feat: add SVL dotenv and env-file variable support
04a150935f is described below

commit 04a150935fab0cf738e30bec2de6d40220ac7c0f
Author: James Bognar <[email protected]>
AuthorDate: Fri May 15 07:16:47 2026 -0400

    feat: add SVL dotenv and env-file variable support
---
 .../org/apache/juneau/commons/svl/VarList.java     |  4 ++
 .../org/apache/juneau/commons/svl/VarResolver.java |  4 ++
 .../apache/juneau/commons/svl/vars/DotenvVar.java  | 72 ++++++++++++++++++++++
 .../apache/juneau/commons/svl/vars/EnvFileVar.java | 69 +++++++++++++++++++++
 .../juneau/commons/svl/vars/PropertyVars_Test.java | 51 +++++++++++++++
 todo/FINISHED-22-svl-external-config-vars.md       | 22 +++++++
 todo/TODO.md                                       |  2 -
 7 files changed, 222 insertions(+), 2 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
index 78d2935c63..d6e1ba9078 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
@@ -88,6 +88,8 @@ public class VarList extends ArrayList<Object> {
         *      <li>{@link PropertyVar}
         *      <li>{@link SystemPropertiesVar}
         *      <li>{@link EnvVariablesVar}
+        *      <li>{@link EnvFileVar}
+        *      <li>{@link DotenvVar}
         *      <li>{@link ArgsVar}
         *      <li>{@link ManifestFileVar}
         *      <li>{@link SwitchVar}
@@ -111,6 +113,8 @@ public class VarList extends ArrayList<Object> {
                        PropertyVar.class,
                        SystemPropertiesVar.class,
                        EnvVariablesVar.class,
+                       EnvFileVar.class,
+                       DotenvVar.class,
                        ManifestFileVar.class,
                        ArgsVar.class,
                        SwitchVar.class,
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarResolver.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarResolver.java
index a06e54975d..142fdfbaac 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarResolver.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarResolver.java
@@ -134,6 +134,8 @@ public class VarResolver {
                 * <ul>
                 *      <li>{@link SystemPropertiesVar}
                 *      <li>{@link EnvVariablesVar}
+        *      <li>{@link EnvFileVar}
+        *      <li>{@link DotenvVar}
                 *      <li>{@link ArgsVar}
                 *      <li>{@link ManifestFileVar}
                 *      <li>{@link SwitchVar}
@@ -213,6 +215,8 @@ public class VarResolver {
         * <ul>
         *      <li><c>$S{key[,default]}</c> - {@link SystemPropertiesVar}
         *      <li><c>$E{key[,default]}</c> - {@link EnvVariablesVar}
+        *      <li><c>$EF{key[,default]}</c> - {@link EnvFileVar}
+        *      <li><c>$DE{key[,default]}</c> - {@link DotenvVar}
         *      <li><c>$A{key[,default]}</c> - {@link ArgsVar}
         *      <li><c>$MF{key[,default]}</c> - {@link ManifestFileVar}
         *      
<li><c>$SW{stringArg,pattern:thenValue[,pattern:thenValue...]}</c> - {@link 
SwitchVar}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/DotenvVar.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/DotenvVar.java
new file mode 100644
index 0000000000..8564c750b7
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/DotenvVar.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.svl.vars;
+
+import java.nio.file.*;
+
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.commons.svl.*;
+
+/**
+ * Dotenv file variable resolver.
+ *
+ * <p>
+ * The format for this var is <js>"$DE{key[,default]}"</js>.
+ *
+ * <p>
+ * Values are resolved from {@link DotenvPropertySource} using either:
+ * <ul class='spaced-list'>
+ *     <li>The default dotenv path resolution when using the no-arg 
constructor.
+ *     <li>The path provided via {@link #create(Path)}.
+ * </ul>
+ */
+public class DotenvVar extends DefaultingVar {
+
+       /** The name of this variable. */
+       public static final String NAME = "DE";
+
+       /**
+        * Creates a {@link DotenvVar} bound to a specific dotenv file path.
+        *
+        * @param path The dotenv file path.
+        * @return A new {@link DotenvVar} instance.
+        */
+       public static DotenvVar create(Path path) {
+               return new DotenvVar(path);
+       }
+
+       private final DotenvPropertySource source;
+
+       /**
+        * Constructor.
+        */
+       public DotenvVar() {
+               super(NAME);
+               this.source = new DotenvPropertySource();
+       }
+
+       private DotenvVar(Path path) {
+               super(NAME);
+               this.source = new DotenvPropertySource(path);
+       }
+
+       @Override /* Overridden from Var */
+       public String resolve(VarResolverSession session, String key) {
+               var result = source.get(key);
+               return result.isPresent() ? result.value().orElse(null) : null;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvFileVar.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvFileVar.java
new file mode 100644
index 0000000000..2976802f42
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvFileVar.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.svl.vars;
+
+import java.nio.file.*;
+
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.commons.svl.*;
+
+/**
+ * Environment-file variable resolver.
+ *
+ * <p>
+ * The format for this var is <js>"$EF{key[,default]}"</js>.
+ *
+ * <p>
+ * This variable is functionally identical to {@link DotenvVar}, but provides a
+ * generic env-file naming option for callers that do not want dotenv-specific 
naming.
+ */
+public class EnvFileVar extends DefaultingVar {
+
+       /** The name of this variable. */
+       public static final String NAME = "EF";
+
+       /**
+        * Creates an {@link EnvFileVar} bound to a specific env file path.
+        *
+        * @param path The env file path.
+        * @return A new {@link EnvFileVar} instance.
+        */
+       public static EnvFileVar create(Path path) {
+               return new EnvFileVar(path);
+       }
+
+       private final DotenvPropertySource source;
+
+       /**
+        * Constructor.
+        */
+       public EnvFileVar() {
+               super(NAME);
+               this.source = new DotenvPropertySource();
+       }
+
+       private EnvFileVar(Path path) {
+               super(NAME);
+               this.source = new DotenvPropertySource(path);
+       }
+
+       @Override /* Overridden from Var */
+       public String resolve(VarResolverSession session, String key) {
+               var result = source.get(key);
+               return result.isPresent() ? result.value().orElse(null) : null;
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
index f55fe419c0..4a414ac158 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
@@ -18,6 +18,8 @@ package org.apache.juneau.commons.svl.vars;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+import java.io.*;
+import java.nio.file.*;
 import java.util.jar.*;
 
 import org.apache.juneau.*;
@@ -137,4 +139,53 @@ class PropertyVars_Test extends TestBase {
                        Settings.get().unsetGlobal("PropertyVars_Test.c03");
                }
        }
+
+       
//====================================================================================================
+       // DotenvVar / EnvFileVar
+       
//====================================================================================================
+
+       @Test
+       void d01_dotenvVar_create_resolvesKeyAndDefault() throws IOException {
+               var tmp = Files.createTempFile("juneau-svl-dotenv-", ".env");
+               Files.writeString(tmp, "API_KEY=abc123\n");
+               try {
+                       var vr = 
VarResolver.create().vars(DotenvVar.create(tmp)).build();
+                       assertEquals("abc123", vr.resolve("$DE{API_KEY}"));
+                       assertEquals("fallback", 
vr.resolve("$DE{MISSING,fallback}"));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d02_envFileVar_create_resolvesKeyAndDefault() throws IOException {
+               var tmp = Files.createTempFile("juneau-svl-envfile-", ".env");
+               Files.writeString(tmp, "REGION=us-east-1\n");
+               try {
+                       var vr = 
VarResolver.create().vars(EnvFileVar.create(tmp)).build();
+                       assertEquals("us-east-1", vr.resolve("$EF{REGION}"));
+                       assertEquals("us-west-2", 
vr.resolve("$EF{MISSING,us-west-2}"));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d03_defaultVars_includeDotenvAndEnvFile() throws IOException {
+               var tmp = Files.createTempFile("juneau-svl-default-vars-", 
".env");
+               Files.writeString(tmp, "D_KEY=dot\nE_KEY=env\n");
+               var oldPath = System.getProperty("juneau.dotenv.path");
+               System.setProperty("juneau.dotenv.path", tmp.toString());
+               try {
+                       var vr = VarResolver.create().defaultVars().build();
+                       assertEquals("dot", vr.resolve("$DE{D_KEY}"));
+                       assertEquals("env", vr.resolve("$EF{E_KEY}"));
+               } finally {
+                       if (oldPath == null)
+                               System.clearProperty("juneau.dotenv.path");
+                       else
+                               System.setProperty("juneau.dotenv.path", 
oldPath);
+                       Files.deleteIfExists(tmp);
+               }
+       }
 }
diff --git a/todo/FINISHED-22-svl-external-config-vars.md 
b/todo/FINISHED-22-svl-external-config-vars.md
new file mode 100644
index 0000000000..f73fa52643
--- /dev/null
+++ b/todo/FINISHED-22-svl-external-config-vars.md
@@ -0,0 +1,22 @@
+# FINISHED-22: SVL external config vars
+
+Implemented additional SVL vars for `.env`-style external configuration 
sources in `juneau-commons`.
+
+## Shipped
+
+- Added `DotenvVar` (`$DE{key[,default]}`) backed by `DotenvPropertySource`.
+- Added `EnvFileVar` (`$EF{key[,default]}`) as a generic env-file alias, also 
backed by `DotenvPropertySource`.
+- Added both vars to `VarList.addDefault()`, so they are included in 
`VarResolver.DEFAULT` / `defaultVars()`.
+- Updated `VarResolver` and `VarList` Javadocs to include the new vars.
+- Added unit tests in `PropertyVars_Test` for:
+  - Direct var usage (`DotenvVar.create(path)` and `EnvFileVar.create(path)`).
+  - Default fallback behavior.
+  - Inclusion in `defaultVars()`.
+
+## Notes
+
+- `DotenvPropertySource` path resolution behavior is reused as-is:
+  - `juneau.dotenv.path` system property
+  - `JUNEAU_DOTENV_PATH` environment variable
+  - fallback `.env`
+- File-glob and JSON-pointer vars remain future enhancements if needed.
diff --git a/todo/TODO.md b/todo/TODO.md
index f00c1abc4c..750d8b1e7f 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -27,7 +27,5 @@
 
 - [TODO-20] Rethink how debugging works in RestServlet.  Can we come up with a 
simpler system?
 
-- [TODO-22] Add additional SVL vars for common external configuration sources 
(e.g. `EnvFileVar` / `DotenvVar` for `.env` files, possibly file-glob and 
JSON-pointer vars). Should land after TODO-14 places SVL in `juneau-commons`.
-
 - [TODO-30] Investigate moving `ClassMeta` and related non-marshalling type 
metadata from `juneau-marshall` into `juneau-commons` (analysis/feasibility 
pass). See `todo/TODO-30-classmeta-to-commons.md`.
 

Reply via email to