// (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
}
}