File: EnvironmentVariablesConfigurationSource.cs
Web Access
Project: src\runtime\src\libraries\Microsoft.Extensions.Configuration.EnvironmentVariables\src\Microsoft.Extensions.Configuration.EnvironmentVariables.csproj (Microsoft.Extensions.Configuration.EnvironmentVariables)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Text;

namespace Microsoft.Extensions.Configuration.EnvironmentVariables
{
    /// <summary>
    /// Represents environment variables as an <see cref="IConfigurationSource"/>.
    /// </summary>
    public class EnvironmentVariablesConfigurationSource : IConfigurationSource
    {
        /// <summary>
        /// The default transformation that replaces double underscores (<c>__</c>) with the
        /// configuration key delimiter (<c>:</c>).
        /// </summary>
        public static Func<string, string> DefaultTransformation { get; } =
            static name =>
            {
                ArgumentNullException.ThrowIfNull(name);
                return name.Replace("__", ConfigurationPath.KeyDelimiter);
            };

        /// <summary>
        /// A transformation that replaces triple underscores (<c>___</c>) with a dot (<c>.</c>)
        /// and double underscores (<c>__</c>) with the configuration key delimiter (<c>:</c>).
        /// </summary>
        /// <remarks>
        /// Runs of underscores are processed greedily from left to right: each <c>___</c> match
        /// is consumed before any remaining <c>__</c>. As a result, a run of four underscores
        /// (<c>____</c>) is interpreted as one triple followed by a single literal underscore
        /// and produces <c>._</c>, not <c>::</c>.
        /// </remarks>
        public static Func<string, string> ColonAndDotTransformation { get; } = TransformUnderscoresToColonAndDot;

        private static string TransformUnderscoresToColonAndDot(string name)
        {
            ArgumentNullException.ThrowIfNull(name);

            int first = name.IndexOf("__", StringComparison.Ordinal);

            if (first < 0)
            {
                return name;
            }

            return Core(name, first);

            // Local function to avoid the overhead of stackalloc when there are no double underscores in the string.
            static string Core(string name, int index)
            {
                var builder = new ValueStringBuilder(stackalloc char[256]);

                builder.Append(name.AsSpan(0, index));

                while (index < name.Length)
                {
                    if (name[index] == '_' && index + 1 < name.Length && name[index + 1] == '_')
                    {
                        if (index + 2 < name.Length && name[index + 2] == '_')
                        {
                            builder.Append('.');
                            index += 3;
                        }
                        else
                        {
                            builder.Append(':');
                            index += 2;
                        }
                    }
                    else
                    {
                        builder.Append(name[index++]);
                    }
                }

                return builder.ToString();
            }
        }

        /// <summary>
        /// A prefix used to filter environment variables.
        /// </summary>
        /// <remarks>
        /// The prefix is itself passed through <see cref="VariableNameTransformation"/> before
        /// matching, and the matching (and stripping) is performed against the transformed
        /// environment variable name.
        /// </remarks>
        public string? Prefix { get; set; }

        /// <summary>
        /// Gets or sets a function that transforms environment variable names. When set, this
        /// transformation replaces the default behavior of converting double
        /// underscores to the configuration key delimiter. When <see langword="null"/>,
        /// <see cref="DefaultTransformation"/> is used.
        /// </summary>
        /// <seealso cref="ColonAndDotTransformation"/>
        public Func<string, string>? VariableNameTransformation { get; set; }

        /// <summary>
        /// Builds the <see cref="EnvironmentVariablesConfigurationProvider"/> for this source.
        /// </summary>
        /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
        /// <returns>A <see cref="EnvironmentVariablesConfigurationProvider"/>.</returns>
        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new EnvironmentVariablesConfigurationProvider(Prefix, VariableNameTransformation);
        }
    }
}