using System; using System.Text.RegularExpressions; //Code taken from : http://www.codeproject.com/KB/recipes/commandlineparser.aspx namespace TechBot.Library { /// Implementation of a command-line parsing class. Is capable of /// having switches registered with it directly or can examine a registered /// class for any properties with the appropriate attributes appended to /// them. public class ParametersParser { /// A simple internal class for passing back to the caller /// some information about the switch. The internals/implementation /// of this class has privillaged access to the contents of the /// SwitchRecord class. public class SwitchInfo { #region Private Variables private object m_Switch = null; #endregion #region Public Properties public string Name { get { return (m_Switch as SwitchRecord).Name; } } public string Description { get { return (m_Switch as SwitchRecord).Description; } } public string[] Aliases { get { return (m_Switch as SwitchRecord).Aliases; } } public System.Type Type { get { return (m_Switch as SwitchRecord).Type; } } public object Value { get { return (m_Switch as SwitchRecord).Value; } } public object InternalValue { get { return (m_Switch as SwitchRecord).InternalValue; } } public bool IsEnum { get { return (m_Switch as SwitchRecord).Type.IsEnum; } } public string[] Enumerations { get { return (m_Switch as SwitchRecord).Enumerations; } } #endregion /// /// Constructor for the SwitchInfo class. Note, in order to hide to the outside world /// information not necessary to know, the constructor takes a System.Object (aka /// object) as it's registering type. If the type isn't of the correct type, an exception /// is thrown. /// /// The SwitchRecord for which this class store information. /// Thrown if the rec parameter is not of /// the type SwitchRecord. public SwitchInfo( object rec ) { if ( rec is SwitchRecord ) m_Switch = rec; else throw new ArgumentException(); } } /// /// The SwitchRecord is stored within the parser's collection of registered /// switches. This class is private to the outside world. /// private class SwitchRecord { #region Private Variables private string m_name = ""; private string m_description = ""; private object m_value = null; private System.Type m_switchType = typeof(bool); private System.Collections.ArrayList m_Aliases = null; private string m_Pattern = ""; // The following advanced functions allow for callbacks to be // made to manipulate the associated data type. private System.Reflection.MethodInfo m_SetMethod = null; private System.Reflection.MethodInfo m_GetMethod = null; private object m_PropertyOwner = null; #endregion #region Private Utility Functions private void Initialize( string name, string description ) { m_name = name; m_description = description; BuildPattern(); } private void BuildPattern() { string matchString = Name; if ( Aliases != null && Aliases.Length > 0 ) foreach( string s in Aliases ) matchString += "|" + s; string strPatternStart = @"(\s|^)(?(-{1,2}|/)("; string strPatternEnd; // To be defined below. // The common suffix ensures that the switches are followed by // a white-space OR the end of the string. This will stop // switches such as /help matching /helpme // string strCommonSuffix = @"(?=(\s|$))"; if ( Type == typeof(bool) ) strPatternEnd = @")(?(\+|-){0,1}))"; else if ( Type == typeof(string) ) strPatternEnd = @")(?::|\s+))((?:"")(?.+)(?:"")|(?\S+))"; else if ( Type == typeof(int) ) strPatternEnd = @")(?::|\s+))((?(-|\+)[0-9]+)|(?[0-9]+))"; else if ( Type.IsEnum ) { string[] enumNames = Enumerations; string e_str = enumNames[0]; for ( int e=1; e" + e_str + @")"; } else throw new System.ArgumentException(); // Set the internal regular expression pattern. m_Pattern = strPatternStart + matchString + strPatternEnd + strCommonSuffix; } #endregion #region Public Properties public object Value { get { if ( ReadValue != null ) return ReadValue; else return m_value; } } public object InternalValue { get { return m_value; } } public string Name { get { return m_name; } set { m_name = value; } } public string Description { get { return m_description; } set { m_description = value; } } public System.Type Type { get { return m_switchType; } } public string[] Aliases { get { return (m_Aliases != null) ? (string[])m_Aliases.ToArray(typeof(string)): null; } } public string Pattern { get { return m_Pattern; } } public System.Reflection.MethodInfo SetMethod { set { m_SetMethod = value; } } public System.Reflection.MethodInfo GetMethod { set { m_GetMethod = value; } } public object PropertyOwner { set { m_PropertyOwner = value; } } public object ReadValue { get { object o = null; if ( m_PropertyOwner != null && m_GetMethod != null ) o = m_GetMethod.Invoke( m_PropertyOwner, null ); return o; } } public string[] Enumerations { get { if ( m_switchType.IsEnum ) return System.Enum.GetNames( m_switchType ); else return null; } } #endregion #region Constructors public SwitchRecord( string name, string description ) { Initialize( name, description ); } public SwitchRecord( string name, string description, System.Type type ) { if ( type == typeof(bool) || type == typeof(string) || type == typeof(int) || type.IsEnum ) { m_switchType = type; Initialize( name, description ); } else throw new ArgumentException("Currently only Ints, Bool and Strings are supported"); } #endregion #region Public Methods public void AddAlias( string alias ) { if ( m_Aliases == null ) m_Aliases = new System.Collections.ArrayList(); m_Aliases.Add( alias ); BuildPattern(); } public void Notify( object value ) { if ( m_PropertyOwner != null && m_SetMethod != null ) { object[] parameters = new object[1]; parameters[0] = value; m_SetMethod.Invoke( m_PropertyOwner, parameters ); } m_value = value; } #endregion } #region Private Variables private string m_commandLine = ""; private string m_workingString = ""; private string m_applicationName = ""; private string[] m_splitParameters = null; private System.Collections.ArrayList m_switches = null; #endregion #region Private Utility Functions private void ExtractApplicationName() { Regex r = new Regex(@"^(?("".+""|(\S)+))(?.+)", RegexOptions.ExplicitCapture); Match m = r.Match(m_commandLine); if ( m != null && m.Groups["commandLine"] != null ) { m_applicationName = m.Groups["commandLine"].Value; m_workingString = m.Groups["remainder"].Value; } } private void SplitParameters() { // Populate the split parameters array with the remaining parameters. // Note that if quotes are used, the quotes are removed. // e.g. one two three "four five six" // 0 - one // 1 - two // 2 - three // 3 - four five six // (e.g. 3 is not in quotes). Regex r = new Regex(@"((\s*(""(?.+?)""|(?\S+))))", RegexOptions.ExplicitCapture); MatchCollection m = r.Matches( m_workingString ); if ( m != null ) { m_splitParameters = new string[ m.Count ]; for ( int i=0; i < m.Count; i++ ) m_splitParameters[i] = m[i].Groups["param"].Value; } } private void HandleSwitches() { if ( m_switches != null ) { foreach ( SwitchRecord s in m_switches ) { Regex r = new Regex( s.Pattern, RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase ); MatchCollection m = r.Matches( m_workingString ); if ( m != null ) { for ( int i=0; i < m.Count; i++ ) { string value = null; if ( m[i].Groups != null && m[i].Groups["value"] != null ) value = m[i].Groups["value"].Value; if ( s.Type == typeof(bool)) { bool state = true; // The value string may indicate what value we want. if ( m[i].Groups != null && m[i].Groups["value"] != null ) { switch ( value ) { case "+": state = true; break; case "-": state = false; break; case "": if ( s.ReadValue != null ) state = !(bool)s.ReadValue; break; default: break; } } s.Notify( state ); break; } else if ( s.Type == typeof(string) ) s.Notify( value ); else if ( s.Type == typeof(int) ) s.Notify( int.Parse( value ) ); else if ( s.Type.IsEnum ) s.Notify( System.Enum.Parse(s.Type,value,true) ); } } m_workingString = r.Replace(m_workingString, " "); } } } #endregion #region Public Properties public string ApplicationName { get { return m_applicationName; } } public string[] Parameters { get { return m_splitParameters; } } public SwitchInfo[] Switches { get { if ( m_switches == null ) return null; else { SwitchInfo[] si = new SwitchInfo[ m_switches.Count ]; for ( int i=0; iThis function returns a list of the unhandled switches /// that the parser has seen, but not processed. /// The unhandled switches are not removed from the remainder /// of the command-line. public string[] UnhandledSwitches { get { string switchPattern = @"(\s|^)(?(-{1,2}|/)(.+?))(?=(\s|$))"; Regex r = new Regex( switchPattern, RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase ); MatchCollection m = r.Matches( m_workingString ); if ( m != null ) { string[] unhandled = new string[ m.Count ]; for ( int i=0; i < m.Count; i++ ) unhandled[i] = m[i].Groups["match"].Value; return unhandled; } else return null; } } #endregion #region Public Methods public void AddSwitch( string name, string description ) { if ( m_switches == null ) m_switches = new System.Collections.ArrayList(); SwitchRecord rec = new SwitchRecord( name, description ); m_switches.Add( rec ); } public void AddSwitch( string[] names, string description ) { if ( m_switches == null ) m_switches = new System.Collections.ArrayList(); SwitchRecord rec = new SwitchRecord( names[0], description ); for ( int s=1; s 0) { SwitchRecord rec = null; foreach ( Attribute attribute in attributes ) { if ( attribute is CommandParameterAttribute ) { CommandParameterAttribute switchAttrib = (CommandParameterAttribute) attribute; // Get the property information. We're only handling // properties at the moment! if ( members[i] is System.Reflection.PropertyInfo ) { System.Reflection.PropertyInfo pi = (System.Reflection.PropertyInfo) members[i]; rec = new SwitchRecord( switchAttrib.Name, switchAttrib.Description, pi.PropertyType ); // Map in the Get/Set methods. rec.SetMethod = pi.GetSetMethod(); rec.GetMethod = pi.GetGetMethod(); rec.PropertyOwner = classForAutoAttributes; // Can only handle a single switch for each property // (otherwise the parsing of aliases gets silly...) break; } } } // See if any aliases are required. We can only do this after // a switch has been registered and the framework doesn't make // any guarantees about the order of attributes, so we have to // walk the collection a second time. if ( rec != null ) { foreach ( Attribute attribute in attributes ) { if (attribute is CommandParameterAliasAttribute) { CommandParameterAliasAttribute aliasAttrib = (CommandParameterAliasAttribute)attribute; rec.AddAlias( aliasAttrib.Alias ); } } } // Assuming we have a switch record (that may or may not have // aliases), add it to the collection of switches. if ( rec != null ) { if ( m_switches == null ) m_switches = new System.Collections.ArrayList(); m_switches.Add( rec ); } } } } #endregion } }