// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.Serialization; namespace Microsoft.Test.CommandLineParsing { /// /// Represents a dictionary that is aware of command line input patterns. All lookups for keys ignore case. /// /// /// /// The example below demonstrates parsing a command line such as "Test.exe /verbose /runId=10" /// /// CommandLineDictionary d = CommandLineDictionary.FromArguments(args); /// /// bool verbose = d.ContainsKey("verbose"); /// int runId = Int32.Parse(d["runId"]); /// /// /// /// /// You can also explicitly provide key and value identifiers for the cases /// that use other characters (rather than '/' and '=') as key/value identifiers. /// The example below demonstrates parsing a command line such as "Test.exe -verbose -runId:10" /// /// CommandLineDictionary d = CommandLineDictionary.FromArguments(args, '-', ':'); /// /// bool verbose = d.ContainsKey("verbose"); /// int runId = Int32.Parse(d["runId"]); /// /// [Serializable] public class CommandLineDictionary : Dictionary { #region Constructors /// /// Create an empty CommandLineDictionary using the default key/value /// separators of '/' and '='. /// public CommandLineDictionary() : base(StringComparer.OrdinalIgnoreCase) { KeyCharacter = '/'; ValueCharacter = '='; } /// /// Creates a dictionary using a serialization info and context. This /// is used for Xml deserialization and isn't normally called from user code. /// /// Data needed to deserialize the dictionary. /// Describes source and destination of the stream. protected CommandLineDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } #endregion #region Public Members /// /// Initializes a new instance of the CommandLineDictionary class, populating a /// dictionary with key/value pairs from a command line that supports syntax /// where options are provided in the form "/key=value". /// /// Key/value pairs. /// public static CommandLineDictionary FromArguments(IEnumerable arguments) { return FromArguments(arguments, '/', '='); } /// /// Creates a dictionary that is populated with key/value pairs from a command line /// that supports syntax where options are provided in the form "/key=value". /// This method supports the ability to specify delimiter characters for options in /// the command line. /// /// Key/value pairs. /// A character that precedes a key. /// A character that separates a key from a value. /// public static CommandLineDictionary FromArguments(IEnumerable arguments, char keyCharacter, char valueCharacter) { CommandLineDictionary cld = new CommandLineDictionary(); cld.KeyCharacter = keyCharacter; cld.ValueCharacter = valueCharacter; foreach (string argument in arguments) { cld.AddArgument(argument); } return cld; } #endregion #region Override Members /// /// Converts dictionary contents to a command line string of key/value pairs. /// /// Command line string. public override string ToString() { string commandline = String.Empty; foreach (KeyValuePair pair in this) { if (!string.IsNullOrEmpty(pair.Value)) { commandline += String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3} ", KeyCharacter, pair.Key, ValueCharacter, pair.Value); } else // There is no value, so we just serialize the key { commandline += String.Format(CultureInfo.InvariantCulture, "{0}{1} ", KeyCharacter, pair.Key); } } return commandline.TrimEnd(); } #endregion #region Protected Members /// /// Populates a SerializationInfo with data needed to serialize the dictionary. /// This is used by Xml serialization and isn't normally called from user code. /// /// SerializationInfo object to populate. /// StreamingContext to populate data from. [SuppressMessage("Microsoft.Security", "CA2123")] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); } #endregion #region Private Members /// /// Character to treat as the key character in the value line. /// If the arguments should be of the form /Foo=Bar, then the /// key character is /. (Which is the default) /// private char KeyCharacter { get; set; } /// /// Character to treat as the value character in the value line. /// If the arguments should be of the form /Foo=Bar, then the /// value character is =. (Which is the default) /// private char ValueCharacter { get; set; } /// /// Adds the specified argument to the dictionary /// /// Key/Value pair argument. private void AddArgument(string argument) { if (argument == null) { throw new ArgumentNullException("argument"); } string key; string value; if (argument.StartsWith(KeyCharacter.ToString(), StringComparison.OrdinalIgnoreCase)) { string[] splitArg = argument.Substring(1).Split(ValueCharacter); //Key is extracted from first element key = splitArg[0]; //Reconstruct the value. We could also do this using substrings. if (splitArg.Length > 1) { value = string.Join("=", splitArg, 1, splitArg.Length - 1); } else { value = string.Empty; } } else { throw new ArgumentException("Unsupported value line argument format.", argument); } Add(key, value); } #endregion } }