// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.
using CommandLine.Core;
using CommandLine.Infrastructure;
using CSharpx;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace CommandLine.Text
{
///
/// Provides means to format an help screen.
/// You can assign it in place of a instance.
///
public struct ComparableOption
{
public bool Required;
public bool IsOption;
public bool IsValue;
public string LongName;
public string ShortName;
public int Index;
}
public class HelpText
{
#region ordering
ComparableOption ToComparableOption(Specification spec, int index)
{
OptionSpecification option = spec as OptionSpecification;
ValueSpecification value = spec as ValueSpecification;
bool required = option?.Required ?? false;
return new ComparableOption()
{
Required = required,
IsOption = option != null,
IsValue = value != null,
LongName = option?.LongName ?? value?.MetaName,
ShortName = option?.ShortName,
Index = index
};
}
public Comparison OptionComparison { get; set; } = null;
public static Comparison RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) =>
{
if (attr1.IsOption && attr2.IsOption)
{
if (attr1.Required && !attr2.Required)
{
return -1;
}
else if (!attr1.Required && attr2.Required)
{
return 1;
}
return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal);
}
else if (attr1.IsOption && attr2.IsValue)
{
return -1;
}
else
{
return 1;
}
};
#endregion
private const int BuilderCapacity = 128;
private const int DefaultMaximumLength = 80; // default console width
///
/// The number of spaces between an option and its associated help text
///
private const int OptionToHelpTextSeparatorWidth = 4;
///
/// The width of the option prefix (either "--" or " "
///
private const int OptionPrefixWidth = 2;
///
/// The total amount of extra space that needs to accounted for when indenting Option help text
///
private const int TotalOptionPadding = OptionToHelpTextSeparatorWidth + OptionPrefixWidth;
private readonly StringBuilder preOptionsHelp;
private readonly StringBuilder postOptionsHelp;
private readonly SentenceBuilder sentenceBuilder;
private int maximumDisplayWidth;
private string heading;
private string copyright;
private bool additionalNewLineAfterOption;
private StringBuilder optionsHelp;
private bool addDashesToOption;
private bool addEnumValuesToHelpText;
private bool autoHelp;
private bool autoVersion;
private bool addNewLineBetweenHelpSections;
///
/// Initializes a new instance of the class.
///
public HelpText()
: this(SentenceBuilder.Create(), string.Empty, string.Empty)
{
}
///
/// Initializes a new instance of the class
/// specifying the sentence builder.
///
///
/// A instance.
///
public HelpText(SentenceBuilder sentenceBuilder)
: this(sentenceBuilder, string.Empty, string.Empty)
{
}
///
/// 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(SentenceBuilder.Create(), heading, string.Empty)
{
}
///
/// 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(SentenceBuilder sentenceBuilder, string heading)
: this(sentenceBuilder, heading, string.Empty)
{
}
///
/// 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(SentenceBuilder.Create(), heading, 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(SentenceBuilder sentenceBuilder, string heading, string copyright)
{
if (sentenceBuilder == null) throw new ArgumentNullException("sentenceBuilder");
if (heading == null) throw new ArgumentNullException("heading");
if (copyright == null) throw new ArgumentNullException("copyright");
preOptionsHelp = new StringBuilder(BuilderCapacity);
postOptionsHelp = new StringBuilder(BuilderCapacity);
try
{
maximumDisplayWidth = Console.WindowWidth;
if (maximumDisplayWidth < 1)
{
maximumDisplayWidth = DefaultMaximumLength;
}
}
catch (IOException)
{
maximumDisplayWidth = DefaultMaximumLength;
}
this.sentenceBuilder = sentenceBuilder;
this.heading = heading;
this.copyright = copyright;
this.autoHelp = true;
this.autoVersion = true;
}
///
/// Gets or sets the heading string.
/// You can directly assign a instance.
///
public string Heading
{
get { return heading; }
set
{
if (value == null) throw new ArgumentNullException("value");
heading = value;
}
}
///
/// Gets or sets the copyright string.
/// You can directly assign a instance.
///
public string Copyright
{
get { return copyright; }
set
{
if (value == null) throw new ArgumentNullException("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; }
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 addDashesToOption; }
set { addDashesToOption = value; }
}
///
/// Gets or sets a value indicating whether to add an additional line after the description of the specification.
///
public bool AdditionalNewLineAfterOption
{
get { return additionalNewLineAfterOption; }
set { additionalNewLineAfterOption = value; }
}
///
/// Gets or sets a value indicating whether to add newlines between help sections.
///
public bool AddNewLineBetweenHelpSections
{
get { return addNewLineBetweenHelpSections; }
set { addNewLineBetweenHelpSections = value; }
}
///
/// Gets or sets a value indicating whether to add the values of an enum after the description of the specification.
///
public bool AddEnumValuesToHelpText
{
get { return addEnumValuesToHelpText; }
set { addEnumValuesToHelpText = value; }
}
///
/// Gets or sets a value indicating whether implicit option or verb 'help' should be supported.
///
public bool AutoHelp
{
get { return autoHelp; }
set { autoHelp = value; }
}
///
/// Gets or sets a value indicating whether implicit option or verb 'version' should be supported.
///
public bool AutoVersion
{
get { return autoVersion; }
set { autoVersion = value; }
}
///
/// Gets the instance specified in constructor.
///
public SentenceBuilder SentenceBuilder
{
get { return sentenceBuilder; }
}
///
/// Creates a new instance of the class using common defaults.
///
///
/// An instance of class.
///
/// The containing the instance that collected command line arguments parsed with class.
/// A delegate used to customize the text block of reporting parsing errors text block.
/// A delegate used to customize model used to render text block of usage examples.
/// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options.
/// The maximum width of the display.
/// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options.
public static HelpText AutoBuild(
ParserResult parserResult,
Func onError,
Func onExample,
bool verbsIndex = false,
int maxDisplayWidth = DefaultMaximumLength)
{
var auto = new HelpText
{
Heading = HeadingInfo.Empty,
Copyright = CopyrightInfo.Empty,
AdditionalNewLineAfterOption = true,
AddDashesToOption = !verbsIndex,
MaximumDisplayWidth = maxDisplayWidth
};
try
{
auto.Heading = HeadingInfo.Default;
auto.Copyright = CopyrightInfo.Default;
}
catch (Exception)
{
auto = onError(auto);
}
var errors = Enumerable.Empty();
if (onError != null && parserResult.Tag == ParserResultType.NotParsed)
{
errors = ((NotParsed)parserResult).Errors;
if (errors.IsHelp() || errors.OnlyMeaningfulOnes().Any())
auto = onError(auto);
}
ReflectionHelper.GetAttribute()
.Do(license => license.AddToHelpText(auto, true));
var usageAttr = ReflectionHelper.GetAttribute();
var usageLines = HelpText.RenderUsageTextAsLines(parserResult, onExample).ToMaybe();
if (usageAttr.IsJust() || usageLines.IsJust())
{
var heading = auto.SentenceBuilder.UsageHeadingText();
if (heading.Length > 0)
{
if (auto.AddNewLineBetweenHelpSections)
heading = Environment.NewLine + heading;
auto.AddPreOptionsLine(heading);
}
}
usageAttr.Do(
usage => usage.AddToHelpText(auto, true));
usageLines.Do(
lines => auto.AddPreOptionsLines(lines));
if ((verbsIndex && parserResult.TypeInfo.Choices.Any())
|| errors.Any(e => e.Tag == ErrorType.NoVerbSelectedError))
{
auto.AddDashesToOption = false;
auto.AddVerbs(parserResult.TypeInfo.Choices.ToArray());
}
else
auto.AddOptions(parserResult);
return auto;
}
///
/// Creates a default instance of the class,
/// automatically handling verbs or options scenario.
///
/// The containing the instance that collected command line arguments parsed with class.
/// The maximum width of the display.
///
/// An instance of class.
///
/// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property
/// of .
public static HelpText AutoBuild(ParserResult parserResult, int maxDisplayWidth = DefaultMaximumLength)
{
return AutoBuild(parserResult, h => h, maxDisplayWidth);
}
///
/// Creates a custom instance of the class,
/// automatically handling verbs or options scenario.
///
/// The containing the instance that collected command line arguments parsed with class.
/// A delegate used to customize the text block of reporting parsing errors text block.
/// The maximum width of the display.
///
/// An instance of class.
///
/// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property
/// of .
public static HelpText AutoBuild(ParserResult parserResult, Func onError, int maxDisplayWidth = DefaultMaximumLength)
{
if (parserResult.Tag != ParserResultType.NotParsed)
throw new ArgumentException("Excepting NotParsed type.", "parserResult");
var errors = ((NotParsed)parserResult).Errors;
if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError))
return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}") { MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine);
if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError))
return AutoBuild(parserResult, current =>
{
onError?.Invoke(current);
return DefaultParsingErrorsHandler(parserResult, current);
}, e => e, maxDisplayWidth: maxDisplayWidth);
var err = errors.OfType().Single();
var pr = new NotParsed