Parser.cs :  » Bloggers » dasBlog » RJH » CommandLineHelper » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Bloggers » dasBlog 
dasBlog » RJH » CommandLineHelper » Parser.cs
// Original code borrowed from Ray Hayes and downloaded from 
// http://www.codeproject.com/csharp/commandlineparser.asp.

// Revisions
// 1. Updates were made to support double quotes.
//
// Note, this should probably be moved to it's own GDN workspace and then the
// DLL be referenced in this workspace.  However, there is a similar workspace
// called Eas.Console that serves a similar purpose and should be investigated
// before another command line parser workspace is created.
using System;
using System.Text.RegularExpressions;

namespace RJH.CommandLineHelper{
  /// <summary>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.</summary>
  public class Parser
  {
    /// <summary>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.</summary>
    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

      /// <summary>
      /// 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.
      /// </summary>
      /// <param name="rec">The SwitchRecord for which this class store information.</param>
      /// <exception cref="ArgumentException">Thrown if the rec parameter is not of
      /// the type SwitchRecord.</exception>
      public SwitchInfo( object rec )
      {
        if ( rec is SwitchRecord )
          m_Switch = rec;
        else
          throw new ArgumentException();
      }
    }

    /// <summary>
    /// The SwitchRecord is stored within the parser's collection of registered
    /// switches.  This class is private to the outside world.
    /// </summary>
    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|^)(?<match>(-{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 = @")(?<value>(\+|-){0,1}))";
        else if ( Type == typeof(string) )
          strPatternEnd = @")(?::|\s+))((?:"")(?<value>.+?)(?:"")|(?:')(?<value>.+?)(?:')|(?<value>\S+))";
        else if ( Type == typeof(int) )
          strPatternEnd = @")(?::|\s+))((?<value>(-|\+)[0-9]+)|(?<value>[0-9]+))";
        else if ( Type.IsEnum )
        {
          string[] enumNames = Enumerations;
          string e_str = enumNames[0];
          for ( int e=1; e<enumNames.Length; e++ )
            e_str += "|" + enumNames[e];
          strPatternEnd = @")(?::|\s+))(?<value>" + 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(@"((?:"")(?<commandLine>.+?)(?:"")|(?:')(?<commandLine>.+?)(?:')|(?<commandLine>\S+)) (?<remainder>.+)",
        RegexOptions.ExplicitCapture);
      Match m = r.Match(m_commandLine);      
      if ( m != null && m.Groups["commandLine"] != null )
      {
        m_applicationName = m.Groups["commandLine"].Value;
        m_applicationName = m_applicationName.Trim();
        if(m_applicationName.Length > 0)
        {
          if(m_applicationName[0]== '\'' || m_applicationName[0]== '"')
          {
            m_applicationName = m_applicationName.Remove(0,1);
            m_applicationName = m_applicationName.Remove(m_applicationName.Length-1,1);
          }
        }
        else
        {
          m_applicationName = m_commandLine;
        }
        m_workingString = m.Groups["remainder"].Value;
      }
    }

    private static readonly Regex splitParametersRegex = new Regex(@"((\s*(""(?<param>.+?)""|'(?<param>.+?)'|(?<param>\S+))))",
      RegexOptions.ExplicitCapture | RegexOptions.Compiled);
        
    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).
      MatchCollection m = splitParametersRegex.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; i<m_switches.Count; i++ )
            si[i] = new SwitchInfo( m_switches[i] );
          return si;
        }
      }
    }

    public object this[string name] 
    {
      get
      {
        if ( m_switches != null )
          for ( int i=0; i<m_switches.Count; i++ )
            if ( string.Compare( (m_switches[i] as SwitchRecord).Name, name, true )==0 )
              return (m_switches[i] as SwitchRecord).Value;
        return null;
      }
    }


    private static readonly Regex switchPatternRegex = new Regex( @"(\s|^)(?<match>(-{1,2}|/)(.+?))(?=(\s|$))",
      RegexOptions.ExplicitCapture
      | RegexOptions.IgnoreCase | RegexOptions.Compiled);
        
    /// <summary>This function returns a list of the unhandled switches
    /// that the parser has seen, but not processed.</summary>
    /// <remark>The unhandled switches are not removed from the remainder
    /// of the command-line.</remark>
    public string[] UnhandledSwitches 
    {
      get
      {
        MatchCollection m = switchPatternRegex.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<names.Length; s++ )
        rec.AddAlias( names[s] );
      m_switches.Add( rec );
    }

    public bool Parse()
    {
      ExtractApplicationName();

      // Remove switches and associated info.
      HandleSwitches();

      // Split parameters.
      SplitParameters();

      return true;
    }

    public string GetHelpText()
    {
      System.Text.StringBuilder message = new System.Text.StringBuilder("");
      message.Append( this.ApplicationName + " ");
      foreach(Parser.SwitchInfo switchInfo in this.Switches)
      {
        message.Append( "[/" );
        if(switchInfo.Aliases != null)
        {
          message.Append("{");
        }
        message.Append( switchInfo.Name);
        if(switchInfo.Aliases != null)
        {
          foreach(string alias in switchInfo.Aliases)
          {
            message.Append( "|" + alias);
          }
          message.Append("}");
        }
        if(switchInfo.IsEnum)
        {
          message.Append( ":" + string.Join("|", Enum.GetNames(switchInfo.Type)));        
        }
        message.Append( "] ");
      }

      message.Append("\n\n");
      foreach(Parser.SwitchInfo switchInfo in this.Switches)
      {
        message.AppendFormat("\t/{0,-17}-{1}\n", switchInfo.Name, switchInfo.Description);
        if(switchInfo.Aliases != null)
        {
          message.AppendFormat("{0,-30}Aliases: ", "");
          message.Append(string.Join(", ", switchInfo.Aliases));
          message.Append("\n");
        }
      }
      return message.ToString();
    }
    public object InternalValue(string name)
    {
      if ( m_switches != null )
        for ( int i=0; i<m_switches.Count; i++ )
          if ( string.Compare( (m_switches[i] as SwitchRecord).Name, name, true )==0 )
            return (m_switches[i] as SwitchRecord).InternalValue;
      return null;
    }
    #endregion

    #region Constructors
    public Parser( string commandLine )
    {
      m_commandLine = commandLine;
    }

    public Parser( string commandLine,
              object classForAutoAttributes )
    {
      m_commandLine = commandLine;

      Type type = classForAutoAttributes.GetType();
      System.Reflection.MemberInfo[] members = type.GetMembers();

      for(int i=0; i<members.Length; i++)
      {
        object[] attributes = members[i].GetCustomAttributes(false);
        if(attributes.Length > 0)
        {
          SwitchRecord rec = null;

          foreach ( Attribute attribute in attributes )
          {
            if ( attribute is CommandLineSwitchAttribute )
            {
              CommandLineSwitchAttribute switchAttrib =
                (CommandLineSwitchAttribute) 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 CommandLineAliasAttribute )
              {
                CommandLineAliasAttribute aliasAttrib =
                  (CommandLineAliasAttribute) 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
  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.