//****************************************************************************************************** // Arguments.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 09/12/2004 - J. Ritchie Carroll // Generated original version of source code. // 03/28/2006 - Pinal C. Patel // Migrated 2.0 version of source code from 1.1 source (GSF.Console). // 10/09/2007 - J. Ritchie Carroll / Pinal C. Patel // Fixed stand-alone argument issue at end of line and changed to use generic Dictionary class. // 09/15/2008 - J. Ritchie Carroll // Converted to C#. // 09/26/2008 - Pinal C. Patel // Entered code comments. // 08/05/2009 - Josh L. Patterson // Edited Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 04/2/2012 - J. Ritchie Carroll // Added new TryGetValue() method. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Security; using System.Text; using System.Text.RegularExpressions; using GSF.Collections; namespace GSF.Console { /// /// Represents an ordered set of arguments along with optional arguments parsed from a command-line. /// /// /// This example shows how to parse a command-line command that does not contain an executable name: /// /// using System; /// using GSF; /// using GSF.Console; /// /// class Program /// { /// static void Main() /// { /// Arguments args = new Arguments("Sample.txt -wrap=true"); /// string file = args["OrderedArg1"]; /// bool wrapText = args["wrap"].ParseBoolean(); /// /// Console.WriteLine(string.Format("File: {0}", file)); /// Console.WriteLine(string.Format("Wrap text: {0}", wrapText)); /// Console.ReadLine(); /// } /// } /// /// This example shows how to parse a command-line command that contains an executable name in it: /// /// using System; /// using GSF.Console; /// /// class Program /// { /// static void Main() /// { /// // Environment.CommandLine = @"""c:\program files\tva\theme application\app.exe"" Document1.dcx -theme=default" /// Arguments args = new Arguments(Environment.CommandLine, true); /// string doc = args["OrderedArg1"]; /// string theme = args["theme"]; /// /// Console.WriteLine(string.Format("Document: {0}", doc)); /// Console.WriteLine(string.Format("Application theme: {0}", theme)); /// Console.ReadLine(); /// } /// } /// /// [Serializable] public class Arguments : IEnumerable>, ISerializable { #region [ Members ] // Constants /// /// Default value for . /// public const string DefaultOrderedArgID = "OrderedArg"; // Fields private readonly string m_commandLine; private readonly string m_orderedArgID; private readonly int m_orderedArgCount; private readonly Dictionary m_arguments; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// /// The command-line command to be parsed. public Arguments(string commandLine) : this(commandLine, false) { } /// /// Initializes a new instance of the class. /// /// The command-line command to be parsed. /// true if the first argument in the command-line command is to be skipped from being processed; otherwise false. public Arguments(string commandLine, bool skipFirstArgument) : this(commandLine, "OrderedArg", skipFirstArgument) { } /// /// Initializes a new instance of the class. /// /// The command-line command to be parsed. /// The prefix to be used in the identifier of ordered arguments. public Arguments(string commandLine, string orderedArgID) : this(commandLine, orderedArgID, false) { } /// /// Initializes a new instance of the class. /// /// The command-line command to be parsed. /// The prefix to be used in the identifier of ordered arguments. /// true if the first argument in the command-line command is to be skipped from being processed; otherwise false. public Arguments(string commandLine, string orderedArgID, bool skipFirstArgument) { Regex spliter = new Regex("^-{1,2}|^/|=|:", RegexOptions.IgnoreCase | RegexOptions.Compiled); Regex remover = new Regex("^[\'\"]?(.*?)[\'\"]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); string parameter = null; string argument; string[] parts; // We create a case-insensitive dictionary for parameters m_arguments = new Dictionary(StringComparer.CurrentCultureIgnoreCase); m_orderedArgCount = 0; m_commandLine = commandLine; m_orderedArgID = orderedArgID; // Valid parameters forms: // {-,/,--}param{=,:}((",')value(",')) // Examples: // -param1=value1 --param2 /param3:"Test-:-work" // /param4=happy -param5 '--=nice=--' string[] args = ParseCommand(m_commandLine); if (skipFirstArgument) { if (args.Length > 1) args = args.Copy(1, args.Length - 1); else args = new string[0]; } foreach (string arg in args) { // Found just a parameter in last pass... // The last parameter is still waiting, with no value, set it to nothing. if ((object)parameter != null) { if (!m_arguments.ContainsKey(parameter)) m_arguments.Add(parameter, null); } parameter = null; if (!string.IsNullOrEmpty(arg)) { // If this argument begins with a quote, we treat it as a stand-alone argument if (arg[0] == '\"' || arg[0] == '\'') { // Handle stand alone ordered arguments m_orderedArgCount++; parameter = orderedArgID + m_orderedArgCount; // Remove possible enclosing characters (",') if (!m_arguments.ContainsKey(parameter)) { argument = remover.Replace(arg, "$1"); m_arguments.Add(parameter, argument); } parameter = null; } else { // Look for new parameters (-,/ or --) and a // possible enclosed value (=,:) parts = spliter.Split(arg, 3); switch (parts.Length) { case 1: // Handle stand alone ordered arguments m_orderedArgCount++; parameter = orderedArgID + m_orderedArgCount; // Remove possible enclosing characters (",') if (!m_arguments.ContainsKey(parameter)) { argument = remover.Replace(arg, "$1"); m_arguments.Add(parameter, argument); } parameter = null; break; case 2: // Found just a parameter parameter = parts[1]; break; case 3: // Parameter with enclosed value parameter = parts[1]; // Remove possible enclosing characters (",') if (!m_arguments.ContainsKey(parameter)) { parts[2] = remover.Replace(parts[2], "$1"); m_arguments.Add(parameter, parts[2]); } parameter = null; break; } } } } // In case a parameter is still waiting if ((object)parameter != null && !m_arguments.ContainsKey(parameter)) m_arguments.Add(parameter, null); } /// /// Creates a new from serialization parameters. /// /// The with populated with data. /// The source for this deserialization. protected Arguments(SerializationInfo info, StreamingContext context) { // Deserialize argument fields m_commandLine = info.GetOrDefault("commandLine", ""); m_orderedArgID = info.GetOrDefault("orderedArgID", DefaultOrderedArgID); m_orderedArgCount = info.GetOrDefault("orderedArgCount", 0); m_arguments = new Dictionary(StringComparer.CurrentCultureIgnoreCase); int argumentCount = info.GetOrDefault("argumentCount", 0); for (int i = 0; i < argumentCount; i++) { string key = info.GetOrDefault("argumentKey" + i, null as string); if (!string.IsNullOrEmpty(key)) m_arguments.Add(key, info.GetOrDefault("argumentValue" + i, null as string)); } } #endregion #region [ Properties ] /// /// Gets the value for the specified argument from the command-line command. /// /// The argument whose value is to retrieved. /// Value for the specified argument if found; otherwise null. public virtual string this[string argument] { get { string value; m_arguments.TryGetValue(argument, out value); return value; } } /// /// Gets the total number of arguments (ordered and optional) present in the command-line command. /// public virtual int Count { get { return m_arguments.Count; } } /// /// Gets the prefix text in the identifier of ordered arguments present in the command-line command. /// public virtual string OrderedArgID { get { return m_orderedArgID; } } /// /// Gets the total number of ordered arguments in the command-line command. /// public virtual int OrderedArgCount { get { return m_orderedArgCount; } } /// /// Gets the ordered arguments as an array of strings. /// public virtual string[] OrderedArgs { get { return Enumerable.Range(1, OrderedArgCount) .Select(arg => this[OrderedArgID + arg]) .ToArray(); } } /// /// Gets a boolean value that indicates whether the command-line command contains request for displaying help. /// public virtual bool ContainsHelpRequest { get { return (m_arguments.ContainsKey("?") || m_arguments.ContainsKey("Help")); } } /// /// Gets the dictionary containing all of the arguments present in the command-line command. /// protected Dictionary InternalDictionary { get { return m_arguments; } } #endregion #region [ Methods ] /// /// Gets the value associated with the specified argument in the command-line command. /// /// The argument whose value is to be retrieved. /// Value associated with the specified argument if it exists in the command-line command, otherwise null. /// true if argument existed and was returned; otherwise false. public virtual bool TryGetValue(string argument, out string value) { return m_arguments.TryGetValue(argument, out value); } /// /// Gets a boolean value that indicates whether the specified argument is present in the command-line command. /// /// The argument to be checked. /// true if the argument exists in the command-line command; otherwise false. public virtual bool Exists(string argument) { return m_arguments.ContainsKey(argument); } /// /// Gets a string representation of the object. /// /// A string representation of the object. public override string ToString() { return m_commandLine; } /// /// Returns an for iterating through all the command-line command arguments. /// /// An for the command-line command arguments. public IEnumerator> GetEnumerator() { return m_arguments.GetEnumerator(); } /// /// Returns an for iterating through all the command-line command arguments. /// /// An for the command-line command arguments. IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)m_arguments).GetEnumerator(); } /// /// Populates a with the data needed to serialize the target object. /// /// The to populate with data. /// The destination (see ) for this serialization. /// The caller does not have the required permission. public void GetObjectData(SerializationInfo info, StreamingContext context) { // Serialize argument fields info.AddValue("commandLine", m_commandLine); info.AddValue("orderedArgID", m_orderedArgID); info.AddValue("orderedArgCount", m_orderedArgCount); info.AddValue("argumentCount", m_arguments.Count); int i = 0; foreach (KeyValuePair argument in m_arguments) { info.AddValue("argumentKey" + i, argument.Key); info.AddValue("argumentValue" + i, argument.Value); i++; } } #endregion #region [ Static ] /// /// This function can be used to parse a single parameterized string and turn it into an array of parameters. /// /// String of parameters. /// Array of parameters. public static string[] ParseCommand(string command) { List parsedCommand = new List(); if (!string.IsNullOrEmpty(command)) { string encodedQuote = Guid.NewGuid().ToString(); string encodedSpace = Guid.NewGuid().ToString(); StringBuilder encodedCommand = new StringBuilder(); bool argumentInQuotes = false; char currentCharacter; string argument; // Encodes embedded quotes. It allows embedded/nested quotes encoded as \". command = command.Replace("\\\"", encodedQuote); // Combines any quoted strings into a single arg by encoding embedded spaces. for (int x = 0; x < command.Length; x++) { currentCharacter = command[x]; if (currentCharacter == '\"') { if (argumentInQuotes) argumentInQuotes = false; else argumentInQuotes = true; } if (argumentInQuotes) { if (currentCharacter == ' ') encodedCommand.Append(encodedSpace); else encodedCommand.Append(currentCharacter); } else { encodedCommand.Append(currentCharacter); } } command = encodedCommand.ToString(); // Parses every argument out by space and combine any quoted strings into a single arg. foreach (string arg in command.Split(' ')) { // Adds tokenized argument, making sure to unencode any embedded quotes or spaces. argument = arg.Replace(encodedQuote, "\"").Replace(encodedSpace, " ").Trim(); if (argument.Length > 0) parsedCommand.Add(argument); } } return parsedCommand.ToArray(); } /// /// Provides an array of arguments parsed from the given command /// using parsing rules similar to those used in POSIX environments. /// /// The command to be parsed. /// An array of arguments. public static string[] ToArgs(string command) { string[] tokenCharacterPatterns = { // Backslash followed by another character @"\\.", // Substring wrapped in double quotes @"""(?:(?:\\"")|[^""])*""", // Substring wrapped in single quotes @"'[^']*'", // Mismatched double quote @"""(?:(?:\\"")|[^""])*$", // Mismatched single quote @"'[^']*$", // A single character that is not a // single quote, double quote, or whitespace @"[^'""\s]" }; // Define the regular expression pattern to match tokens string pattern = $"(?<=^|\\s)({string.Join("|", tokenCharacterPatterns)})+(?=\\s|$)"; // This function converts an escape // sequence into the corresponding character Func toChar = escapeSequence => { if (escapeSequence == @"\n") return '\n'; if (escapeSequence == @"\r") return '\r'; if (escapeSequence == @"\t") return '\t'; return escapeSequence[1]; }; // This function converts a token character into // its corresponding output in the args array Func interpretCharacter = tokenChar => { string value = tokenChar.Value; switch (value[0]) { // Backslash followed by any character produces // only the character following the backslash case '\\': if (value.Length == 1) throw new FormatException("Malformed expression - dangling escape sequence."); return toChar(value).ToString(); // Expressions wrapped in double quotes must be stripped of the // surrounding double quotes, and backslashes inside double-quoted // expressions must be replaces by the character immediately following them case '"': if (!value.EndsWith("\"")) throw new FormatException("Malformed expression - mismatched quote."); return new string(value.Zip(value.Substring(1, value.Length - 2), (c1, c2) => { // Handle escape sequences // inside double quotes if (c1 == '\\') return toChar(string.Concat(c1, c2)); return c2; }).ToArray()); // Expressions wrapped in single quotes must // be stripped of the surrounding single quotes case '\'': if (!value.EndsWith("'")) throw new FormatException("Malformed expression - mismatched quote."); return value.Substring(1, value.Length - 2); // Any other character produces itself default: return value; } }; // This function converts a token to its corresponding output in the args array Func interpretToken = token => string.Concat(token.Groups[1].Captures.Cast().Select(interpretCharacter)); // Apply the regular expression pattern and // convert the result into the array of arguments return Regex.Matches(command, pattern) .Cast() .Select(interpretToken) .ToArray(); } /// /// Escapes a string so that it will be parsed as a /// single argument by the method. /// /// The argument to be escaped. /// The escaped argument. public static string Escape(string arg) { StringBuilder escapedArgBuilder = new StringBuilder(); // Empty string is encoded as // two empty double quotes if (string.IsNullOrEmpty(arg)) return new string('"', 2); foreach (char c in arg) { switch (c) { case '\\': case '"': case '\'': // Quotes and backslashes need // to be escaped by a backslash escapedArgBuilder.Append('\\').Append(c); break; case '\n': // Newline (\n) escapedArgBuilder.Append(@"\n"); break; case '\r': // Carriage return (\r) escapedArgBuilder.Append(@"\r"); break; case '\t': // Tab (\t) escapedArgBuilder.Append(@"\t"); break; default: // White space needs to be // escaped by a backslash if (char.IsWhiteSpace(c)) escapedArgBuilder.Append('\\'); // All other characters do // not need to be escaped escapedArgBuilder.Append(c); break; } } return escapedArgBuilder.ToString(); } #endregion } }