UsageBuilder.cs :  » Development » Command-Line-Option-Parsing » CommandLine » OptParse » 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 » Development » Command Line Option Parsing 
Command Line Option Parsing » CommandLine » OptParse » UsageBuilder.cs
using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Xsl;

namespace CommandLine.OptParse{
  #region ListType enum
  /// <summary>
  /// Type of list to create in the usage
  /// </summary>
  public enum ListType
  {
    /// <summary>
    /// Numbered/ordered list
    /// </summary>
    Ordered,

    /// <summary>
    /// Unordered list
    /// </summary>
    Unordered
  }
  #endregion ListType enum

  #region UsageBuilder class
  /// <summary>
  /// Class to assist the building of program usage information
  /// </summary>
  /// <remarks>
  /// The API of this class is similar to that of an 
  /// <see cref="System.Xml.XmlTextWriter"/>. The output of the usage
  /// is an <see cref="XmlDocument"/>. The using the 
  /// <see cref="ToHtml"/>, <see cref="ToText"/> and <see cref="Transform"/> methods
  /// the XML can be transformed into the desired output format for the usage.
  /// </remarks>
  /// <include file='ExternalDocs.xml' 
  ///    path='externalDocs/doc[@name="ExampleUsageCode"]/*'/>
  public class UsageBuilder
  {
    #region Members
    private XmlDocument _usage;
    private XmlNode     _currentNode;
    private bool        _groupOptionsByCategory;
    private string      _defaultOptionCategory = "Uncategorized";
    #endregion Members

    #region Constructor
    /// <summary>
    /// Default constructor
    /// </summary>
    public UsageBuilder()
    {
      _usage = new XmlDocument();
      _currentNode = _usage.AppendChild(_usage.CreateElement("usage"));
    }
    #endregion Constructor

    #region Properties
    /// <summary>
    /// Get the <see cref="XmlDocument"/> containing the usage content
    /// </summary>
    public XmlDocument Usage
    {
      get { return _usage; }
    }


    /// <summary>
    /// Get or set the category to use for options when an option is missing a category
    /// <seealso cref="GroupOptionsByCategory"/>
    /// </summary>
    /// <remarks>Cannot be set to null or an empty string, if so, the default value will
    /// be used</remarks>
    [DefaultValue("Uncategorized")]
    public string DefaultOptionCategory 
    {
      get { return _defaultOptionCategory; }
      set 
      {
        if (value == null || value.Length == 0)
          value = "Uncategorized";

        _defaultOptionCategory = value; 
      }
    }


    /// <summary>
    /// Get or set to group options by their categories
    /// <seealso cref="DefaultOptionCategory"/>
    /// </summary>
    public bool GroupOptionsByCategory 
    {
      get { return _groupOptionsByCategory; }
      set { _groupOptionsByCategory = value; }
    }
    #endregion Properties

    #region Printing methods
    /// <summary>
    /// Convenience method for transforming the XML using a custom XSLT
    /// </summary>
    /// <param name="writer">TextWriter to write the output to</param>
    /// <param name="xsltSource">Xslt content to use to transform with</param>
    /// <param name="arguments">Xslt arguments to pass to the <see cref="XslTransform"/></param>
    public void Transform(TextWriter writer, XmlReader xsltSource, XsltArgumentList arguments)
    {
      XslTransform trans = new XslTransform();
      trans.Load(xsltSource, null, Assembly.GetExecutingAssembly().Evidence);
      trans.Transform(this.Usage, arguments, writer, null);
    }


    /// <summary>
    /// Convert the usage to HTML
    /// </summary>
    /// <param name="writer">TextWriter to write the output to</param>
    /// <param name="optStyle">The style to use when printing possible option names</param>
    /// <param name="cssStyleSheet">Stylesheet URI to apply to the HTML content</param>
    /// <param name="includeDefaultValues">True to include the default option values in the output</param>
    /// <param name="title">Title to use for the HTML page</param>
    public void ToHtml(TextWriter writer, OptStyle optStyle, Uri cssStyleSheet, bool includeDefaultValues, string title)
    {
      XsltArgumentList argList = new XsltArgumentList();
      XslTransform trans = new XslTransform();

      using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
             "CommandLine.OptParse.Xslt.Html.xslt"))
      {
        trans.Load(new XmlTextReader(stream), null, Assembly.GetExecutingAssembly().Evidence);
      }

      argList.AddParam("shortOptPrefix", string.Empty, (optStyle == OptStyle.Unix) ? "-" : "/");
      argList.AddParam("longOptPrefix", string.Empty, (optStyle == OptStyle.Unix) ? "--" : "/");
      argList.AddParam("includeDefaultValues", string.Empty, includeDefaultValues);
        
      if (cssStyleSheet != null)
        argList.AddParam("cssStyleSheet", string.Empty, cssStyleSheet.AbsoluteUri);
        
      argList.AddParam("title", string.Empty, title);

      trans.Transform(this.Usage, argList, writer, null);
    }


    /// <summary>
    /// Convert the usage to Text
    /// </summary>
    /// <param name="writer">TextWriter to write the output to</param>
    /// <param name="optStyle">The style to use when printing possible option names</param>
    /// <param name="includeDefaultValues">True to include the default option values in the output</param>
    public void ToText(TextWriter writer, OptStyle optStyle, bool includeDefaultValues)
    {
      ToText(writer, optStyle, includeDefaultValues, -1);
    }


    /// <summary>
    /// Convert the usage to Text
    /// </summary>
    /// <remarks>if <paramref name="maxColumns"/> is -1, the width of the console will be used
    /// on windows machines, and the text will not be wrapped on non-windows machines</remarks>
    /// <param name="writer">TextWriter to write the output to</param>
    /// <param name="optStyle">The style to use when printing possible option names</param>
    /// <param name="includeDefaultValues">True to include the default option values in the output</param>
    /// <param name="maxColumns">Wrap text at the given column (attempts to wrap at '-' or ' ')</param>
    public void ToText(TextWriter writer, OptStyle optStyle, bool includeDefaultValues, int maxColumns)
    {
      XsltArgumentList argList = new XsltArgumentList();
      XslTransform     trans = new XslTransform();

      using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
             "CommandLine.OptParse.Xslt.Text.xslt"))
      {
        trans.Load(new XmlTextReader(stream), null, Assembly.GetExecutingAssembly().Evidence);
      }

      argList.AddParam("shortOptPrefix", string.Empty, (optStyle == OptStyle.Unix) ? "-" : "/");
      argList.AddParam("longOptPrefix", string.Empty, (optStyle == OptStyle.Unix) ? "--" : "/");
      argList.AddParam("includeDefaultValues", string.Empty, includeDefaultValues);
      argList.AddParam("newline", string.Empty, Environment.NewLine);
      argList.AddExtensionObject("extObj", new TextTransformHelper(maxColumns));

      if (maxColumns <= 0)
        maxColumns = -1;

      argList.AddParam("maxColumns", string.Empty, maxColumns);

      trans.Transform(this.Usage, argList, writer, null);
    }
    #endregion Printing methods

    #region Usage building functions
    /// <summary>
    /// Begin a new section tag
    /// </summary>
    /// <remarks>
    /// May only be under a "usage" or "section" tag. <see cref="EndSection"/> must be called
    /// to close this tag
    /// </remarks>
    /// <param name="header">Name of the section</param>
    public void BeginSection(string header)
    {
      ValidateCurrentNode("section", "usage");
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("section"));
      _currentNode.Attributes.Append(_usage.CreateAttribute("name")).Value = header;
    }


    /// <summary>
    /// Close the section
    /// </summary>
    /// <remarks>Section tag must be active</remarks>
    public void EndSection()
    {
      ValidateCurrentNode("section");
      _currentNode = _currentNode.ParentNode;
    }


    /// <summary>
    /// Add a paragraph to the usage
    /// </summary>
    /// <remarks>
    /// May only be inside of "section" or "description" tags
    /// </remarks>
    /// <param name="body">The body of the paragraph</param>
    public void AddParagraph(string body)
    {
      ValidateCurrentNode("section", "description");
      _currentNode.AppendChild(_usage.CreateElement("para")).InnerText = body;
    }


    /// <summary>
    /// Add a paragraph to the usage
    /// </summary>
    /// <remarks>
    /// May only be inside of "section" or "description" tags. <see cref="EndParagraph"/> must be called
    /// to close this tag. Use <see cref="AddParagraphContent"/> to add text to the paragraph body.
    /// </remarks>
    public void BeginParagraph()
    {
      ValidateCurrentNode("section", "description");
      _currentNode.AppendChild(_usage.CreateElement("para"));
    }


    /// <summary>
    /// Add content to the current paragraph node
    /// </summary>
    /// <remarks>May only be called when a "para" tag is active.</remarks>
    /// <param name="text">Text to add</param>
    public void AddParagraphContent(string text)
    {
      ValidateCurrentNode("para");
      _currentNode.InnerText += text;
    }


    /// <summary>
    /// Close the active "para" tag
    /// </summary>
    /// <remarks>May only be called when a "para" tag is active.</remarks>
    public void EndParagraph()
    {
      ValidateCurrentNode("para");
      _currentNode = _currentNode.ParentNode;
    }


    /// <summary>
    /// Open a list tag
    /// </summary>
    /// <remarks>Sets the active tag to a new "list" tag. May only be called when
    /// one of the following tags are active: "section", "paragraph", "list", "description".
    /// <see cref="EndList"/> must be called to close this tag</remarks>
    /// <param name="type">The type of list</param>
    public void BeginList(ListType type)
    {
      ValidateCurrentNode("section", "paragraph", "list", "description");
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("list"));
      _currentNode.Attributes.Append(_usage.CreateAttribute("type")).Value = type.ToString().ToLower();
    }

    
    /// <summary>
    /// Add a list item tag to the current list
    /// </summary>
    /// <remarks>May only be called when a "list" tag is active.</remarks>
    /// <param name="body">Body of the list item to add</param>
    public void AddListItem(string body)
    {
      ValidateCurrentNode("list");
      _currentNode.AppendChild(_usage.CreateElement("item")).InnerText = body;
    }


    /// <summary>
    /// Close the active "list" tag
    /// </summary>
    /// <remarks>May only be called when a "list" tag is active.</remarks>
    public void EndList()
    {
      ValidateCurrentNode("list");
      _currentNode = _currentNode.ParentNode;
    }

    
    /// <summary>
    /// Begin an active "options" tag
    /// <seealso cref="GroupOptionsByCategory"/>
    /// <seealso cref="DefaultOptionCategory"/>
    /// </summary>
    /// <remarks>May only be called when a "header" tag is active.</remarks>
    public void BeginOptions()
    {
      ValidateCurrentNode("section");
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("options"));
    }


    /// <summary>
    /// Close the active "options" tag
    /// </summary>
    /// <remarks>May only be called when a "options" tag is active.</remarks>
    public void EndOptions()
    {
      ValidateCurrentNode("options");
      _currentNode = _currentNode.ParentNode;
    }


    /// <summary>
    /// Method to start adding an option to the usage.
    /// <seealso cref="AddOption"/>
    /// </summary>
    /// <remarks>Leaves the "description" tag as the active tag. Allows paragraph
    /// and list content to be added to the description of an option beyond
    /// the normal description. Does not include the description body from
    /// the <paramref name="opt"/> (must be added manually). Call 
    /// <see cref="EndOption"/> to close this tag.</remarks>
    /// <param name="opt">Option to start</param>
    public void BeginOption(OptionDefinition opt)
    {
      ValidateCurrentNode("options", "category");

      if (_groupOptionsByCategory)
      {
        XmlNode node;
        string  cat = opt.Category;
        
        if (cat == null)
          cat = _defaultOptionCategory;

        node = _currentNode.SelectSingleNode("category[@name='" + cat + "']");
        if (node == null)
        {
          _currentNode = _currentNode.AppendChild(_usage.CreateElement("category"));
          _currentNode.Attributes.Append(_usage.CreateAttribute("name")).Value = cat;
        }
        else
          _currentNode = node;
      }
      
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("option"));
      _currentNode.Attributes.Append(_usage.CreateAttribute("type")).Value = 
        EnumDescriptorReader.GetEnumFieldDescription(opt.Type);
      
      // names
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("names"));
      foreach (char name in opt.ShortNames)
      {
        _currentNode = _currentNode.AppendChild(_usage.CreateElement("name"));
        _currentNode.Attributes.Append(_usage.CreateAttribute("value")).Value = name.ToString();
        _currentNode.Attributes.Append(_usage.CreateAttribute("type")).Value = "short";
        _currentNode = _currentNode.ParentNode;
      }
      foreach (string name in opt.LongNames)
      {
        _currentNode = _currentNode.AppendChild(_usage.CreateElement("name"));
        _currentNode.Attributes.Append(_usage.CreateAttribute("value")).Value = name;
        _currentNode.Attributes.Append(_usage.CreateAttribute("type")).Value = "long";
        _currentNode = _currentNode.ParentNode;
      }

      _currentNode = _currentNode.ParentNode;

      if (opt.ValueType != null)
      {
        if (typeof(ValueType).IsAssignableFrom(opt.ValueType) || opt.ValueType.Namespace == "System")
          _currentNode.AppendChild(_usage.CreateElement("valueType")).InnerText = opt.ValueType.Name;
        else
          _currentNode.AppendChild(_usage.CreateElement("valueType")).InnerText = opt.ValueType.ToString();
      }

      if (opt.DefaultValue != null)
        _currentNode.AppendChild(_usage.CreateElement("defaultValue")).InnerText = 
          opt.DefaultValue.ToString();

      _currentNode = _currentNode.AppendChild(_usage.CreateElement("description"));
    }


    /// <summary>
    /// Close the active "description" tag and "option" tags
    /// </summary>
    /// <remarks>May only be called when a "description" tag is active below an
    /// "option" tag.</remarks>
    public void EndOption()
    {
      ValidateCurrentNode("description");
      _currentNode = _currentNode.ParentNode;
      _currentNode = _currentNode.ParentNode;

      if (_currentNode.Name == "category")
        _currentNode = _currentNode.ParentNode;
    }


    /// <summary>
    /// Add multiple options to the output
    /// </summary>
    /// <remarks>Can only be called if the active tag is "section"</remarks>
    /// <param name="optionContainer">Container with all the options 
    /// for the program to document</param>
    public void AddOptions(IOptionContainer optionContainer)
    {
      BeginOptions();
      foreach (OptionDefinition opt in optionContainer.GetOptions())
        AddOption(opt);
      EndOptions();
    }


    /// <summary>
    /// Add multiple options to the output
    /// </summary>
    /// <remarks>Can only be called if the active tag is "section"</remarks>
    /// <param name="options">All the options for the program</param>
    public void AddOptions(OptionDefinition[] options)
    {
      BeginOptions();
      foreach (OptionDefinition opt in options)
        AddOption(opt);
      EndOptions();
    }


    /// <summary>
    /// Adds an option to the output
    /// </summary>
    /// <remarks>Like <see cref="BeginOption"/>, but uses the desription from the
    /// option as the body of the "description" tag. Does not open a new active tag</remarks>
    /// <param name="opt">Option to add</param>
    public void AddOption(OptionDefinition opt)
    {
      BeginOption(opt);
      _currentNode.InnerText = opt.Description;
      EndOption();
    }

    
    /// <summary>
    /// Begin an active "arguments" tag
    /// </summary>
    /// <remarks>May only be called when a "header" tag is active.</remarks>
    public void BeginArguments()
    {
      ValidateCurrentNode("section");
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("arguments"));
    }


    /// <summary>
    /// Close the active "arguments" tag
    /// </summary>
    /// <remarks>May only be called when an "arguments" tag is active.</remarks>
    public void EndArguments()
    {
      ValidateCurrentNode("arguments");
      _currentNode = _currentNode.ParentNode;
    }


    
    /// <summary>
    /// Add an argument overview to the usage
    /// </summary>
    /// <remarks>Can only be called when a "section" tag is active. Does not leave a new
    /// active tag open.</remarks>
    /// <param name="name">Short name description of the argument</param>
    /// <param name="description">Description of the argument</param>
    /// <param name="type">Supported data type for the argument (<c>typeof(string)</c> is
    /// usually the best choice).</param>
    /// <param name="optional">True if the argument should be marked as optional</param>
    public void AddArgument(string name, string description, Type type, bool optional)
    {
      BeginArgument(name, type, optional);
      _currentNode.InnerText = description;
      EndArgument();
    }


    /// <summary>
    /// Add an argument overview to the usage
    /// </summary>
    /// <remarks>Can only be called when an "arguments" tag is active. Leaves the "description" tag
    /// active under the "argument" tag.</remarks>
    /// <param name="name">Short name description of the argument</param>
    /// <param name="type">Supported data type for the argument (<c>typeof(string)</c> is
    /// usually the best choice).</param>
    /// <param name="optional">True if the argument should be marked as optional</param>
    public void BeginArgument(string name, Type type, bool optional)
    {
      ValidateCurrentNode("arguments");
      
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("argument"));

      if (typeof(ValueType).IsAssignableFrom(type) || type.Namespace == "System")
        _currentNode.Attributes.Append(_usage.CreateAttribute("type")).Value = type.Name;
      else
        _currentNode.Attributes.Append(_usage.CreateAttribute("type")).Value = type.ToString();
      
      _currentNode.Attributes.Append(_usage.CreateAttribute("name")).Value = name;
      _currentNode.Attributes.Append(_usage.CreateAttribute("optional")).Value = optional.ToString();
      _currentNode = _currentNode.AppendChild(_usage.CreateElement("description"));
    }


    /// <summary>
    /// Close the active "description" tag and "argument" tags
    /// </summary>
    /// <remarks>May only be called when a "description" tag is active below an
    /// "argument" tag.</remarks>
    public void EndArgument()
    {
      EndOption();
    }
    #endregion Usage building functions

    #region Validation methods
    void ValidateCurrentNode(params string[] expectedTypes)
    {
      foreach (string type in expectedTypes)
        if (_currentNode.Name == type)
          return;

      throw new InvalidOperationException("Invaild command, must be inside of one of: " + 
        string.Join(", ", expectedTypes));
    }
    #endregion Validation methods

    #region TextTransformHelper class
    /// <summary>
    /// Class to help with Text transformation
    /// </summary>
    private class TextTransformHelper
    {
      #region Members
      private readonly char[] _splitChars = new char[] { '-', ' ' };

      private int _maxColumns;
      #endregion Members

      #region Constructor
      /// <summary>
      /// Constructor
      /// </summary>
      /// <param name="maxColumns">Column to wrap at</param>
      public TextTransformHelper(int maxColumns)
      {
        _maxColumns = maxColumns;

        if (_maxColumns == -1)
        {
          // try to determine console width
          string os = Environment.GetEnvironmentVariable("OS");

          if (os != null && os.StartsWith("Win"))
          {
            ConsoleUtils.ConsoleHelper ch = new ConsoleUtils.ConsoleHelper();
            _maxColumns = ch.GetScreenInfo().Size.X;
          }
        }
      }
      #endregion Constructor

      #region Functions
      /// <summary>
      /// Create a string of spaces
      /// </summary>
      /// <param name="length">Length of the string to create</param>
      /// <returns>The string</returns>
      public string CreateSpaces(int length)
      {
        return new string(' ', length);
      }


      /// <summary>
      /// Formats text, adding indent and wrapping lines
      /// </summary>
      /// <param name="text">Text to format</param>
      /// <param name="indent">Indent to add to each line</param>
      /// <param name="hangingIndent">Additional indent to add to lines after the first</param>
      /// <returns>Formatted text</returns>
      public string FormatText(string text, int indent, int hangingIndent)
      {
        StringBuilder output = new StringBuilder();
        bool          first = true;
        int           firstColumns = _maxColumns - indent - 1;
        int           subseqColumns = _maxColumns - indent - hangingIndent - 1;
        string        handingIndentStr = new string(' ', indent + hangingIndent);
        string        indentStr = new string(' ', indent);
        string[]      lines;
        
        text = text.Replace("\r\n", "\n");
        text = text.Replace("\r", "\n");

        lines = text.Split('\n');
        foreach (string line in lines)
        {
          if (!first)
            output.Append(Environment.NewLine);

          if (_maxColumns == -1)
          {
            output.Append((first) ? indentStr : handingIndentStr);
            output.Append(line);
            output.Append(Environment.NewLine);
            first = false;
            continue;
          }

          for (int start = 0; start < line.Length;)
          {
            int    cols = (first) ? firstColumns : subseqColumns;
            int    len;
            string istr = (first) ? indentStr : handingIndentStr;

            len = Math.Min(line.Length - start, cols);

            if (len < cols)
            {
              output.Append(istr);
              output.Append(line.Substring(start));
              first = false;
              break;
            }
            else
            {
              int splitLoc = line.LastIndexOfAny(_splitChars, start + len - 1, len);

              if (splitLoc > 0)
              {
                output.Append(istr);
                output.Append(line.Substring(start, splitLoc - start));
                output.Append(Environment.NewLine);

                start = splitLoc;
                if (line[start] == ' ')
                  start++;
              }
              else
              {
                output.Append(istr);
                output.Append(line.Substring(start, cols));
                output.Append(Environment.NewLine);
                start += cols;
              }
            }
            first = false;
          }
        }

        output.Append(Environment.NewLine);

        return output.ToString();
      }
      #endregion Functions
    }
    #endregion TextTransformHelper class
  }
  #endregion UsageBuilder class
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.