#region License // // Copyright 2015-2013 Giacomo Stelluti Scala // // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #endregion #region Using Directives using System; using System.Collections.Generic; using System.Text; using CommandLine.Extensions; using CommandLine.Infrastructure; #endregion namespace CommandLine.Text { /// /// Provides means to format an help screen. /// You can assign it in place of a instance. /// public class HelpText { private const int BuilderCapacity = 128; private const int DefaultMaximumLength = 80; // default console width private const string DefaultRequiredWord = "Required."; private readonly StringBuilder _preOptionsHelp; private readonly StringBuilder _postOptionsHelp; private readonly BaseSentenceBuilder _sentenceBuilder; private int? _maximumDisplayWidth; private string _heading; private string _copyright; private bool _additionalNewLineAfterOption; private StringBuilder _optionsHelp; private bool _addDashesToOption; /// /// Initializes a new instance of the class. /// public HelpText() { _preOptionsHelp = new StringBuilder(BuilderCapacity); _postOptionsHelp = new StringBuilder(BuilderCapacity); _sentenceBuilder = BaseSentenceBuilder.CreateBuiltIn(); } /// /// Initializes a new instance of the class /// specifying the sentence builder. /// /// /// A instance. /// public HelpText(BaseSentenceBuilder sentenceBuilder) : this() { Assumes.NotNull(sentenceBuilder, "sentenceBuilder"); _sentenceBuilder = sentenceBuilder; } /// /// Initializes a new instance of the class /// specifying heading string. /// /// An heading string or an instance of . /// Thrown when parameter is null or empty string. public HelpText(string heading) : this() { Assumes.NotNullOrEmpty(heading, "heading"); _heading = heading; } /// /// Initializes a new instance of the class /// specifying the sentence builder and heading string. /// /// A instance. /// A string with heading or an instance of . public HelpText(BaseSentenceBuilder sentenceBuilder, string heading) : this(heading) { Assumes.NotNull(sentenceBuilder, "sentenceBuilder"); _sentenceBuilder = sentenceBuilder; } /// /// Initializes a new instance of the class /// specifying heading and copyright strings. /// /// A string with heading or an instance of . /// A string with copyright or an instance of . /// Thrown when one or more parameters are null or empty strings. public HelpText(string heading, string copyright) : this() { Assumes.NotNullOrEmpty(heading, "heading"); Assumes.NotNullOrEmpty(copyright, "copyright"); _heading = heading; _copyright = copyright; } /// /// Initializes a new instance of the class /// specifying heading and copyright strings. /// /// A instance. /// A string with heading or an instance of . /// A string with copyright or an instance of . /// Thrown when one or more parameters are null or empty strings. public HelpText(BaseSentenceBuilder sentenceBuilder, string heading, string copyright) : this(heading, copyright) { Assumes.NotNull(sentenceBuilder, "sentenceBuilder"); _sentenceBuilder = sentenceBuilder; } /// /// Initializes a new instance of the class /// specifying heading and copyright strings. /// /// A string with heading or an instance of . /// A string with copyright or an instance of . /// The instance that collected command line arguments parsed with class. /// Thrown when one or more parameters are null or empty strings. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "When DoAddOptions is called with fireEvent=false virtual member is not called")] public HelpText(string heading, string copyright, object options) : this() { Assumes.NotNullOrEmpty(heading, "heading"); Assumes.NotNullOrEmpty(copyright, "copyright"); Assumes.NotNull(options, "options"); _heading = heading; _copyright = copyright; DoAddOptions(options, DefaultRequiredWord, MaximumDisplayWidth, fireEvent: false); } /// /// Initializes a new instance of the class /// specifying heading and copyright strings. /// /// A instance. /// A string with heading or an instance of . /// A string with copyright or an instance of . /// The instance that collected command line arguments parsed with class. /// Thrown when one or more parameters are null or empty strings. public HelpText(BaseSentenceBuilder sentenceBuilder, string heading, string copyright, object options) : this(heading, copyright, options) { Assumes.NotNull(sentenceBuilder, "sentenceBuilder"); _sentenceBuilder = sentenceBuilder; } /// /// Occurs when an option help text is formatted. /// public event EventHandler FormatOptionHelpText; /// /// Gets or sets the heading string. /// You can directly assign a instance. /// public string Heading { get { return _heading; } set { Assumes.NotNullOrEmpty(value, "value"); _heading = value; } } /// /// Gets or sets the copyright string. /// You can directly assign a instance. /// public string Copyright { get { return _heading; } set { Assumes.NotNullOrEmpty(value, "value"); _copyright = value; } } /// /// Gets or sets the maximum width of the display. This determines word wrap when displaying the text. /// /// The maximum width of the display. public int MaximumDisplayWidth { get { return _maximumDisplayWidth.HasValue ? _maximumDisplayWidth.Value : DefaultMaximumLength; } set { _maximumDisplayWidth = value; } } /// /// Gets or sets a value indicating whether the format of options should contain dashes. /// It modifies behavior of method. /// public bool AddDashesToOption { get { return this._addDashesToOption; } set { this._addDashesToOption = value; } } /// /// Gets or sets a value indicating whether to add an additional line after the description of the option. /// public bool AdditionalNewLineAfterOption { get { return _additionalNewLineAfterOption; } set { _additionalNewLineAfterOption = value; } } /// /// Gets the instance specified in constructor. /// public BaseSentenceBuilder SentenceBuilder { get { return _sentenceBuilder; } } /// /// Creates a new instance of the class using common defaults. /// /// /// An instance of class. /// /// The instance that collected command line arguments parsed with class. public static HelpText AutoBuild(object options) { return AutoBuild(options, (Action)null); } /// /// Creates a new instance of the class using common defaults. /// /// /// An instance of class. /// /// The instance that collected command line arguments parsed with class. /// A delegate used to customize the text block for reporting parsing errors. /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. public static HelpText AutoBuild(object options, Action onError, bool verbsIndex = false) { var auto = new HelpText { Heading = HeadingInfo.Default, Copyright = CopyrightInfo.Default, AdditionalNewLineAfterOption = true, AddDashesToOption = !verbsIndex }; if (onError != null) { var list = ReflectionHelper.RetrievePropertyList(options); if (list != null) { onError(auto); } } var license = ReflectionHelper.GetAttribute(); if (license != null) { license.AddToHelpText(auto, true); } var usage = ReflectionHelper.GetAttribute(); if (usage != null) { usage.AddToHelpText(auto, true); } auto.AddOptions(options); return auto; } /// /// Creates a new instance of the class using common defaults, /// for verb commands scenario. /// /// /// An instance of class. /// /// The instance that collected command line arguments parsed with class. /// The verb command invoked. public static HelpText AutoBuild(object options, string verb) { bool found; var instance = Parser.InternalGetVerbOptionsInstanceByName(verb, options, out found); var verbsIndex = verb == null || !found; var target = verbsIndex ? options : instance; return HelpText.AutoBuild(target, current => HelpText.DefaultParsingErrorsHandler(target, current), verbsIndex); } /// /// Supplies a default parsing error handler implementation. /// /// The instance that collects parsed arguments parsed and associates /// to a property of type . /// The instance. public static void DefaultParsingErrorsHandler(object options, HelpText current) { var list = ReflectionHelper.RetrievePropertyList(options); if (list.Count == 0) { return; } var parserState = (IParserState)list[0].Left.GetValue(options, null); if (parserState == null || parserState.Errors.Count == 0) { return; } var errors = current.RenderParsingErrorsText(options, 2); // indent with two spaces if (!string.IsNullOrEmpty(errors)) { current.AddPreOptionsLine(string.Concat(Environment.NewLine, current.SentenceBuilder.ErrorsHeadingText)); var lines = errors.Split(new[] { Environment.NewLine }, StringSplitOptions.None); foreach (var line in lines) { current.AddPreOptionsLine(line); } } } /// /// Converts the help instance to a . /// /// This instance. /// The that contains the help screen. public static implicit operator string(HelpText info) { return info.ToString(); } /// /// Adds a text line after copyright and before options usage strings. /// /// A instance. /// Thrown when parameter is null or empty string. public void AddPreOptionsLine(string value) { AddPreOptionsLine(value, MaximumDisplayWidth); } /// /// Adds a text line at the bottom, after options usage string. /// /// A instance. /// Thrown when parameter is null or empty string. public void AddPostOptionsLine(string value) { AddLine(_postOptionsHelp, value); } /// /// Adds a text block with options usage string. /// /// The instance that collected command line arguments parsed with class. /// Thrown when parameter is null. public void AddOptions(object options) { AddOptions(options, DefaultRequiredWord); } /// /// Adds a text block with options usage string. /// /// The instance that collected command line arguments parsed with the class. /// The word to use when the option is required. /// Thrown when parameter is null. /// Thrown when parameter is null or empty string. public void AddOptions(object options, string requiredWord) { Assumes.NotNull(options, "options"); Assumes.NotNullOrEmpty(requiredWord, "requiredWord"); AddOptions(options, requiredWord, MaximumDisplayWidth); } /// /// Adds a text block with options usage string. /// /// The instance that collected command line arguments parsed with the class. /// The word to use when the option is required. /// The maximum length of the help documentation. /// Thrown when parameter is null. /// Thrown when parameter is null or empty string. public void AddOptions(object options, string requiredWord, int maximumLength) { Assumes.NotNull(options, "options"); Assumes.NotNullOrEmpty(requiredWord, "requiredWord"); DoAddOptions(options, requiredWord, maximumLength); } /// /// Builds a string that contains a parsing error message. /// /// An options target instance that collects parsed arguments parsed with the /// associated to a property of type . /// Number of spaces used to indent text. /// The that contains the parsing error message. public string RenderParsingErrorsText(object options, int indent) { var list = ReflectionHelper.RetrievePropertyList(options); if (list.Count == 0) { return string.Empty; // Or exception? } var parserState = (IParserState)list[0].Left.GetValue(options, null); if (parserState == null || parserState.Errors.Count == 0) { return string.Empty; } var text = new StringBuilder(); foreach (var e in parserState.Errors) { var line = new StringBuilder(); line.Append(indent.Spaces()); if (e.BadOption.ShortName != null) { line.Append('-'); line.Append(e.BadOption.ShortName); if (!string.IsNullOrEmpty(e.BadOption.LongName)) { line.Append('/'); } } if (!string.IsNullOrEmpty(e.BadOption.LongName)) { line.Append("--"); line.Append(e.BadOption.LongName); } line.Append(" "); line.Append(e.ViolatesRequired ? _sentenceBuilder.RequiredOptionMissingText : _sentenceBuilder.OptionWord); if (e.ViolatesFormat) { line.Append(" "); line.Append(_sentenceBuilder.ViolatesFormatText); } if (e.ViolatesMutualExclusiveness) { if (e.ViolatesFormat || e.ViolatesRequired) { line.Append(" "); line.Append(_sentenceBuilder.AndWord); } line.Append(" "); line.Append(_sentenceBuilder.ViolatesMutualExclusivenessText); } line.Append('.'); text.AppendLine(line.ToString()); } return text.ToString(); } /// /// Returns the help screen as a . /// /// The that contains the help screen. public override string ToString() { const int ExtraLength = 10; var builder = new StringBuilder(GetLength(_heading) + GetLength(_copyright) + GetLength(_preOptionsHelp) + GetLength(this._optionsHelp) + ExtraLength); builder.Append(_heading); if (!string.IsNullOrEmpty(_copyright)) { builder.Append(Environment.NewLine); builder.Append(_copyright); } if (_preOptionsHelp.Length > 0) { builder.Append(Environment.NewLine); builder.Append(_preOptionsHelp); } if (this._optionsHelp != null && this._optionsHelp.Length > 0) { builder.Append(Environment.NewLine); builder.Append(Environment.NewLine); builder.Append(this._optionsHelp); } if (_postOptionsHelp.Length > 0) { builder.Append(Environment.NewLine); builder.Append(_postOptionsHelp); } return builder.ToString(); } /// /// The OnFormatOptionHelpText method also allows derived classes to handle the event without attaching a delegate. /// This is the preferred technique for handling the event in a derived class. /// /// Data for the event. protected virtual void OnFormatOptionHelpText(FormatOptionHelpTextEventArgs e) { EventHandler handler = FormatOptionHelpText; if (handler != null) { handler(this, e); } } private static int GetLength(string value) { if (value == null) { return 0; } return value.Length; } private static int GetLength(StringBuilder value) { if (value == null) { return 0; } return value.Length; } private static void AddLine(StringBuilder builder, string value, int maximumLength) { Assumes.NotNull(value, "value"); if (builder.Length > 0) { builder.Append(Environment.NewLine); } do { int wordBuffer = 0; string[] words = value.Split(new[] { ' ' }); for (int i = 0; i < words.Length; i++) { if (words[i].Length < (maximumLength - wordBuffer)) { builder.Append(words[i]); wordBuffer += words[i].Length; if ((maximumLength - wordBuffer) > 1 && i != words.Length - 1) { builder.Append(" "); wordBuffer++; } } else if (words[i].Length >= maximumLength && wordBuffer == 0) { builder.Append(words[i].Substring(0, maximumLength)); wordBuffer = maximumLength; break; } else { break; } } value = value.Substring(Math.Min(wordBuffer, value.Length)); if (value.Length > 0) { builder.Append(Environment.NewLine); } } while (value.Length > maximumLength); builder.Append(value); } private void DoAddOptions(object options, string requiredWord, int maximumLength, bool fireEvent = true) { var optionList = ReflectionHelper.RetrievePropertyAttributeList(options); var optionHelp = ReflectionHelper.RetrieveMethodAttributeOnly(options); if (optionHelp != null) { optionList.Add(optionHelp); } if (optionList.Count == 0) { return; } int maxLength = GetMaxLength(optionList); this._optionsHelp = new StringBuilder(BuilderCapacity); int remainingSpace = maximumLength - (maxLength + 6); foreach (BaseOptionAttribute option in optionList) { AddOption(requiredWord, maxLength, option, remainingSpace, fireEvent); } } private void AddPreOptionsLine(string value, int maximumLength) { AddLine(_preOptionsHelp, value, maximumLength); } private void AddOption(string requiredWord, int maxLength, BaseOptionAttribute option, int widthOfHelpText, bool fireEvent = true) { this._optionsHelp.Append(" "); var optionName = new StringBuilder(maxLength); if (option.HasShortName) { if (this._addDashesToOption) { optionName.Append('-'); } optionName.AppendFormat("{0}", option.ShortName); if (option.HasMetaValue) { optionName.AppendFormat(" {0}", option.MetaValue); } if (option.HasLongName) { optionName.Append(", "); } } if (option.HasLongName) { if (this._addDashesToOption) { optionName.Append("--"); } optionName.AppendFormat("{0}", option.LongName); if (option.HasMetaValue) { optionName.AppendFormat("={0}", option.MetaValue); } } this._optionsHelp.Append(optionName.Length < maxLength ? optionName.ToString().PadRight(maxLength) : optionName.ToString()); this._optionsHelp.Append(" "); if (option.HasDefaultValue) { option.HelpText = "(Default: {0}) ".FormatLocal(option.DefaultValue) + option.HelpText; } if (option.Required) { option.HelpText = "{0} ".FormatInvariant(requiredWord) + option.HelpText; } if (fireEvent) { var e = new FormatOptionHelpTextEventArgs(option); OnFormatOptionHelpText(e); option.HelpText = e.Option.HelpText; } if (!string.IsNullOrEmpty(option.HelpText)) { do { int wordBuffer = 0; var words = option.HelpText.Split(new[] { ' ' }); for (int i = 0; i < words.Length; i++) { if (words[i].Length < (widthOfHelpText - wordBuffer)) { this._optionsHelp.Append(words[i]); wordBuffer += words[i].Length; if ((widthOfHelpText - wordBuffer) > 1 && i != words.Length - 1) { this._optionsHelp.Append(" "); wordBuffer++; } } else if (words[i].Length >= widthOfHelpText && wordBuffer == 0) { this._optionsHelp.Append(words[i].Substring(0, widthOfHelpText)); wordBuffer = widthOfHelpText; break; } else { break; } } option.HelpText = option.HelpText.Substring( Math.Min(wordBuffer, option.HelpText.Length)).Trim(); if (option.HelpText.Length > 0) { this._optionsHelp.Append(Environment.NewLine); this._optionsHelp.Append(new string(' ', maxLength + 6)); } } while (option.HelpText.Length > widthOfHelpText); } this._optionsHelp.Append(option.HelpText); this._optionsHelp.Append(Environment.NewLine); if (_additionalNewLineAfterOption) { this._optionsHelp.Append(Environment.NewLine); } } private void AddLine(StringBuilder builder, string value) { Assumes.NotNull(value, "value"); AddLine(builder, value, MaximumDisplayWidth); } private int GetMaxLength(IEnumerable optionList) { int length = 0; foreach (BaseOptionAttribute option in optionList) { int optionLength = 0; bool hasShort = option.HasShortName; bool hasLong = option.HasLongName; int metaLength = 0; if (option.HasMetaValue) { metaLength = option.MetaValue.Length + 1; } if (hasShort) { ++optionLength; if (AddDashesToOption) { ++optionLength; } optionLength += metaLength; } if (hasLong) { optionLength += option.LongName.Length; if (AddDashesToOption) { optionLength += 2; } optionLength += metaLength; } if (hasShort && hasLong) { optionLength += 2; // ", " } length = Math.Max(length, optionLength); } return length; } } }