File: CommandLineConfigurationProvider.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.Configuration.CommandLine\src\Microsoft.Extensions.Configuration.CommandLine.csproj (Microsoft.Extensions.Configuration.CommandLine)
// 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.Collections.Generic;
 
namespace Microsoft.Extensions.Configuration.CommandLine
{
    /// <summary>
    /// Provides configuration key-value pairs that are obtained from the command line.
    /// </summary>
    public class CommandLineConfigurationProvider : ConfigurationProvider
    {
        private readonly Dictionary<string, string>? _switchMappings;
 
        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="args">The command line args.</param>
        /// <param name="switchMappings">The switch mappings.</param>
        public CommandLineConfigurationProvider(IEnumerable<string> args, IDictionary<string, string>? switchMappings = null)
        {
            ThrowHelper.ThrowIfNull(args);
 
            Args = args;
 
            if (switchMappings != null)
            {
                _switchMappings = GetValidatedSwitchMappingsCopy(switchMappings);
            }
        }
 
        /// <summary>
        /// Gets the command-line arguments.
        /// </summary>
        protected IEnumerable<string> Args { get; }
 
        /// <summary>
        /// Loads the configuration data from the command-line arguments.
        /// </summary>
        public override void Load()
        {
            var data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
            string key, value;
 
            using (IEnumerator<string> enumerator = Args.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    string currentArg = enumerator.Current;
                    int keyStartIndex = 0;
 
                    if (currentArg.StartsWith("--"))
                    {
                        keyStartIndex = 2;
                    }
                    else if (currentArg.StartsWith("-"))
                    {
                        keyStartIndex = 1;
                    }
                    else if (currentArg.StartsWith("/"))
                    {
                        // "/SomeSwitch" is equivalent to "--SomeSwitch" when interpreting switch mappings
                        // So we do a conversion to simplify later processing
                        currentArg = $"--{currentArg.Substring(1)}";
                        keyStartIndex = 2;
                    }
 
                    int separator = currentArg.IndexOf('=');
 
                    if (separator < 0)
                    {
                        // If there is neither equal sign nor prefix in current argument, it is an invalid format
                        if (keyStartIndex == 0)
                        {
                            // Ignore invalid formats
                            continue;
                        }
 
                        // If the switch is a key in given switch mappings, interpret it
                        if (_switchMappings != null && _switchMappings.TryGetValue(currentArg, out string? mappedKey))
                        {
                            key = mappedKey;
                        }
                        // If the switch starts with a single "-" and it isn't in given mappings , it is an invalid usage so ignore it
                        else if (keyStartIndex == 1)
                        {
                            continue;
                        }
                        // Otherwise, use the switch name directly as a key
                        else
                        {
                            key = currentArg.Substring(keyStartIndex);
                        }
 
                        if (!enumerator.MoveNext())
                        {
                            // ignore missing values
                            continue;
                        }
 
                        value = enumerator.Current;
                    }
                    else
                    {
                        string keySegment = currentArg.Substring(0, separator);
 
                        // If the switch is a key in given switch mappings, interpret it
                        if (_switchMappings != null && _switchMappings.TryGetValue(keySegment, out string? mappedKeySegment))
                        {
                            key = mappedKeySegment;
                        }
                        // If the switch starts with a single "-" and it isn't in given mappings , it is an invalid usage
                        else if (keyStartIndex == 1)
                        {
                            throw new FormatException(SR.Format(SR.Error_ShortSwitchNotDefined, currentArg));
                        }
                        // Otherwise, use the switch name directly as a key
                        else
                        {
                            key = currentArg.Substring(keyStartIndex, separator - keyStartIndex);
                        }
 
                        value = currentArg.Substring(separator + 1);
                    }
 
                    // Override value when key is duplicated. So we always have the last argument win.
                    data[key] = value;
                }
            }
 
            Data = data;
        }
 
        private static Dictionary<string, string> GetValidatedSwitchMappingsCopy(IDictionary<string, string> switchMappings)
        {
            // The dictionary passed in might be constructed with a case-sensitive comparer
            // However, the keys in configuration providers are all case-insensitive
            // So we check whether the given switch mappings contain duplicated keys with case-insensitive comparer
            var switchMappingsCopy = new Dictionary<string, string>(switchMappings.Count, StringComparer.OrdinalIgnoreCase);
            foreach (KeyValuePair<string, string> mapping in switchMappings)
            {
                // Only keys start with "--" or "-" are acceptable
                if (!mapping.Key.StartsWith("-") && !mapping.Key.StartsWith("--"))
                {
                    throw new ArgumentException(
                        SR.Format(SR.Error_InvalidSwitchMapping, mapping.Key),
                        nameof(switchMappings));
                }
 
                if (switchMappingsCopy.ContainsKey(mapping.Key))
                {
                    throw new ArgumentException(
                        SR.Format(SR.Error_DuplicatedKeyInSwitchMappings, mapping.Key),
                        nameof(switchMappings));
                }
 
                switchMappingsCopy.Add(mapping.Key, mapping.Value);
            }
 
            return switchMappingsCopy;
        }
    }
}