diff --git a/.editorconfig b/.editorconfig index e6796a9..522a693 100644 --- a/.editorconfig +++ b/.editorconfig @@ -289,6 +289,12 @@ dotnet_diagnostic.ide0044.severity = none # IDE0042: Deconstruct variable declaration dotnet_diagnostic.ide0042.severity = none +# IDE0305: Simplify collection initialization +dotnet_diagnostic.ide0305.severity = none + +# IDE0057: Substring can be simplified +dotnet_diagnostic.ide0057.severity = none + # CS1998: Async method lacks 'await' operators and will run synchronously dotnet_diagnostic.cs1998.severity = none diff --git a/Src/Attributes.cs b/Src/Attributes.cs new file mode 100644 index 0000000..a745d42 --- /dev/null +++ b/Src/Attributes.cs @@ -0,0 +1,144 @@ +using RT.Util; +using RT.Util.Consoles; + +namespace RT.CommandLine; + +/// +/// Use this to specify that a field in a class can be specified on the command line using an option, for example +/// -a or --option-name. The option name(s) MUST begin with a dash (-). +/// +/// The name of the option. Specify several names as synonyms if required. +[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public sealed class OptionAttribute(params string[] names) : Attribute +{ + /// All of the names of the option. + public string[] Names { get; private set; } = names; +} + +/// +/// Use this to specify that a command-line parameter is positional, i.e. is not invoked by an option that starts with +/// "-". +[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public sealed class IsPositionalAttribute : Attribute +{ + /// Constructor. + public IsPositionalAttribute() { } +} + +/// Use this to specify that a command-line parameter is mandatory. +[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public sealed class IsMandatoryAttribute() : Attribute +{ +} + +/// Specifies that the command-line parser should ignore a field. +[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] +public sealed class IgnoreAttribute : Attribute +{ + /// Constructor. + public IgnoreAttribute() { } +} + +/// +/// Specifies that a field of an enum type should be interpreted as multiple possible options, each specified by an on the enum values in the enum type. +[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] +public sealed class EnumOptionsAttribute(EnumBehavior behavior) : Attribute +{ + /// + /// Specifies whether the enum is considered to represent a single value or a bitfield containing multiple values. + public EnumBehavior Behavior { get; private set; } = behavior; +} + +/// +/// Use this on a sub-class of an abstract class or on an enum value to specify the command the user must use to invoke +/// that class or enum value. +/// +/// The command(s) the user can specify to invoke this class or enum value. +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public sealed class CommandNameAttribute(params string[] names) : Attribute +{ + /// The command the user can specify to invoke this class. + public string[] Names { get; private set; } = names; +} + +/// Use this on an abstract class to specify that its subclasses represent various commands. +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class CommandGroupAttribute : Attribute +{ + /// Constructor. + public CommandGroupAttribute() { } +} + +/// +/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable +/// for single-language applications only. See Remarks. +/// +/// This attribute specifies the documentation in plain text. All characters are printed exactly as specified. You may +/// wish to use to specify documentation with special markup for +/// command-line-related concepts, as well as for an alternative markup +/// language without command-line specific concepts. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public class DocumentationAttribute(string documentation) : Attribute +{ + /// + /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed + /// where applicable. + public virtual ConsoleColoredString Text => OriginalText; + /// Gets a string describing the documentation format to the programmer (not seen by the users). + public virtual string OriginalFormat => "Plain text"; + /// Gets the original documentation string exactly as specified in the attribute. + public string OriginalText { get; private set; } = documentation; +} + +/// +/// This is a legacy attribute. Do not use in new programs. This attribute is equivalent to . +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public class DocumentationLiteralAttribute(string documentation) : DocumentationEggsMLAttribute(documentation) +{ +} + +/// +/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable +/// for single-language applications only. The documentation is to be specified in , which is +/// interpreted as described in . See also and . +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public class DocumentationEggsMLAttribute(string documentation) : DocumentationAttribute(documentation) +{ + /// Gets a string describing the documentation format to the programmer (not seen by the users). + public override string OriginalFormat { get { return "EggsML"; } } + /// + /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed + /// where applicable. + public override ConsoleColoredString Text => _parsed ??= CommandLineParser.Colorize(EggsML.Parse(OriginalText)); + private ConsoleColoredString _parsed; +} + +/// +/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable +/// for single-language applications only. The documentation is to be specified in , which is +/// interpreted as described in . See also . +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] +public class DocumentationRhoMLAttribute(string documentation) : DocumentationAttribute(documentation) +{ + /// Gets a string describing the documentation format to the programmer (not seen by the users). + public override string OriginalFormat { get { return "RhoML"; } } + /// + /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed + /// where applicable. + public override ConsoleColoredString Text => _parsed ??= CommandLineParser.Colorize(RhoML.Parse(OriginalText)); + private ConsoleColoredString _parsed; +} + +/// +/// Specifies that a specific command-line option should not be printed in help pages, i.e. the option should explicitly +/// be undocumented. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class UndocumentedAttribute : Attribute +{ + /// Constructor. + public UndocumentedAttribute() { } +} diff --git a/Src/CommandGroupAttribute.cs b/Src/CommandGroupAttribute.cs deleted file mode 100644 index f51e67f..0000000 --- a/Src/CommandGroupAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace RT.CommandLine; - -/// Use this on an abstract class to specify that its subclasses represent various commands. -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] -public sealed class CommandGroupAttribute : Attribute -{ - /// Constructor. - public CommandGroupAttribute() { } -} diff --git a/Src/CommandLineHelpRequestedException.cs b/Src/CommandLineHelpRequestedException.cs deleted file mode 100644 index 1785256..0000000 --- a/Src/CommandLineHelpRequestedException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// Indicates that the user supplied one of the standard options we recognize as a help request. -[Serializable] -public sealed class CommandLineHelpRequestedException(Func helpGenerator) - : CommandLineParseException("The user has requested help using one of the help options.".Color(ConsoleColor.Gray), helpGenerator) -{ - /// Prints usage information. - public override void WriteUsageInfoToConsole() => ConsoleUtil.Write(GenerateHelp(ConsoleUtil.WrapToWidth())); - /// - protected internal override bool WriteErrorText => false; -} diff --git a/Src/CommandLineParseException.cs b/Src/CommandLineParseException.cs deleted file mode 100644 index 14c006e..0000000 --- a/Src/CommandLineParseException.cs +++ /dev/null @@ -1,70 +0,0 @@ -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// Represents any error encountered while parsing a command line. This class is abstract. -[Serializable] -public abstract class CommandLineParseException(ConsoleColoredString message, Func helpGenerator, Exception inner) - : Exception(message.ToString(), inner) -{ - /// - /// Generates the help screen to be output to the user on the console. For non-internationalised (single-language) - /// applications, pass null for the Translation parameter. - internal Func GenerateHelpFunc { get; private set; } = helpGenerator; - /// Contains the error message that describes the cause of this exception. - public ConsoleColoredString ColoredMessage { get; private set; } = message; - /// - /// Generates the help screen to be output to the user on the console. - /// - /// The character width at which the output should be word-wrapped. The default (null) uses . - public ConsoleColoredString GenerateHelp(int? wrapWidth = null) { return GenerateHelpFunc(wrapWidth ?? ConsoleUtil.WrapToWidth()); } - /// - /// Generates a printable description of the error represented by this exception, typically used to tell the user what - /// they did wrong. - /// - /// The character width at which the output should be word-wrapped. The default (null) uses . - public ConsoleColoredString GenerateErrorText(int? wrapWidth = null) - { - var strings = new List(); - var message = "Error:".Color(CmdLineColor.Error) + " " + ColoredMessage; - foreach (var line in message.WordWrap(wrapWidth ?? ConsoleUtil.WrapToWidth(), "Error:".Length + 1)) - { - strings.Add(line); - strings.Add(Environment.NewLine); - } - return new ConsoleColoredString(strings); - } - - /// Constructor. - public CommandLineParseException(ConsoleColoredString message, Func helpGenerator) : this(message, helpGenerator, null) { } - - /// - /// Prints usage information, followed by an error message describing to the user what it was that the parser didn't - /// understand. - public virtual void WriteUsageInfoToConsole() - { - ConsoleUtil.Write(GetUsageInfo()); - } - - /// - /// Generates and returns usage information, followed by an error message describing to the user what it was that the - /// parser didn't understand. - public ConsoleColoredString GetUsageInfo() - { - var s = GenerateHelp(ConsoleUtil.WrapToWidth()); - if (WriteErrorText) - s += Environment.NewLine + GenerateErrorText(ConsoleUtil.WrapToWidth()); - return s; - } - - /// - /// Determines whether and should call and output it to the console. Default is true. - /// - /// Only set this to false if the user explicitly asked to see the help screen. Otherwise its appearance - /// without explanation is confusing. - protected internal virtual bool WriteErrorText => true; -} diff --git a/Src/CommandLineValidationException.cs b/Src/CommandLineValidationException.cs deleted file mode 100644 index faf35e7..0000000 --- a/Src/CommandLineValidationException.cs +++ /dev/null @@ -1,10 +0,0 @@ -using RT.Util.Consoles; - -namespace RT.CommandLine; - -/// Specifies that the arguments specified by the user on the command-line do not pass the custom validation checks. -[Serializable] -public sealed class CommandLineValidationException(ConsoleColoredString message, Func helpGenerator) - : CommandLineParseException(message, helpGenerator) -{ -} diff --git a/Src/CommandNameAttribute.cs b/Src/CommandNameAttribute.cs deleted file mode 100644 index 3460d57..0000000 --- a/Src/CommandNameAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using RT.Util; - -namespace RT.CommandLine; - -/// -/// Use this on a sub-class of an abstract class or on an enum value to specify the command the user must use to invoke -/// that class or enum value. -/// -/// The command(s) the user can specify to invoke this class or enum value. -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public sealed class CommandNameAttribute(params string[] names) : Attribute -{ - /// The command the user can specify to invoke this class. - public string[] Names { get; private set; } = names; -} diff --git a/Src/DocumentationAttribute.cs b/Src/DocumentationAttribute.cs deleted file mode 100644 index ca70e76..0000000 --- a/Src/DocumentationAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using RT.Util; -using RT.Util.Consoles; - -namespace RT.CommandLine; - -/// -/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable -/// for single-language applications only. See Remarks. -/// -/// This attribute specifies the documentation in plain text. All characters are printed exactly as specified. You may -/// wish to use to specify documentation with special markup for -/// command-line-related concepts, as well as for an alternative markup -/// language without command-line specific concepts. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public class DocumentationAttribute(string documentation) : Attribute -{ - /// - /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed - /// where applicable. - public virtual ConsoleColoredString Text => OriginalText; - /// Gets a string describing the documentation format to the programmer (not seen by the users). - public virtual string OriginalFormat => "Plain text"; - /// Gets the original documentation string exactly as specified in the attribute. - public string OriginalText { get; private set; } = documentation; -} diff --git a/Src/DocumentationEggsMLAttribute.cs b/Src/DocumentationEggsMLAttribute.cs deleted file mode 100644 index 6ff83be..0000000 --- a/Src/DocumentationEggsMLAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using RT.Util; -using RT.Util.Consoles; - -namespace RT.CommandLine; - -/// -/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable -/// for single-language applications only. The documentation is to be specified in , which is -/// interpreted as described in . See also and . -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public class DocumentationEggsMLAttribute(string documentation) : DocumentationAttribute(documentation) -{ - /// Gets a string describing the documentation format to the programmer (not seen by the users). - public override string OriginalFormat { get { return "EggsML"; } } - /// - /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed - /// where applicable. - public override ConsoleColoredString Text => _parsed ??= CommandLineParser.Colorize(EggsML.Parse(OriginalText)); - private ConsoleColoredString _parsed; -} diff --git a/Src/DocumentationLiteralAttribute.cs b/Src/DocumentationLiteralAttribute.cs deleted file mode 100644 index a141f1e..0000000 --- a/Src/DocumentationLiteralAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -using RT.Util; - -namespace RT.CommandLine; - -/// -/// This is a legacy attribute. Do not use in new programs. This attribute is equivalent to . -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public class DocumentationLiteralAttribute(string documentation) : DocumentationEggsMLAttribute(documentation) -{ -} diff --git a/Src/DocumentationRhoMLAttribute.cs b/Src/DocumentationRhoMLAttribute.cs deleted file mode 100644 index 10b7965..0000000 --- a/Src/DocumentationRhoMLAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using RT.Util; -using RT.Util.Consoles; - -namespace RT.CommandLine; - -/// -/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable -/// for single-language applications only. The documentation is to be specified in , which is -/// interpreted as described in . See also . -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public class DocumentationRhoMLAttribute(string documentation) : DocumentationAttribute(documentation) -{ - /// Gets a string describing the documentation format to the programmer (not seen by the users). - public override string OriginalFormat { get { return "RhoML"; } } - /// - /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed - /// where applicable. - public override ConsoleColoredString Text => _parsed ??= CommandLineParser.Colorize(RhoML.Parse(OriginalText)); - private ConsoleColoredString _parsed; -} diff --git a/Src/EnumOptionsAttribute.cs b/Src/EnumOptionsAttribute.cs deleted file mode 100644 index 47cff33..0000000 --- a/Src/EnumOptionsAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace RT.CommandLine; - -/// -/// Specifies that a field of an enum type should be interpreted as multiple possible options, each specified by an on the enum values in the enum type. -[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] -public sealed class EnumOptionsAttribute(EnumBehavior behavior) : Attribute -{ - /// - /// Specifies whether the enum is considered to represent a single value or a bitfield containing multiple values. - public EnumBehavior Behavior { get; private set; } = behavior; -} diff --git a/Src/Exceptions.cs b/Src/Exceptions.cs new file mode 100644 index 0000000..b185086 --- /dev/null +++ b/Src/Exceptions.cs @@ -0,0 +1,189 @@ +using System.Reflection; +using RT.Util.Consoles; +using RT.Util.ExtensionMethods; + +namespace RT.CommandLine; + +/// Represents any error encountered while parsing a command line. This class is abstract. +[Serializable] +public abstract class CommandLineParseException(ConsoleColoredString message, Func helpGenerator, Exception inner) + : Exception(message.ToString(), inner) +{ + /// + /// Generates the help screen to be output to the user on the console. For non-internationalised (single-language) + /// applications, pass null for the Translation parameter. + internal Func GenerateHelpFunc { get; private set; } = helpGenerator; + /// Contains the error message that describes the cause of this exception. + public ConsoleColoredString ColoredMessage { get; private set; } = message; + /// + /// Generates the help screen to be output to the user on the console. + /// + /// The character width at which the output should be word-wrapped. The default (null) uses . + public ConsoleColoredString GenerateHelp(int? wrapWidth = null) { return GenerateHelpFunc(wrapWidth ?? ConsoleUtil.WrapToWidth()); } + /// + /// Generates a printable description of the error represented by this exception, typically used to tell the user what + /// they did wrong. + /// + /// The character width at which the output should be word-wrapped. The default (null) uses . + public ConsoleColoredString GenerateErrorText(int? wrapWidth = null) + { + var strings = new List(); + var message = "Error:".Color(CmdLineColor.Error) + " " + ColoredMessage; + foreach (var line in message.WordWrap(wrapWidth ?? ConsoleUtil.WrapToWidth(), "Error:".Length + 1)) + { + strings.Add(line); + strings.Add(Environment.NewLine); + } + return new ConsoleColoredString(strings); + } + + /// Constructor. + public CommandLineParseException(ConsoleColoredString message, Func helpGenerator) : this(message, helpGenerator, null) { } + + /// + /// Prints usage information, followed by an error message describing to the user what it was that the parser didn't + /// understand. + public virtual void WriteUsageInfoToConsole() + { + ConsoleUtil.Write(GetUsageInfo()); + } + + /// + /// Generates and returns usage information, followed by an error message describing to the user what it was that the + /// parser didn't understand. + public ConsoleColoredString GetUsageInfo() + { + var s = GenerateHelp(ConsoleUtil.WrapToWidth()); + if (WriteErrorText) + s += Environment.NewLine + GenerateErrorText(ConsoleUtil.WrapToWidth()); + return s; + } + + /// + /// Determines whether and should call and output it to the console. Default is true. + /// + /// Only set this to false if the user explicitly asked to see the help screen. Otherwise its appearance + /// without explanation is confusing. + protected internal virtual bool WriteErrorText => true; +} + +/// Indicates that the user supplied one of the standard options we recognize as a help request. +[Serializable] +public sealed class CommandLineHelpRequestedException(Func helpGenerator) + : CommandLineParseException("The user has requested help using one of the help options.".Color(ConsoleColor.Gray), helpGenerator) +{ + /// Prints usage information. + public override void WriteUsageInfoToConsole() => ConsoleUtil.Write(GenerateHelp(ConsoleUtil.WrapToWidth())); + /// + protected internal override bool WriteErrorText => false; +} + +/// +/// Specifies that the command-line parser encountered a command or option that was not recognised (there was no or attribute with a matching option or command name). +[Serializable] +public sealed class UnrecognizedCommandOrOptionException(string commandOrOptionName, Func helpGenerator, Exception inner) + : CommandLineParseException("The specified command or option, {0}, is not recognized.".ToConsoleColoredString().Fmt(commandOrOptionName.Color(ConsoleColor.White)), helpGenerator, inner) +{ + /// The unrecognized command name or option name. + public string CommandOrOptionName { get; private set; } = commandOrOptionName; + /// Constructor. + public UnrecognizedCommandOrOptionException(string commandOrOptionName, Func helpGenerator) : this(commandOrOptionName, helpGenerator, null) { } +} + +/// +/// Specifies that the command-line parser encountered a command or option that is not allowed in conjunction with a +/// previously-encountered command or option. +[Serializable] +public sealed class IncompatibleCommandOrOptionException(string earlier, string later, Func helpGenerator, Exception inner) + : CommandLineParseException("The command or option, {0}, cannot be used in conjunction with {1}. Please specify only one of the two.".ToConsoleColoredString().Fmt(later.Color(ConsoleColor.White), earlier.Color(ConsoleColor.White)), helpGenerator, inner) +{ + /// + /// The earlier option or command, which by itself is valid, but conflicts with the . + public string EarlierCommandOrOption { get; private set; } = earlier; + /// The later option or command, which conflicts with the . + public string LaterCommandOrOption { get; private set; } = later; + /// Constructor. + public IncompatibleCommandOrOptionException(string earlier, string later, Func helpGenerator) : this(earlier, later, helpGenerator, null) { } +} + +/// +/// Specifies that the command-line parser encountered the end of the command line when it expected additional mandatory +/// options. +[Serializable] +public sealed class MissingParameterException(FieldInfo paramField, FieldInfo beforeField, bool isOption, Func helpGenerator, Exception inner) + : CommandLineParseException(getMessage(paramField, beforeField, isOption), helpGenerator, inner) +{ + /// Contains the field pertaining to the parameter that was missing. + public FieldInfo Field { get; private set; } = paramField; + + /// Contains an optional reference to a field which the missing parameter must precede. + public FieldInfo BeforeField { get; private set; } = beforeField; + + /// + /// Specifies whether the missing parameter was a missing option (true) or a missing positional parameter (false). + public bool IsOption { get; private set; } = isOption; + + /// Constructor. + public MissingParameterException(FieldInfo paramField, FieldInfo beforeField, bool isOption, Func helpGenerator) : this(paramField, beforeField, isOption, helpGenerator, null) { } + + private static ConsoleColoredString getMessage(FieldInfo field, FieldInfo beforeField, bool isOption) + { + if (beforeField == null) + return (isOption ? "The option {0} is mandatory and must be specified." : "The parameter {0} is mandatory and must be specified.").ToConsoleColoredString().Fmt(field.FormatParameterUsage(true)); + + return (isOption ? "The option {0} is mandatory and must be specified before the {1} parameter." : "The parameter {0} is mandatory and must be specified before the {1} parameter.").ToConsoleColoredString().Fmt( + field.FormatParameterUsage(true), + "<".Color(CmdLineColor.FieldBrackets) + beforeField.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets)); + } +} + +/// +/// Specifies that the command-line parser encountered additional command-line arguments when it expected the end of the +/// command line. +[Serializable] +public sealed class UnexpectedArgumentException(string[] unexpectedArgs, Func helpGenerator, Exception inner) + : CommandLineParseException("Unexpected parameter: {0}".ToConsoleColoredString().Fmt(unexpectedArgs.Select(prm => prm.Length > 50 ? $"{prm.Substring(0, 47)}..." : prm).FirstOrDefault().Color(CmdLineColor.UnexpectedArgument)), helpGenerator, inner) +{ + /// Contains the first unexpected argument and all of the subsequent arguments. + public string[] UnexpectedParameters { get; private set; } = unexpectedArgs; + /// Constructor. + public UnexpectedArgumentException(string[] unexpectedArgs, Func helpGenerator) : this(unexpectedArgs, helpGenerator, null) { } +} + +/// +/// Specifies that the command-line parser encountered the end of the command line when it expected an argument to an +/// option. +[Serializable] +public sealed class IncompleteOptionException(string optionName, Func helpGenerator, Exception inner) + : CommandLineParseException("The {0} option must be followed by an additional parameter.".ToConsoleColoredString().Fmt(optionName.Color(ConsoleColor.White)), helpGenerator, inner) +{ + /// The name of the option that was missing an argument. + public string OptionName { get; private set; } = optionName; + /// Constructor. + public IncompleteOptionException(string optionName, Func helpGenerator) : this(optionName, helpGenerator, null) { } +} + +/// +/// Specifies that a parameter that expected a numerical value was passed a string by the user that doesn’t parse as a +/// number. +[Serializable] +public sealed class InvalidNumericParameterException(string fieldName, Func helpGenerator, Exception inner) + : CommandLineParseException("The {0} option expects a number. The specified parameter does not constitute a valid number.".ToConsoleColoredString().Fmt("<".Color(CmdLineColor.FieldBrackets) + fieldName.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets)), helpGenerator, inner) +{ + /// Contains the name of the field pertaining to the parameter that was passed an invalid value. + public string FieldName { get; private set; } = fieldName; + /// Constructor. + public InvalidNumericParameterException(string fieldName, Func helpGenerator) : this(fieldName, helpGenerator, null) { } +} + +/// Specifies that the arguments specified by the user on the command-line do not pass the custom validation checks. +[Serializable] +public sealed class CommandLineValidationException(ConsoleColoredString message, Func helpGenerator) + : CommandLineParseException(message, helpGenerator) +{ +} diff --git a/Src/GlobalSuppressions.cs b/Src/GlobalSuppressions.cs deleted file mode 100644 index 7ae7a12..0000000 --- a/Src/GlobalSuppressions.cs +++ /dev/null @@ -1,9 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Let me use ToArray", Scope = "member", Target = "~M:RT.CommandLine.CommandLineParser.parseCommandLine(System.String[],System.Type,System.Int32,System.Func{RT.Util.Consoles.ConsoleColoredString,RT.Util.Consoles.ConsoleColoredString})~System.Object")] -[assembly: SuppressMessage("Style", "IDE0057:Substring can be simplified", Justification = "Let me use Substring")] diff --git a/Src/IgnoreAttribute.cs b/Src/IgnoreAttribute.cs deleted file mode 100644 index 43ec55e..0000000 --- a/Src/IgnoreAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace RT.CommandLine; - -/// Specifies that the command-line parser should ignore a field. -[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] -public sealed class IgnoreAttribute : Attribute -{ - /// Constructor. - public IgnoreAttribute() { } -} diff --git a/Src/IncompatibleCommandOrOptionException.cs b/Src/IncompatibleCommandOrOptionException.cs deleted file mode 100644 index f4101c1..0000000 --- a/Src/IncompatibleCommandOrOptionException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// -/// Specifies that the command-line parser encountered a command or option that is not allowed in conjunction with a -/// previously-encountered command or option. -[Serializable] -public sealed class IncompatibleCommandOrOptionException(string earlier, string later, Func helpGenerator, Exception inner) - : CommandLineParseException("The command or option, {0}, cannot be used in conjunction with {1}. Please specify only one of the two.".ToConsoleColoredString().Fmt(later.Color(ConsoleColor.White), earlier.Color(ConsoleColor.White)), helpGenerator, inner) -{ - /// - /// The earlier option or command, which by itself is valid, but conflicts with the . - public string EarlierCommandOrOption { get; private set; } = earlier; - /// The later option or command, which conflicts with the . - public string LaterCommandOrOption { get; private set; } = later; - /// Constructor. - public IncompatibleCommandOrOptionException(string earlier, string later, Func helpGenerator) : this(earlier, later, helpGenerator, null) { } -} diff --git a/Src/IncompleteOptionException.cs b/Src/IncompleteOptionException.cs deleted file mode 100644 index 4c5d048..0000000 --- a/Src/IncompleteOptionException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// -/// Specifies that the command-line parser encountered the end of the command line when it expected an argument to an -/// option. -[Serializable] -public sealed class IncompleteOptionException(string optionName, Func helpGenerator, Exception inner) - : CommandLineParseException("The {0} option must be followed by an additional parameter.".ToConsoleColoredString().Fmt(optionName.Color(ConsoleColor.White)), helpGenerator, inner) -{ - /// The name of the option that was missing an argument. - public string OptionName { get; private set; } = optionName; - /// Constructor. - public IncompleteOptionException(string optionName, Func helpGenerator) : this(optionName, helpGenerator, null) { } -} diff --git a/Src/InvalidNumericParameterException.cs b/Src/InvalidNumericParameterException.cs deleted file mode 100644 index 35d138d..0000000 --- a/Src/InvalidNumericParameterException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// -/// Specifies that a parameter that expected a numerical value was passed a string by the user that doesn’t parse as a -/// number. -[Serializable] -public sealed class InvalidNumericParameterException(string fieldName, Func helpGenerator, Exception inner) - : CommandLineParseException("The {0} option expects a number. The specified parameter does not constitute a valid number.".ToConsoleColoredString().Fmt("<".Color(CmdLineColor.FieldBrackets) + fieldName.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets)), helpGenerator, inner) -{ - /// Contains the name of the field pertaining to the parameter that was passed an invalid value. - public string FieldName { get; private set; } = fieldName; - /// Constructor. - public InvalidNumericParameterException(string fieldName, Func helpGenerator) : this(fieldName, helpGenerator, null) { } -} diff --git a/Src/IsMandatoryAttribute.cs b/Src/IsMandatoryAttribute.cs deleted file mode 100644 index 74050cc..0000000 --- a/Src/IsMandatoryAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using RT.Util; - -namespace RT.CommandLine; - -/// Use this to specify that a command-line parameter is mandatory. -[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public sealed class IsMandatoryAttribute() : Attribute -{ -} diff --git a/Src/IsPositionalAttribute.cs b/Src/IsPositionalAttribute.cs deleted file mode 100644 index 35a5d2d..0000000 --- a/Src/IsPositionalAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -using RT.Util; - -namespace RT.CommandLine; - -/// -/// Use this to specify that a command-line parameter is positional, i.e. is not invoked by an option that starts with -/// "-". -[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public sealed class IsPositionalAttribute : Attribute -{ - /// Constructor. - public IsPositionalAttribute() { } -} diff --git a/Src/MissingParameterException.cs b/Src/MissingParameterException.cs deleted file mode 100644 index f3ec76e..0000000 --- a/Src/MissingParameterException.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// -/// Specifies that the command-line parser encountered the end of the command line when it expected additional mandatory -/// options. -[Serializable] -public sealed class MissingParameterException(FieldInfo paramField, FieldInfo beforeField, bool isOption, Func helpGenerator, Exception inner) - : CommandLineParseException(getMessage(paramField, beforeField, isOption), helpGenerator, inner) -{ - /// Contains the field pertaining to the parameter that was missing. - public FieldInfo Field { get; private set; } = paramField; - - /// Contains an optional reference to a field which the missing parameter must precede. - public FieldInfo BeforeField { get; private set; } = beforeField; - - /// - /// Specifies whether the missing parameter was a missing option (true) or a missing positional parameter (false). - public bool IsOption { get; private set; } = isOption; - - /// Constructor. - public MissingParameterException(FieldInfo paramField, FieldInfo beforeField, bool isOption, Func helpGenerator) : this(paramField, beforeField, isOption, helpGenerator, null) { } - - private static ConsoleColoredString getMessage(FieldInfo field, FieldInfo beforeField, bool isOption) - { - if (beforeField == null) - return (isOption ? "The option {0} is mandatory and must be specified." : "The parameter {0} is mandatory and must be specified.").ToConsoleColoredString().Fmt(field.FormatParameterUsage(true)); - - return (isOption ? "The option {0} is mandatory and must be specified before the {1} parameter." : "The parameter {0} is mandatory and must be specified before the {1} parameter.").ToConsoleColoredString().Fmt( - field.FormatParameterUsage(true), - "<".Color(CmdLineColor.FieldBrackets) + beforeField.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets)); - } -} diff --git a/Src/OptionAttribute.cs b/Src/OptionAttribute.cs deleted file mode 100644 index ba3ad9a..0000000 --- a/Src/OptionAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using RT.Util; - -namespace RT.CommandLine; - -/// -/// Use this to specify that a field in a class can be specified on the command line using an option, for example -/// -a or --option-name. The option name(s) MUST begin with a dash (-). -/// -/// The name of the option. Specify several names as synonyms if required. -[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe] -public sealed class OptionAttribute(params string[] names) : Attribute -{ - /// All of the names of the option. - public string[] Names { get; private set; } = names; -} diff --git a/Src/UndocumentedAttribute.cs b/Src/UndocumentedAttribute.cs deleted file mode 100644 index fc17f5c..0000000 --- a/Src/UndocumentedAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace RT.CommandLine; - -/// -/// Specifies that a specific command-line option should not be printed in help pages, i.e. the option should explicitly -/// be undocumented. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public sealed class UndocumentedAttribute : Attribute -{ - /// Constructor. - public UndocumentedAttribute() { } -} diff --git a/Src/UnexpectedArgumentException.cs b/Src/UnexpectedArgumentException.cs deleted file mode 100644 index a8c9b8b..0000000 --- a/Src/UnexpectedArgumentException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using RT.Internal; -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// -/// Specifies that the command-line parser encountered additional command-line arguments when it expected the end of the -/// command line. -[Serializable] -public sealed class UnexpectedArgumentException(string[] unexpectedArgs, Func helpGenerator, Exception inner) - : CommandLineParseException("Unexpected parameter: {0}".ToConsoleColoredString().Fmt(unexpectedArgs.Select(prm => prm.Length > 50 ? $"{prm.Substring(0, 47)}..." : prm).FirstOrDefault().Color(CmdLineColor.UnexpectedArgument)), helpGenerator, inner) -{ - /// Contains the first unexpected argument and all of the subsequent arguments. - public string[] UnexpectedParameters { get; private set; } = unexpectedArgs; - /// Constructor. - public UnexpectedArgumentException(string[] unexpectedArgs, Func helpGenerator) : this(unexpectedArgs, helpGenerator, null) { } -} diff --git a/Src/UnrecognizedCommandOrOptionException.cs b/Src/UnrecognizedCommandOrOptionException.cs deleted file mode 100644 index 3d763d6..0000000 --- a/Src/UnrecognizedCommandOrOptionException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using RT.Util.Consoles; -using RT.Util.ExtensionMethods; - -namespace RT.CommandLine; - -/// -/// Specifies that the command-line parser encountered a command or option that was not recognised (there was no or attribute with a matching option or command name). -[Serializable] -public sealed class UnrecognizedCommandOrOptionException(string commandOrOptionName, Func helpGenerator, Exception inner) - : CommandLineParseException("The specified command or option, {0}, is not recognized.".ToConsoleColoredString().Fmt(commandOrOptionName.Color(ConsoleColor.White)), helpGenerator, inner) -{ - /// The unrecognized command name or option name. - public string CommandOrOptionName { get; private set; } = commandOrOptionName; - /// Constructor. - public UnrecognizedCommandOrOptionException(string commandOrOptionName, Func helpGenerator) : this(commandOrOptionName, helpGenerator, null) { } -}