Skip to content

Instantly share code, notes, and snippets.

@rudfoss
Last active August 12, 2024 11:50
Show Gist options
  • Save rudfoss/d2b547c96d33a0678c891cd08ff06c44 to your computer and use it in GitHub Desktop.
Save rudfoss/d2b547c96d33a0678c891cd08ff06c44 to your computer and use it in GitHub Desktop.
A very simple parser for loading .env files in a c# application. Has no external dependencies and supports basic references.
namespace DotEnv
{
public class DotEnv(ILogger<DotEnv> logger)
{
/// <summary>
/// Holds a set of all environment variables set from .env files. Listed if logLevel set do debug.
/// </summary>
private readonly IDictionary<string, string> _finalEnvironmentVariables = new Dictionary<string, string>();
private void LoadAndParse()
{
string currentDirectory = Directory.GetCurrentDirectory();
logger.LogDebug("Working directory: {CurrentDirectory}", currentDirectory);
var allEnvFiles = FindEnvFilesInDirectoryTree(currentDirectory);
foreach (var envFile in allEnvFiles)
{
logger.LogDebug("Parsing env file: {EnvFile}", envFile);
ParseEnvFile(envFile);
}
var allEnvVariables = _finalEnvironmentVariables.Select((keyValuePair) => $"{keyValuePair.Key}={keyValuePair.Value}");
logger.LogDebug("Environment variables loaded:\n{EnvironmentVariables}", string.Join('\n', allEnvVariables));
}
/// <summary>
/// Locates all .env* files in the specified directory and the set number of ancestors up to the root directory of the volume. Orders them so that the most specific files are loaded last.
/// </summary>
/// <param name="startingDirectory">Where to begin looking for env files</param>
/// <param name="nrOfAncestorsToCheck">The number of ancestor directories to check (default = 20) up to the root directory. Set to 0 to only check current startingDirectory</param>
/// <returns>A list of absolute file paths to all env files found orderer from least to most specific.</returns>
private static List<string> FindEnvFilesInDirectoryTree(string startingDirectory, int nrOfAncestorsToCheck = 20) {
string? currentDirectory = startingDirectory;
var allFiles = new List<string>();
while (nrOfAncestorsToCheck-- >= 0 && currentDirectory is not null)
{
var currentDirectoryFiles = Directory.GetFiles(currentDirectory, ".env*", SearchOption.TopDirectoryOnly);
allFiles.AddRange(currentDirectoryFiles);
currentDirectory = Directory.GetParent(currentDirectory)?.FullName;
}
allFiles.Sort();
return allFiles;
}
private void ParseEnvFile(string filePath)
{
foreach (var line in File.ReadAllLines(filePath))
{
var parts = line.Trim().Split("=", StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2) continue;
var name = parts[0];
var value = parts[1];
if (value.StartsWith('$'))
{
var referenceEnvironmentVariableName = value[1..];
// This value is a reference to another environment variable that must be resolved.
logger.LogDebug("Resolving reference {Value} in {Name}", referenceEnvironmentVariableName, name);
value = Environment.GetEnvironmentVariable(referenceEnvironmentVariableName);
}
logger.LogDebug("Setting environment variable {Name} to {Value}", name, value);
_finalEnvironmentVariables[name] = value ?? "";
Environment.SetEnvironmentVariable(name, value);
}
}
/// <summary>
/// Set up and load environment variables from any .env file.
/// </summary>
public static void LoadDotEnv(LogLevel logLevel = LogLevel.Information)
{
var logger = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(logLevel);
}).CreateLogger<DotEnv>();
var env = new DotEnv(logger);
env.LoadAndParse();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment