File: HelpGenerator.cs
Web Access
Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
 
namespace Microsoft.Tools.ServiceModel.Svcutil
{
    internal static class HelpGenerator
    {
        private static StringBuilder s_helpBuilder;
 
        internal static string GenerateHelpText()
        {
            s_helpBuilder = new StringBuilder();
            HelpGenerator.WriteUsage();
            s_helpBuilder.AppendLine();
            s_helpBuilder.AppendLine();
            HelpGenerator.WriteCodeGenerationHelp();
            s_helpBuilder.AppendLine();
            s_helpBuilder.AppendLine();
            HelpGenerator.WriteExamples();
            s_helpBuilder.AppendLine();
            s_helpBuilder.AppendLine();
            return s_helpBuilder.ToString();
        }
 
        private static void WriteUsage()
        {
            s_helpBuilder.AppendLine(SR.HelpUsage1);
            s_helpBuilder.AppendLine();
            s_helpBuilder.AppendLine(SR.HelpUsage2);
        }
 
        private static void WriteCodeGenerationHelp()
        {
            HelpCategory helpCategory = new HelpCategory(SR.HelpUsageCategory)
            {
                Inputs = new ArgumentInfo[] {
                    ArgumentInfo.CreateInputHelpInfo(SR.HelpInputMetadataDocumentPath, SR.HelpCodeGenerationSyntaxInput1),
                    ArgumentInfo.CreateInputHelpInfo(SR.HelpInputUrl,                  SR.HelpCodeGenerationSyntaxInput2),
                    ArgumentInfo.CreateInputHelpInfo(SR.HelpInputEpr,                  SR.HelpCodeGenerationSyntaxInput3)
                },
 
                Options = new ArgumentInfo[] {
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.OutputDirectory.Name,   SR.ParametersDirectory,                           string.Format(SR.HelpDirectoryFormat, CommandProcessorOptions.Switches.OutputDirectory.Abbreviation)),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.NoLogo.Name,                                                              string.Format(SR.HelpNologoFormat, CommandProcessorOptions.Switches.NoLogo.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.Verbosity.Name,         SR.ParametersVerbosity,                           string.Format(SR.HelpVerbosityFormat, string.Join(", ", System.Enum.GetNames(typeof(Verbosity))), CommandProcessorOptions.Switches.Verbosity.Abbreviation)),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.Help.Name,                                                                string.Format(SR.HelpHelpFormat, CommandProcessorOptions.Switches.Help.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.ProjectFile.Name,       SR.ParametersProjectFile,                         string.Format(SR.HelpProjectFileFormat, CommandProcessorOptions.Switches.ProjectFile.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.OutputFile.Name,        SR.ParametersOut,                                 string.Format(SR.HelpOutFormat, CommandProcessorOptions.Switches.OutputFile.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.Namespace.Name,         SR.ParametersNamespace,                           string.Format(SR.HelpNamespaceFormat, CommandProcessorOptions.Switches.Namespace.Abbreviation), true),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.MessageContract.Name,                                                     string.Format(SR.HelpMessageContractFormat, CommandProcessorOptions.Switches.MessageContract.Abbreviation), true),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.EnableDataBinding.Name,                                                   string.Format(SR.HelpEnableDataBindingFormat, CommandProcessorOptions.Switches.EnableDataBinding.Abbreviation)),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.Internal.Name,                                                            string.Format(SR.HelpInternalFormat, CommandProcessorOptions.Switches.Internal.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.Reference.Name,         SR.ParametersReference,                           string.Format(SR.HelpReferenceCodeGenerationFormat, CommandProcessorOptions.Switches.Reference.Abbreviation), true),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.NoTypeReuse.Name,                                                         string.Format(SR.HelpNoTypeReuseFormat, CommandProcessorOptions.Switches.NoTypeReuse.Abbreviation, CommandProcessorOptions.Switches.Reference.Name, CommandProcessorOptions.Switches.CollectionType.Name)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.CollectionType.Name,    SR.ParametersCollectionType,                      string.Format(SR.HelpCollectionTypeFormat, CommandProcessorOptions.Switches.CollectionType.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.ExcludeType.Name,       SR.ParametersExcludeType,                         string.Format(SR.HelpExcludeTypeCodeGenerationFormat, CommandProcessorOptions.Switches.ExcludeType.Abbreviation)),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.NoStdlib.Name,                                                            string.Format(SR.HelpNostdlibFormat, CommandProcessorOptions.Switches.NoStdlib.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.Serializer.Name,        SerializerMode.Auto.ToString(),                   string.Format(SR.HelpAutoSerializerFormat, CommandProcessorOptions.Switches.Serializer.Abbreviation), true),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.Serializer.Name,        SerializerMode.DataContractSerializer.ToString(), SR.HelpDataContractSerializer),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.Serializer.Name,        SerializerMode.XmlSerializer.ToString(),          SR.HelpXmlSerializer),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.Sync.Name,                                                                string.Format(SR.HelpSyncFormat, CommandProcessorOptions.Switches.Sync.Abbreviation)),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.Wrapped.Name,                                                             string.Format(SR.HelpWrappedFormat, CommandProcessorOptions.Switches.Wrapped.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.Update.Name,            SR.ParametersWebServiceReferenceName,             string.Format(SR.HelpUpdateWebServiceReferenceFormat, CommandProcessorOptions.Switches.Update.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.RuntimeIdentifier.Name, SR.ParametersRuntimeIdentifier,                   string.Format(SR.HelpRuntimeIdentifierFormat, CommandProcessorOptions.Switches.RuntimeIdentifier.Abbreviation)),
                    ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.TargetFramework.Name,   SR.ParametersTargetFramework,                     string.Format(SR.HelpTargetFrameworkFormat, CommandProcessorOptions.Switches.TargetFramework.Abbreviation)),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.AcceptCertificate.Name,                                                   string.Format(SR.HelpAcceptCertificateFormat, CommandProcessorOptions.Switches.AcceptCertificate.Abbreviation)),
                    ArgumentInfo.CreateFlagHelpInfo(     CommandProcessorOptions.Switches.ServiceContract.Name,                                                     string.Format(SR.HelpServiceContractFormat, CommandProcessorOptions.Switches.ServiceContract.Abbreviation))
                }
            };
 
            helpCategory.WriteHelp();
        }
 
        private static void WriteExamples()
        {
            HelpCategory helpCategory = new HelpCategory(SR.HelpExamples);
            helpCategory.WriteHelp();
 
            WriteExample(SR.HelpExamples2, SR.HelpExamples3);
            WriteExample(SR.HelpExamples8, SR.HelpExamples9);
            WriteExample(SR.HelpExamples10, SR.HelpExamples11);
        }
 
        private static void WriteExample(string syntax, string explanation)
        {
            ParagraphHelper paragraphHelper = new ParagraphHelper();
            s_helpBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, " {0}", syntax));
            s_helpBuilder.AppendLine(paragraphHelper.AddIndentation(string.Format(CultureInfo.InvariantCulture, "    {0}", explanation), indentLength: 4));
            s_helpBuilder.AppendLine();
        }
 
        private class ArgumentInfo
        {
            private const string argHelpPrefix = " ";
            private const string argHelpSeperator = " - ";
 
            internal static ArgumentInfo CreateInputHelpInfo(string input, string helpText, bool beginGroup = false)
            {
                return new ArgumentInfo()
                {
                    _name = input,
                    _helpText = helpText,
                    _beginGroup = beginGroup
                };
            }
 
            internal static ArgumentInfo CreateFlagHelpInfo(string option, string helpText, bool beginGroup = false)
            {
                return new ArgumentInfo()
                {
                    _name = String.Format(CultureInfo.InvariantCulture, "{0}{1}", CommandSwitch.FullSwitchIndicator, option),
                    _helpText = helpText,
                    _beginGroup = beginGroup
                };
            }
 
            internal static ArgumentInfo CreateParameterHelpInfo(string option, string optionUse, string helpText, bool beginGroup = false)
            {
                return new ArgumentInfo()
                {
                    _name = String.Format(CultureInfo.InvariantCulture, "{0}{1} {2}", CommandSwitch.FullSwitchIndicator, option, optionUse),
                    _helpText = helpText,
                    _beginGroup = beginGroup
                };
            }
 
            private bool _beginGroup;
            private string _name;
            private string _helpText;
 
            public bool BeginGroup
            {
                get { return _beginGroup; }
                set { _beginGroup = value; }
            }
 
            public string Name
            {
                get { return _name; }
            }
            public string HelpText
            {
                set { _helpText = value; }
            }
 
            private string GenerateHelp(string pattern)
            {
                return string.Format(CultureInfo.InvariantCulture, pattern, _name, _helpText);
            }
 
            private static int CalculateMaxNameLength(ArgumentInfo[] arguments)
            {
                int maxNameLength = 0;
                foreach (ArgumentInfo argument in arguments)
                {
                    if (argument.Name.Length > maxNameLength)
                    {
                        maxNameLength = argument.Name.Length;
                    }
                }
                return maxNameLength;
            }
 
            public static void WriteArguments(ArgumentInfo[] arguments)
            {
                int maxArgumentnLength = CalculateMaxNameLength(arguments);
                int helpTextIndent = argHelpPrefix.Length + maxArgumentnLength + argHelpSeperator.Length;
                string helpPattern = argHelpPrefix + "{0, -" + maxArgumentnLength + "}" + argHelpSeperator + "{1}";
 
                ParagraphHelper paragraphHelper = new ParagraphHelper();
 
                foreach (ArgumentInfo argument in arguments)
                {
                    if (argument.BeginGroup)
                        s_helpBuilder.AppendLine();
 
                    string optionHelp = argument.GenerateHelp(helpPattern);
                    s_helpBuilder.AppendLine(paragraphHelper.AddIndentation(optionHelp, helpTextIndent));
                }
            }
        }
 
        private class HelpCategory
        {
            static HelpCategory()
            {
                try
                {
                    if (Console.WindowWidth > 75)
                        s_nameMidpoint = Console.WindowWidth / 3;
                    else
                        s_nameMidpoint = 25;
                }
                catch
                {
                    s_nameMidpoint = 25;
                }
            }
 
            private static int s_nameMidpoint;
 
            private int _nameStart;
            private string _name;
            private string _description = null;
            private string _syntax = null;
            private ArgumentInfo[] _options;
            private ArgumentInfo[] _inputs;
 
            public HelpCategory(string name)
            {
                Debug.Assert(!string.IsNullOrEmpty(name), "Help category name should have a valid value!");
                if (name == null)
                {
                    name = string.Empty;
                }
                _name = name;
                _nameStart = s_nameMidpoint - (name.Length / 2);
            }
 
            public ArgumentInfo[] Options
            {
                get { return _options; }
                set { _options = value; }
            }
 
            public ArgumentInfo[] Inputs
            {
                get { return _inputs; }
                set { _inputs = value; }
            }
 
            public void WriteHelp()
            {
                s_helpBuilder.AppendLine(new string(' ', _nameStart) + _name);
                s_helpBuilder.AppendLine();
 
                if (_inputs != null)
                {
                    ArgumentInfo.WriteArguments(_inputs);
                    s_helpBuilder.AppendLine();
                }
 
                if (_options != null)
                {
                    s_helpBuilder.AppendLine(SR.HelpOptions);
                    s_helpBuilder.AppendLine();
                    ArgumentInfo.WriteArguments(_options);
                    s_helpBuilder.AppendLine();
                }
            }
        }
    }
 
    // Helper class to insert whitespace into a string so multiple lines will line up correctly in the console window.
    internal class ParagraphHelper
    {
        private int _indentLength;
        private int _cursorLeft;
        private int _lineWidth;
        private StringBuilder _stringBuilder;
 
        public string AddIndentation(string text, int indentLength)
        {
            _indentLength = indentLength;
            this.Reset();
            this.AppendParagraph(text);
            return _stringBuilder.ToString();
        }
 
        private void Reset()
        {
            _stringBuilder = new StringBuilder();
            _cursorLeft = GetConsoleCursorLeft();
            _lineWidth = GetBufferWidth();
        }
 
        private void AppendParagraph(string text)
        {
            int index = 0;
            while (index < text.Length)
            {
                this.AppendWord(text, ref index);
                this.AppendWhitespace(text, ref index);
            }
        }
 
        private void AppendWord(string text, ref int index)
        {
            // If we're at the beginning of a new line we should indent.
            if ((_cursorLeft == 0) && (index != 0))
                AppendIndent();
 
            int wordLength = FindWordLength(text, index);
 
            // Now that we know how long the string is we can:
            //   1. print it on the current line if we have enough space
            //   2. print it on the next line if we don't have space 
            //      on the current line and it will fit on the next line
            //   3. print whatever will fit on the current line 
            //      and overflow to the next line.
            if (wordLength < this.HangingLineWidth)
            {
                if (wordLength > this.BufferWidth)
                {
                    this.AppendLineBreak();
                    this.AppendIndent();
                }
                _stringBuilder.Append(text, index, wordLength);
                _cursorLeft += wordLength;
            }
            else
            {
                AppendWithOverflow(text, ref index, ref wordLength);
            }
 
            index += wordLength;
        }
 
        private void AppendWithOverflow(string test, ref int start, ref int wordLength)
        {
            do
            {
                _stringBuilder.Append(test, start, this.BufferWidth);
                start += this.BufferWidth;
                wordLength -= this.BufferWidth;
                this.AppendLineBreak();
 
                if (wordLength > 0)
                    this.AppendIndent();
            } while (wordLength > this.BufferWidth);
 
            if (wordLength > 0)
            {
                _stringBuilder.Append(test, start, wordLength);
                _cursorLeft += wordLength;
            }
        }
 
        private void AppendWhitespace(string text, ref int index)
        {
            while ((index < text.Length) && char.IsWhiteSpace(text[index]))
            {
                if (BufferWidth == 0)
                {
                    this.AppendLineBreak();
                }
 
                // For each whitespace character:
                //   1. If we're at a newline character we insert 
                //      a new line and reset the cursor.
                //   2. If the whitespace character is at the beginning of a new 
                //      line, we insert an indent instead of the whitespace
                //   3. Insert the whitespace 
                if (AtNewLine(text, index))
                {
                    this.AppendLineBreak();
                    index += Environment.NewLine.Length;
                }
                else if (_cursorLeft == 0 && index != 0)
                {
                    AppendIndent();
                    index++;
                }
                else
                {
                    _stringBuilder.Append(text[index]);
                    index++;
                    _cursorLeft++;
                }
            }
        }
 
        private void AppendIndent()
        {
            _stringBuilder.Append(' ', _indentLength);
            _cursorLeft += _indentLength;
        }
 
        private void AppendLineBreak()
        {
            if (BufferWidth != 0)
                _stringBuilder.AppendLine();
            _cursorLeft = 0;
        }
 
        private int BufferWidth
        {
            get
            {
                return _lineWidth - _cursorLeft;
            }
        }
 
        private int HangingLineWidth
        {
            get
            {
                return _lineWidth - _indentLength;
            }
        }
 
        private static int FindWordLength(string text, int index)
        {
            for (int end = index; end < text.Length; end++)
            {
                if (char.IsWhiteSpace(text[end]))
                    return end - index;
            }
            return text.Length - index;
        }
 
        private static bool AtNewLine(string text, int index)
        {
            if ((index + Environment.NewLine.Length) > text.Length)
            {
                return false;
            }
 
            for (int i = 0; i < Environment.NewLine.Length; i++)
            {
                if (Environment.NewLine[i] != text[index + i])
                {
                    return false;
                }
            }
 
            return true;
        }
 
        private static int GetConsoleCursorLeft()
        {
            try
            {
                return Console.CursorLeft;
            }
            catch
            {
                return 0;
            }
        }
 
        private static int GetBufferWidth()
        {
            try
            {
                int bufferWidth = Console.BufferWidth;
 
                if (bufferWidth > 0)
                {
                    return Console.BufferWidth;
                }
                else
                {
                    return int.MaxValue;
                }
            }
            catch
            {
                return int.MaxValue;
            }
        }
    }
}