From e3612ee7c187a07d9296c86bc4bbe4e7e08beedb Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Wed, 29 Nov 2023 14:39:30 -0800 Subject: [PATCH 01/54] Test AsyncCancelableCommandBase.Run --- src/Ookii.CommandLine.Tests/CommandTypes.cs | 13 +++---------- src/Ookii.CommandLine.Tests/SubCommandTest.cs | 8 ++++++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/CommandTypes.cs b/src/Ookii.CommandLine.Tests/CommandTypes.cs index 80e33d1..d9681ce 100644 --- a/src/Ookii.CommandLine.Tests/CommandTypes.cs +++ b/src/Ookii.CommandLine.Tests/CommandTypes.cs @@ -99,23 +99,16 @@ public Task RunAsync() [Command(IsHidden = true)] [Description("Async command description.")] -partial class AsyncCancelableCommand : IAsyncCancelableCommand +partial class AsyncCancelableCommand : AsyncCancelableCommandBase { [CommandLineArgument(Position = 0)] [Description("Argument description.")] public int Value { get; set; } - public int Run() - { - // Do something different than RunAsync so the test can differentiate which one was - // called. - return Value + 1; - } - - public async Task RunAsync(CancellationToken cancellationToken) + public override async Task RunAsync(CancellationToken cancellationToken) { await Task.Delay(Value, cancellationToken); - return 10; + return Value; } } diff --git a/src/Ookii.CommandLine.Tests/SubCommandTest.cs b/src/Ookii.CommandLine.Tests/SubCommandTest.cs index 6de3875..6091d71 100644 --- a/src/Ookii.CommandLine.Tests/SubCommandTest.cs +++ b/src/Ookii.CommandLine.Tests/SubCommandTest.cs @@ -355,9 +355,13 @@ await Assert.ThrowsExceptionAsync( async () => await manager.RunCommandAsync(["AsyncCancelableCommand", "10000"], source.Token)); // Command works if not passed a token. - Assert.AreEqual(10, await manager.RunCommandAsync(["AsyncCancelableCommand", "10"])); - } + var result = await manager.RunCommandAsync(["AsyncCancelableCommand", "10"]); + Assert.AreEqual(10, result); + // RunCommand works but calls Run. + result = manager.RunCommand(["AsyncCancelableCommand", "5"]); + Assert.AreEqual(5, result); + } [TestMethod] public async Task TestAsyncCommandBase() From 5c2b961c9b77724afeb710049af4440093a9ae91 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 10:38:00 -0800 Subject: [PATCH 02/54] Use CsWin32 for PInvoke, and improved console mode reset. --- src/Ookii.CommandLine/NativeMethods.cs | 115 ------------------ src/Ookii.CommandLine/NativeMethods.txt | 5 + .../Ookii.CommandLine.csproj | 3 + .../Terminal/VirtualTerminal.cs | 92 +++++++++++--- .../Terminal/VirtualTerminalSupport.cs | 21 ++-- 5 files changed, 94 insertions(+), 142 deletions(-) delete mode 100644 src/Ookii.CommandLine/NativeMethods.cs create mode 100644 src/Ookii.CommandLine/NativeMethods.txt diff --git a/src/Ookii.CommandLine/NativeMethods.cs b/src/Ookii.CommandLine/NativeMethods.cs deleted file mode 100644 index 50b02b7..0000000 --- a/src/Ookii.CommandLine/NativeMethods.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Ookii.CommandLine.Terminal; -using System; -using System.Runtime.InteropServices; - -namespace Ookii.CommandLine; - -static partial class NativeMethods -{ - static readonly IntPtr INVALID_HANDLE_VALUE = new(-1); - - public static (bool, ConsoleModes?) EnableVirtualTerminalSequences(StandardStream stream, bool enable) - { - if (stream == StandardStream.Input) - { - throw new ArgumentException(Properties.Resources.InvalidStandardStream, nameof(stream)); - } - - var handle = GetStandardHandle(stream); - if (handle == INVALID_HANDLE_VALUE) - { - return (false, null); - } - - if (!GetConsoleMode(handle, out ConsoleModes mode)) - { - return (false, null); - } - - var oldMode = mode; - if (enable) - { - mode |= ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING; - } - else - { - mode &= ~ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING; - } - - if (oldMode == mode) - { - return (true, null); - } - - if (!SetConsoleMode(handle, mode)) - { - return (false, null); - } - - return (true, oldMode); - } - - public static IntPtr GetStandardHandle(StandardStream stream) - { - var stdHandle = stream switch - { - StandardStream.Output => StandardHandle.STD_OUTPUT_HANDLE, - StandardStream.Input => StandardHandle.STD_INPUT_HANDLE, - StandardStream.Error => StandardHandle.STD_ERROR_HANDLE, - _ => throw new ArgumentException(Properties.Resources.InvalidStandardStream, nameof(stream)), - }; - - return GetStdHandle(stdHandle); - } - -#if NET7_0_OR_GREATER - [LibraryImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool SetConsoleMode(IntPtr hConsoleHandle, ConsoleModes dwMode); - - [LibraryImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool GetConsoleMode(IntPtr hConsoleHandle, out ConsoleModes lpMode); - - [LibraryImport("kernel32.dll", SetLastError = true)] - private static partial IntPtr GetStdHandle(StandardHandle nStdHandle); -#else - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool SetConsoleMode(IntPtr hConsoleHandle, ConsoleModes dwMode); - - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool GetConsoleMode(IntPtr hConsoleHandle, out ConsoleModes lpMode); - - [DllImport("kernel32.dll", SetLastError = true)] - static extern IntPtr GetStdHandle(StandardHandle nStdHandle); -#endif - - [Flags] - public enum ConsoleModes : uint - { - ENABLE_PROCESSED_INPUT = 0x0001, - ENABLE_LINE_INPUT = 0x0002, - ENABLE_ECHO_INPUT = 0x0004, - ENABLE_WINDOW_INPUT = 0x0008, - ENABLE_MOUSE_INPUT = 0x0010, - ENABLE_INSERT_MODE = 0x0020, - ENABLE_QUICK_EDIT_MODE = 0x0040, - ENABLE_EXTENDED_FLAGS = 0x0080, - ENABLE_AUTO_POSITION = 0x0100, - -#pragma warning disable CA1069 // Enums values should not be duplicated - ENABLE_PROCESSED_OUTPUT = 0x0001, - ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002, - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004, - DISABLE_NEWLINE_AUTO_RETURN = 0x0008, - ENABLE_LVB_GRID_WORLDWIDE = 0x0010 -#pragma warning restore CA1069 // Enums values should not be duplicated - } - - private enum StandardHandle - { - STD_OUTPUT_HANDLE = -11, - STD_INPUT_HANDLE = -10, - STD_ERROR_HANDLE = -12, - } -} diff --git a/src/Ookii.CommandLine/NativeMethods.txt b/src/Ookii.CommandLine/NativeMethods.txt new file mode 100644 index 0000000..501bcc2 --- /dev/null +++ b/src/Ookii.CommandLine/NativeMethods.txt @@ -0,0 +1,5 @@ +CONSOLE_MODE +STD_HANDLE +GetConsoleMode +GetStdHandle +SetConsoleMode \ No newline at end of file diff --git a/src/Ookii.CommandLine/Ookii.CommandLine.csproj b/src/Ookii.CommandLine/Ookii.CommandLine.csproj index d876bf4..d063b57 100644 --- a/src/Ookii.CommandLine/Ookii.CommandLine.csproj +++ b/src/Ookii.CommandLine/Ookii.CommandLine.csproj @@ -62,6 +62,9 @@ library for .Net applications. all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + diff --git a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs index d4e5202..22e4e91 100644 --- a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs +++ b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs @@ -1,5 +1,9 @@ -using System; +using Microsoft.Win32.SafeHandles; +using System; +using System.Diagnostics; using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.System.Console; namespace Ookii.CommandLine.Terminal; @@ -26,8 +30,9 @@ public static class VirtualTerminal /// The to enable VT sequences for. /// /// An instance of the class that will disable - /// virtual terminal support when disposed or destructed. Use the - /// property to check if virtual terminal sequences are supported. + /// virtual terminal support when disposed or finalized. Use the + /// property to check if + /// virtual terminal sequences are supported. /// /// /// @@ -37,8 +42,12 @@ public static class VirtualTerminal /// environment variable is defined. /// /// - /// For , this method does nothing and always returns - /// . + /// If you also want to check for a NO_COLOR environment variable, use the + /// method instead. + /// + /// + /// For , this method does nothing and + /// always returns . /// /// public static VirtualTerminalSupport EnableVirtualTerminalSequences(StandardStream stream) @@ -63,19 +72,16 @@ public static VirtualTerminalSupport EnableVirtualTerminalSequences(StandardStre if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - var (enabled, previousMode) = NativeMethods.EnableVirtualTerminalSequences(stream, true); - if (!enabled) + var result = SetVirtualTerminalSequences(stream, true); + if (result.NeedRestore) { - return new VirtualTerminalSupport(false); + Debug.Assert(result.Supported); + return new VirtualTerminalSupport(stream); } - if (previousMode is NativeMethods.ConsoleModes mode) - { - return new VirtualTerminalSupport(NativeMethods.GetStandardHandle(stream), mode); - } - - // Support was already enabled externally, so don't change the console mode on dispose. - return new VirtualTerminalSupport(true); + // VT sequences are either not supported, or were already enabled so we don't need to + // disable them. + return new VirtualTerminalSupport(result.Supported); } // Support is assumed on non-Windows platforms if TERM is set. @@ -195,4 +201,60 @@ private static int FindOscEndPartial(ReadOnlySpan value, ref StringSegment type = hasEscape ? StringSegmentType.PartialFormattingOscWithEscape : StringSegmentType.PartialFormattingOsc; return -1; } + + internal static (bool Supported, bool NeedRestore) SetVirtualTerminalSequences(StandardStream stream, bool enable) + { + if (stream == StandardStream.Input) + { + throw new ArgumentException(Properties.Resources.InvalidStandardStream, nameof(stream)); + } + + var handle = GetStandardHandle(stream); + if (handle.IsInvalid) + { + return (false, false); + } + + if (!PInvoke.GetConsoleMode(handle, out var mode)) + { + return (false, false); + } + + var oldMode = mode; + if (enable) + { + mode |= CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING; + } + else + { + mode &= ~CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING; + } + + if (oldMode == mode) + { + return (true, false); + } + + if (!PInvoke.SetConsoleMode(handle, mode)) + { + return (false, false); + } + + return (true, true); + } + + private static SafeFileHandle GetStandardHandle(StandardStream stream) + { + var stdHandle = stream switch + { + StandardStream.Output => STD_HANDLE.STD_OUTPUT_HANDLE, + StandardStream.Input => STD_HANDLE.STD_INPUT_HANDLE, + StandardStream.Error => STD_HANDLE.STD_ERROR_HANDLE, + _ => throw new ArgumentException(Properties.Resources.InvalidStandardStream, nameof(stream)), + }; + + // Generated function uses ownsHandle: false so the standard handle is not closed, as + // expected. + return PInvoke.GetStdHandle_SafeHandle(stdHandle); + } } diff --git a/src/Ookii.CommandLine/Terminal/VirtualTerminalSupport.cs b/src/Ookii.CommandLine/Terminal/VirtualTerminalSupport.cs index c4387ce..17e1301 100644 --- a/src/Ookii.CommandLine/Terminal/VirtualTerminalSupport.cs +++ b/src/Ookii.CommandLine/Terminal/VirtualTerminalSupport.cs @@ -12,21 +12,18 @@ namespace Ookii.CommandLine.Terminal; /// public sealed class VirtualTerminalSupport : IDisposable { - private readonly bool _supported; - private IntPtr _handle; - private readonly NativeMethods.ConsoleModes _previousMode; + private StandardStream? _restoreStream; internal VirtualTerminalSupport(bool supported) { - _supported = supported; + IsSupported = supported; GC.SuppressFinalize(this); } - internal VirtualTerminalSupport(IntPtr handle, NativeMethods.ConsoleModes previousMode) + internal VirtualTerminalSupport(StandardStream restoreStream) { - _supported = true; - _handle = handle; - _previousMode = previousMode; + IsSupported = true; + _restoreStream = restoreStream; } /// @@ -52,7 +49,7 @@ internal VirtualTerminalSupport(IntPtr handle, NativeMethods.ConsoleModes previo /// if virtual terminal sequences are supported; otherwise, /// . /// - public bool IsSupported => _supported; + public bool IsSupported { get; } /// /// Cleans up resources for the class. @@ -73,10 +70,10 @@ public void Dispose() private void ResetConsoleMode() { - if (_handle != IntPtr.Zero) + if (_restoreStream is StandardStream stream) { - NativeMethods.SetConsoleMode(_handle, _previousMode); - _handle = IntPtr.Zero; + VirtualTerminal.SetVirtualTerminalSequences(stream, false); + _restoreStream = null; } } } From 57f823b02ce6dedc02d7cecfb5639e1263ebdf7f Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 10:38:44 -0800 Subject: [PATCH 03/54] Update version to 4.1.0-preview. --- src/Directory.Build.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 88fe7c8..f3d8146 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -4,6 +4,7 @@ Sven Groot Ookii.org Copyright (c) Sven Groot (Ookii.org) - 4.0.1 + 4.1.0 + preview \ No newline at end of file From 96c6950bfa3d1a530dc9449edbaa5903d9f895d5 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 10:41:26 -0800 Subject: [PATCH 04/54] Remove unnecessary SourceLink package. --- src/Ookii.CommandLine/Ookii.CommandLine.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Ookii.CommandLine/Ookii.CommandLine.csproj b/src/Ookii.CommandLine/Ookii.CommandLine.csproj index d063b57..e89c8fc 100644 --- a/src/Ookii.CommandLine/Ookii.CommandLine.csproj +++ b/src/Ookii.CommandLine/Ookii.CommandLine.csproj @@ -1,4 +1,4 @@ - + net8.0;net7.0;net6.0;netstandard2.0;netstandard2.1 @@ -58,10 +58,6 @@ library for .Net applications. - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - all From c39f824e9862a7378dcacc63bec8d5c88f4e14e4 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 10:47:01 -0800 Subject: [PATCH 05/54] Dispose the SafeHandle. --- src/Ookii.CommandLine/Terminal/VirtualTerminal.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs index 22e4e91..458d091 100644 --- a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs +++ b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs @@ -209,7 +209,8 @@ internal static (bool Supported, bool NeedRestore) SetVirtualTerminalSequences(S throw new ArgumentException(Properties.Resources.InvalidStandardStream, nameof(stream)); } - var handle = GetStandardHandle(stream); + // Dispose should not close the handle here, but use it anyway. + using var handle = GetStandardHandle(stream); if (handle.IsInvalid) { return (false, false); From d547aa5d1bad3c3a7741c273f3bd9d07db6ed9e5 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 13:27:04 -0800 Subject: [PATCH 06/54] Add helpers for working with StandardStream. --- .../StandardStreamTest.cs | 56 ++++++++ .../Properties/Resources.Designer.cs | 9 ++ .../Properties/Resources.resx | 3 + .../Terminal/StandardStreamExtensions.cs | 134 ++++++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 src/Ookii.CommandLine.Tests/StandardStreamTest.cs create mode 100644 src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs diff --git a/src/Ookii.CommandLine.Tests/StandardStreamTest.cs b/src/Ookii.CommandLine.Tests/StandardStreamTest.cs new file mode 100644 index 0000000..b6c1def --- /dev/null +++ b/src/Ookii.CommandLine.Tests/StandardStreamTest.cs @@ -0,0 +1,56 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Ookii.CommandLine.Terminal; +using System; +using System.IO; + +namespace Ookii.CommandLine.Tests; + +[TestClass] +public class StandardStreamTest +{ + [TestMethod] + public void TestGetWriter() + { + Assert.AreSame(Console.Out, StandardStream.Output.GetWriter()); + Assert.AreSame(Console.Error, StandardStream.Error.GetWriter()); + Assert.ThrowsException(() => StandardStream.Input.GetWriter()); + } + + [TestMethod] + public void TestOpenStream() + { + using var output = StandardStream.Output.OpenStream(); + using var error = StandardStream.Error.OpenStream(); + using var input = StandardStream.Input.OpenStream(); + Assert.AreNotSame(output, input); + Assert.AreNotSame(output, error); + Assert.AreNotSame(error, input); + } + + [TestMethod] + public void TestGetStandardStream() + { + Assert.AreEqual(StandardStream.Output, Console.Out.GetStandardStream()); + Assert.AreEqual(StandardStream.Error, Console.Error.GetStandardStream()); + Assert.AreEqual(StandardStream.Input, Console.In.GetStandardStream()); + using (var writer = new StringWriter()) + { + Assert.IsNull(writer.GetStandardStream()); + } + + using (var writer = LineWrappingTextWriter.ForConsoleOut()) + { + Assert.AreEqual(StandardStream.Output, writer.GetStandardStream()); + } + + using (var writer = LineWrappingTextWriter.ForStringWriter()) + { + Assert.IsNull(writer.GetStandardStream()); + } + + using (var reader = new StringReader("foo")) + { + Assert.IsNull(reader.GetStandardStream()); + } + } +} diff --git a/src/Ookii.CommandLine/Properties/Resources.Designer.cs b/src/Ookii.CommandLine/Properties/Resources.Designer.cs index 5f491e3..db9fc38 100644 --- a/src/Ookii.CommandLine/Properties/Resources.Designer.cs +++ b/src/Ookii.CommandLine/Properties/Resources.Designer.cs @@ -411,6 +411,15 @@ internal static string InvalidStandardStream { } } + /// + /// Looks up a localized string similar to Invalid StandardStream value.. + /// + internal static string InvalidStandardStreamError { + get { + return ResourceManager.GetString("InvalidStandardStreamError", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid value for the StringComparison enumeration.. /// diff --git a/src/Ookii.CommandLine/Properties/Resources.resx b/src/Ookii.CommandLine/Properties/Resources.resx index ac855b2..43987fd 100644 --- a/src/Ookii.CommandLine/Properties/Resources.resx +++ b/src/Ookii.CommandLine/Properties/Resources.resx @@ -408,4 +408,7 @@ The type '{0}' is not an enumeration type. + + Invalid StandardStream value. + \ No newline at end of file diff --git a/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs new file mode 100644 index 0000000..604b44b --- /dev/null +++ b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; + +namespace Ookii.CommandLine.Terminal; + +/// +/// Provides extension methods for the enumeration. +/// +/// +public static class StandardStreamExtensions +{ + /// + /// Gets the for either + /// or . + /// + /// A value. + /// + /// The value of either or + /// . + /// + /// + /// was a value other than + /// or . + /// + /// + /// + /// The returned should not be disposed by the caller. + /// + /// + public static TextWriter GetWriter(this StandardStream stream) + { + return stream switch + { + StandardStream.Output => Console.Out, + StandardStream.Error => Console.Error, + _ => throw new ArgumentException(Properties.Resources.InvalidStandardStreamError, nameof(stream)), + }; + } + + /// + /// Gets the for either + /// or . + /// + /// A value. + /// + /// The value of either or + /// . + /// + /// + /// was a value other than + /// or . + /// + public static Stream OpenStream(this StandardStream stream) + { + return stream switch + { + StandardStream.Output => Console.OpenStandardOutput(), + StandardStream.Error => Console.OpenStandardError(), + StandardStream.Input => Console.OpenStandardInput(), + _ => throw new ArgumentException(Properties.Resources.InvalidStandardStreamError, nameof(stream)), + }; + } + + /// + /// Gets the associated with a if that + /// writer is for either the standard output or error stream. + /// + /// The . + /// + /// The that is writing to, or + /// if it's not writing to either the standard output or standard error + /// stream. + /// + /// + /// is . + /// + /// + /// + /// If is an instance of the + /// class, the will be + /// checked. + /// + /// + public static StandardStream? GetStandardStream(this TextWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (writer is LineWrappingTextWriter lwtw) + { + writer = lwtw.BaseWriter; + } + + if (writer == Console.Out) + { + return StandardStream.Output; + } + else if (writer == Console.Error) + { + return StandardStream.Error; + } + + return null; + } + + /// + /// Gets the associated with a if that + /// reader is for the standard input stream. + /// + /// The . + /// + /// The that is reader from, or + /// if it's not reader from the standard input stream. + /// + /// + /// is . + /// + public static StandardStream? GetStandardStream(this TextReader reader) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + if (reader == Console.In) + { + return StandardStream.Input; + } + + return null; + } +} From 7ddc7431a105a0e72f920c7f56fdad9f7cb37787 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 13:36:55 -0800 Subject: [PATCH 07/54] Add helper method to write formatted messages to the console. --- .../Terminal/VirtualTerminal.cs | 69 +++++++++++++++ src/Samples/NestedCommands/BaseCommand.cs | 23 +---- src/Samples/Subcommand/Program.cs | 84 +++++++------------ src/Samples/Subcommand/ReadCommand.cs | 5 +- src/Samples/Subcommand/WriteCommand.cs | 7 +- 5 files changed, 106 insertions(+), 82 deletions(-) diff --git a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs index 458d091..9db977d 100644 --- a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs +++ b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs @@ -1,6 +1,7 @@ using Microsoft.Win32.SafeHandles; using System; using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using Windows.Win32; using Windows.Win32.System.Console; @@ -115,6 +116,57 @@ public static VirtualTerminalSupport EnableColor(StandardStream stream) return EnableVirtualTerminalSequences(stream); } + /// + /// Writes a line to the standard output stream which, if virtual terminal sequences are + /// supported, will use the specified formatting. + /// + /// The text to write. + /// The formatting that should be applied to the text. + /// + /// The VT sequence that should be used to undo the formatting, or to + /// use . + /// + /// + /// + /// This method takes care of checking whether VT sequences are supported by using the + /// method, and on Windows, will reset the console mode afterwards + /// if needed. + /// + /// + /// The and parameters will be ignored + /// if the + /// + /// + public static void WriteLineFormatted(string text, TextFormat textFormat, TextFormat? reset = null) + => WriteLineFormatted(StandardStream.Output, Console.Out, text, textFormat, reset ?? TextFormat.Default); + + /// + /// Writes a line to the standard error stream which, if virtual terminal sequences are + /// supported, will use the specified formatting. + /// + /// The text to write. + /// + /// The formatting that should be applied to the text, or to use + /// . + /// + /// + /// The VT sequence that should be used to undo the formatting, or to + /// use . + /// + /// + /// + /// This method takes care of checking whether VT sequences are supported by using the + /// method, and on Windows, will reset the console mode afterwards + /// if needed. + /// + /// + /// The and parameters will be ignored + /// if the + /// + /// + public static void WriteLineErrorFormatted(string text, TextFormat? textFormat = null, TextFormat? reset = null) + => WriteLineFormatted(StandardStream.Error, Console.Error, text, textFormat ?? TextFormat.ForegroundRed, reset ?? TextFormat.Default); + // Returns the index of the character after the end of the sequence. internal static int FindSequenceEnd(ReadOnlySpan value, ref StringSegmentType type) { @@ -140,6 +192,23 @@ internal static int FindSequenceEnd(ReadOnlySpan value, ref StringSegmentT }; } + private static void WriteLineFormatted(StandardStream stream, TextWriter writer, string text, TextFormat textFormat, TextFormat reset) + { + using var support = EnableColor(stream); + if (support.IsSupported) + { + writer.Write(textFormat); + } + + writer.Write(text); + if (support.IsSupported) + { + writer.Write(reset); + } + + writer.WriteLine(); + } + private static int FindCsiEnd(ReadOnlySpan value, ref StringSegmentType type) { int result = FindCsiEndPartial(value.Slice(1), ref type); diff --git a/src/Samples/NestedCommands/BaseCommand.cs b/src/Samples/NestedCommands/BaseCommand.cs index ca529ec..512d201 100644 --- a/src/Samples/NestedCommands/BaseCommand.cs +++ b/src/Samples/NestedCommands/BaseCommand.cs @@ -24,35 +24,16 @@ public override async Task RunAsync() } catch (IOException ex) { - WriteErrorMessage(ex.Message); + VirtualTerminal.WriteLineErrorFormatted(ex.Message); return (int)ExitCode.IOError; } catch (UnauthorizedAccessException ex) { - WriteErrorMessage(ex.Message); + VirtualTerminal.WriteLineErrorFormatted(ex.Message); return (int)ExitCode.IOError; } } // Derived classes will implement this instead of the normal RunAsync. protected abstract Task RunAsync(Database db); - - // Helper method to print error messages. - private static void WriteErrorMessage(string message) - { - using var support = VirtualTerminal.EnableColor(StandardStream.Error); - using var writer = LineWrappingTextWriter.ForConsoleError(); - - // Add some color if we can. - if (support.IsSupported) - { - writer.Write(TextFormat.ForegroundRed); - } - - writer.WriteLine(message); - if (support.IsSupported) - { - writer.Write(TextFormat.Default); - } - } } diff --git a/src/Samples/Subcommand/Program.cs b/src/Samples/Subcommand/Program.cs index 09a07ee..ca14134 100644 --- a/src/Samples/Subcommand/Program.cs +++ b/src/Samples/Subcommand/Program.cs @@ -1,65 +1,37 @@ using Ookii.CommandLine; using Ookii.CommandLine.Commands; -using Ookii.CommandLine.Terminal; +using SubcommandSample; // For an application using subcommands, set the friendly name used for the automatic version // command by using this attribute on the assembly rather than an arguments type. +// You can also use the property in the .csproj file. [assembly: ApplicationFriendlyName("Ookii.CommandLine Subcommand Sample")] -namespace SubcommandSample; - -static class Program +// You can use the CommandOptions class to customize the parsing behavior and usage help +// output. CommandOptions inherits from ParseOptions so it supports all the same options. +var options = new CommandOptions() { - // No need to use the Main(string[] args) overload (though you can if you want), because - // CommandManager can take the arguments directly from Environment.GetCommandLineArgs(). - static async Task Main() + // Set options so the command names are determined by the class name, transformed to + // dash-case and with the "Command" suffix stripped. + CommandNameTransform = NameTransform.DashCase, + UsageWriter = new UsageWriter() { - // You can use the CommandOptions class to customize the parsing behavior and usage help - // output. CommandOptions inherits from ParseOptions so it supports all the same options. - var options = new CommandOptions() - { - // Set options so the command names are determined by the class name, transformed to - // dash-case and with the "Command" suffix stripped. - CommandNameTransform = NameTransform.DashCase, - UsageWriter = new UsageWriter() - { - // Show the application description before the command list. - IncludeApplicationDescriptionBeforeCommandList = true, - }, - }; - - // Create a CommandManager for the commands in the current assembly. We use the manager we - // defined to use source generation, which allows trimming even when using commands. - // - // In addition to our commands, it will also have an automatic "version" command (this can - // be disabled with the options). - var manager = new GeneratedManager(options); - - // Run the command indicated in the first argument to this application, and use the return - // value of its Run method as the application exit code. If the command could not be - // created, we return an error code. - // - // We use the async version because our commands use the IAsyncCommand interface. Note that - // you can use this method even if not all of your commands use IAsyncCommand. - return await manager.RunCommandAsync() ?? (int)ExitCode.CreateCommandFailure; - } - - // Utility method used by the commands to write exception messages to the console. - public static void WriteErrorMessage(string message) - { - using var support = VirtualTerminal.EnableColor(StandardStream.Error); - using var writer = LineWrappingTextWriter.ForConsoleError(); - - // Add some color if we can. - if (support.IsSupported) - { - writer.Write(TextFormat.ForegroundRed); - } - - writer.WriteLine(message); - if (support.IsSupported) - { - writer.Write(TextFormat.Default); - } - } -} + // Show the application description before the command list. + IncludeApplicationDescriptionBeforeCommandList = true, + }, +}; + +// Create a CommandManager for the commands in the current assembly. We use the manager we +// defined to use source generation, which allows trimming even when using commands. +// +// In addition to our commands, it will also have an automatic "version" command (this can +// be disabled with the options). +var manager = new GeneratedManager(options); + +// Run the command indicated in the first argument to this application, and use the return +// value of its Run method as the application exit code. If the command could not be +// created, we return an error code. +// +// We use the async version because our commands use the IAsyncCommand interface. Note that +// you can use this method even if not all of your commands use IAsyncCommand. +return await manager.RunCommandAsync() ?? (int)ExitCode.CreateCommandFailure; diff --git a/src/Samples/Subcommand/ReadCommand.cs b/src/Samples/Subcommand/ReadCommand.cs index c7f3e86..af63dc2 100644 --- a/src/Samples/Subcommand/ReadCommand.cs +++ b/src/Samples/Subcommand/ReadCommand.cs @@ -1,6 +1,7 @@ using Ookii.CommandLine; using Ookii.CommandLine.Commands; using Ookii.CommandLine.Conversion; +using Ookii.CommandLine.Terminal; using System.ComponentModel; using System.Text; @@ -58,12 +59,12 @@ public override async Task RunAsync() } catch (IOException ex) { - Program.WriteErrorMessage(ex.Message); + VirtualTerminal.WriteLineErrorFormatted(ex.Message); return (int)ExitCode.ReadWriteFailure; } catch (UnauthorizedAccessException ex) { - Program.WriteErrorMessage(ex.Message); + VirtualTerminal.WriteLineErrorFormatted(ex.Message); return (int)ExitCode.ReadWriteFailure; } } diff --git a/src/Samples/Subcommand/WriteCommand.cs b/src/Samples/Subcommand/WriteCommand.cs index 714433b..90e7916 100644 --- a/src/Samples/Subcommand/WriteCommand.cs +++ b/src/Samples/Subcommand/WriteCommand.cs @@ -1,6 +1,7 @@ using Ookii.CommandLine; using Ookii.CommandLine.Commands; using Ookii.CommandLine.Conversion; +using Ookii.CommandLine.Terminal; using Ookii.CommandLine.Validation; using System.ComponentModel; using System.Text; @@ -66,7 +67,7 @@ public override async Task RunAsync() // The Main method will return the exit status to the operating system. The numbers // are made up for the sample, they don't mean anything. Usually, 0 means success, // and any other value indicates an error. - Program.WriteErrorMessage("File already exists."); + VirtualTerminal.WriteLineErrorFormatted("File already exists."); return (int)ExitCode.FileExists; } @@ -93,12 +94,12 @@ public override async Task RunAsync() } catch (IOException ex) { - Program.WriteErrorMessage(ex.Message); + VirtualTerminal.WriteLineErrorFormatted(ex.Message); return (int)ExitCode.ReadWriteFailure; } catch (UnauthorizedAccessException ex) { - Program.WriteErrorMessage(ex.Message); + VirtualTerminal.WriteLineErrorFormatted(ex.Message); return (int)ExitCode.ReadWriteFailure; } } From dd1220502f1c92359fcb618db9bb8976bac53ef8 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 13:44:04 -0800 Subject: [PATCH 08/54] Add StandardStreamExtensions.IsRedirected(). --- .../Terminal/StandardStreamExtensions.cs | 19 +++++++++++++++++++ .../Terminal/VirtualTerminal.cs | 8 +------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs index 604b44b..0cb7fc4 100644 --- a/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs +++ b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs @@ -105,6 +105,25 @@ public static Stream OpenStream(this StandardStream stream) return null; } + /// + /// Gets a value that indicates whether the specified standard stream is redirected. + /// + /// The value. + /// + /// if the standard stream indicated by is + /// redirected; otherwise, . + /// + public static bool IsRedirected(this StandardStream stream) + { + return stream switch + { + StandardStream.Output => Console.IsOutputRedirected, + StandardStream.Error => Console.IsErrorRedirected, + StandardStream.Input => Console.IsInputRedirected, + _ => false, + }; + } + /// /// Gets the associated with a if that /// reader is for the standard input stream. diff --git a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs index 9db977d..e12c505 100644 --- a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs +++ b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs @@ -53,13 +53,7 @@ public static class VirtualTerminal /// public static VirtualTerminalSupport EnableVirtualTerminalSequences(StandardStream stream) { - bool supported = stream switch - { - StandardStream.Output => !Console.IsOutputRedirected, - StandardStream.Error => !Console.IsErrorRedirected, - _ => false, - }; - + bool supported = stream != StandardStream.Input && !stream.IsRedirected(); if (!supported) { return new VirtualTerminalSupport(false); From fa7e0dd9e2ff0bd6fa5ce035afec84668d76832d Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 14:44:12 -0800 Subject: [PATCH 09/54] Add LineWrappingTextWriter.IndentAfterBlankLine. --- .../LineWrappingTextWriterTest.Constants.cs | 103 ++++++++++++++++++ .../LineWrappingTextWriterTest.cs | 36 +++++- src/Ookii.CommandLine/Convert-SyncMethod.ps1 | 18 +-- .../LineWrappingTextWriter.Async.cs | 2 +- .../LineWrappingTextWriter.cs | 26 ++++- 5 files changed, 169 insertions(+), 16 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs index e2c5b0d..49e580d 100644 --- a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs +++ b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs @@ -232,4 +232,107 @@ dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in 6789012345678901234567890123456789012345678901234567890123456789012345678901 234567890123456789012345678901234567890123456789".ReplaceLineEndings(); + private static readonly string _expectedIndentAfterEmptyLine = @" +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique + risus nec feugiat in fermentum. + +Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum + consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. + Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae + sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae + elementum curabitur. + +Lorem + 0123456789012345678901234567890123456789012345678901234567890123456789012345 + 6789012345678901234567890123456789012345678901234567890123456789012345678901 + 234567890123456789012345678901234567890123456789 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing + tristique risus nec feugiat in fermentum. + + Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum + consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. + Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae + sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae + elementum curabitur. + + Lorem + 0123456789012345678901234567890123456789012345678901234567890123456789012345 + 6789012345678901234567890123456789012345678901234567890123456789012345678901 + 234567890123456789012345678901234567890123456789 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique + risus nec feugiat in fermentum. + +Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum + consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. + Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae + sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae + elementum curabitur. + +Lorem + 0123456789012345678901234567890123456789012345678901234567890123456789012345 + 6789012345678901234567890123456789012345678901234567890123456789012345678901 + 234567890123456789012345678901234567890123456789 +".ReplaceLineEndings(); + + private static readonly string _expectedIndentAfterEmptyLineNoLimit = @" +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. + +Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae elementum curabitur. + +Lorem 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. + + Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae elementum curabitur. + + Lorem 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. + +Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae elementum curabitur. + +Lorem 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +".ReplaceLineEndings(); + } diff --git a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs index 6ae81a9..01a7e72 100644 --- a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs +++ b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs @@ -408,8 +408,8 @@ public void TestToString() [TestMethod] public void TestWrappingMode() { + using (var writer = LineWrappingTextWriter.ForStringWriter(80)) { - using var writer = LineWrappingTextWriter.ForStringWriter(80); writer.Indent = 4; writer.WriteLine(_inputWrappingMode); writer.Wrapping = WrappingMode.Disabled; @@ -420,8 +420,8 @@ public void TestWrappingMode() } // Make sure the buffer is cleared if not empty. + using (var writer = LineWrappingTextWriter.ForStringWriter(80)) { - using var writer = LineWrappingTextWriter.ForStringWriter(80); writer.Indent = 4; writer.Write(_inputWrappingMode); writer.Wrapping = WrappingMode.Disabled; @@ -432,8 +432,8 @@ public void TestWrappingMode() } // Test EnabledNoForce + using (var writer = LineWrappingTextWriter.ForStringWriter(80)) { - using var writer = LineWrappingTextWriter.ForStringWriter(80); writer.Indent = 4; writer.Wrapping = WrappingMode.EnabledNoForce; writer.Write(_inputWrappingMode); @@ -443,15 +443,41 @@ public void TestWrappingMode() Assert.AreEqual(_expectedWrappingModeNoForce, writer.ToString()); } - // Should be false and unchangeable if no maximum length. + // Should be disabled and unchangeable if no maximum length. + using (var writer = LineWrappingTextWriter.ForStringWriter()) { - using var writer = LineWrappingTextWriter.ForStringWriter(); Assert.AreEqual(WrappingMode.Disabled, writer.Wrapping); writer.Wrapping = WrappingMode.Enabled; Assert.AreEqual(WrappingMode.Disabled, writer.Wrapping); } } + [TestMethod] + public void TestIndentAfterEmptyLine() + { + using var writer = LineWrappingTextWriter.ForStringWriter(80); + writer.Indent = 4; + writer.WriteLine(_input); + writer.IndentAfterEmptyLine = true; + writer.WriteLine(_input); + writer.IndentAfterEmptyLine = false; + writer.WriteLine(_input); + Assert.AreEqual(_expectedIndentAfterEmptyLine, writer.ToString()); + } + + [TestMethod] + public void TestIndentAfterEmptyLineNoLimit() + { + using var writer = LineWrappingTextWriter.ForStringWriter(); + writer.Indent = 4; + writer.WriteLine(_input); + writer.IndentAfterEmptyLine = true; + writer.WriteLine(_input); + writer.IndentAfterEmptyLine = false; + writer.WriteLine(_input); + Assert.AreEqual(_expectedIndentAfterEmptyLineNoLimit, writer.ToString()); + } + [TestMethod] public void TestExactLineLength() { diff --git a/src/Ookii.CommandLine/Convert-SyncMethod.ps1 b/src/Ookii.CommandLine/Convert-SyncMethod.ps1 index 064b005..2ae0ecf 100644 --- a/src/Ookii.CommandLine/Convert-SyncMethod.ps1 +++ b/src/Ookii.CommandLine/Convert-SyncMethod.ps1 @@ -23,13 +23,17 @@ $files = Get-Item $Path foreach ($file in $files) { $outputPath = Join-Path $OutputDir ($file.Name.Replace("Async", "Sync")) - Get-Content $file | ForEach-Object { - # Regex replace generic Task before the other replacements. - $result = ($_ -creplace 'async Task<(.*?)>','partial $1') - foreach ($item in $replacements) { - $result = $result.Replace($item[0], $item[1]) - } - $result + &{ + "// " + Get-Content $file | ForEach-Object { + # Regex replace generic Task before the other replacements. + $result = ($_ -creplace 'async Task<(.*?)>','partial $1') + foreach ($item in $replacements) { + $result = $result.Replace($item[0], $item[1]) + } + + $result + } } | Set-Content $outputPath } diff --git a/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs b/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs index 8bb0ede..ddf3079 100644 --- a/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs +++ b/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs @@ -254,7 +254,7 @@ await buffer.SplitAsync(true, async (type, span) => private async Task WriteLineBreakDirectAsync(CancellationToken cancellationToken) { await WriteBlankLineAsync(_baseWriter, cancellationToken); - _noWrappingState.IndentNextWrite = _noWrappingState.CurrentLineLength != 0; + _noWrappingState.IndentNextWrite = IndentAfterEmptyLine || _noWrappingState.CurrentLineLength != 0; _noWrappingState.CurrentLineLength = 0; } diff --git a/src/Ookii.CommandLine/LineWrappingTextWriter.cs b/src/Ookii.CommandLine/LineWrappingTextWriter.cs index d7a27f2..cc54e1d 100644 --- a/src/Ookii.CommandLine/LineWrappingTextWriter.cs +++ b/src/Ookii.CommandLine/LineWrappingTextWriter.cs @@ -106,12 +106,14 @@ private ref struct BreakLineResult private partial class LineBuffer { + private readonly LineWrappingTextWriter _writer; private readonly RingBuffer _buffer; private readonly List _segments = new(); private bool _hasOverflow; - public LineBuffer(int capacity) + public LineBuffer(LineWrappingTextWriter writer, int capacity) { + _writer = writer; _buffer = new(capacity); } @@ -260,7 +262,7 @@ public void ClearCurrentLine(int indent, bool clearSegments = true) _segments.Clear(); } - if (!IsContentEmpty) + if (_writer.IndentAfterEmptyLine || !IsContentEmpty) { Indentation = indent; } @@ -337,7 +339,7 @@ public LineWrappingTextWriter(TextWriter baseWriter, int maximumLineLength, bool if (_maximumLineLength > 0) { // Add some slack for formatting characters. - _lineBuffer = new(countFormatting ? _maximumLineLength : _maximumLineLength * 2); + _lineBuffer = new(this, countFormatting ? _maximumLineLength : _maximumLineLength * 2); } } @@ -420,6 +422,24 @@ public int Indent } } + /// + /// Gets or sets a value which indicates whether a line after an empty line should have + /// indentation. + /// + /// + /// if a line after am empty line should be indented; otherwise, + /// . The default value is . + /// + /// + /// + /// By default, the class will start lines that follow + /// an empty line at the beginning of the line, regardless of the value of the + /// property. Set this property to to apply + /// indentation even to lines following an empty line. + /// + /// + public bool IndentAfterEmptyLine { get; set; } + /// /// Gets or sets a value which indicates how to wrap lines at the maximum line length. /// From 4af33321fbfda60773e27384d55440682ab45741 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 15:47:30 -0800 Subject: [PATCH 10/54] Add UsageWriter.IndentAfterEmptyLine property. --- src/Ookii.CommandLine.Tests/ArgumentTypes.cs | 8 +++++ .../CommandLineParserTest.Usage.cs | 31 +++++++++++++++++++ .../CommandLineParserTest.cs | 21 +++++++++++++ src/Ookii.CommandLine/UsageWriter.cs | 24 ++++++++++++++ 4 files changed, 84 insertions(+) diff --git a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs index e86fa9e..9013fe7 100644 --- a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs +++ b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs @@ -689,3 +689,11 @@ partial class AutoPositionArguments : AutoPositionArgumentsBase [CommandLineArgument] public int Arg3 { get; set; } } + +[GeneratedParser] +partial class EmptyLineDescriptionArguments +{ + [CommandLineArgument] + [Description("A description with\n\na blank line.")] + public string Argument { get; set; } +} diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs index 2d1cfdc..b064d5b 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs @@ -668,4 +668,35 @@ Displays this help message. Displays version information. ".ReplaceLineEndings(); + + private static readonly string _expectedEmptyLineDefaultUsage = @"Usage: test [-Argument ] [-Help] [-Version] + + -Argument + A description with + +a blank line. + + -Help [] (-?, -h) + Displays this help message. + + -Version [] + Displays version information. + +".ReplaceLineEndings(); + + private static readonly string _expectedEmptyLineIndentAfterBlankLineUsage = @"Usage: test [-Argument ] [-Help] [-Version] + + -Argument + A description with + + a blank line. + + -Help [] (-?, -h) + Displays this help message. + + -Version [] + Displays version information. + +".ReplaceLineEndings(); + } diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index 0b9b2be..b45e9e5 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -459,6 +459,27 @@ public void TestWriteUsageCustomIndent(ProviderKind kind) Assert.AreEqual(_expectedCustomIndentUsage, actual); } + [TestMethod] + [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] + public void TestWriteUsageIndentAfterBlankLine(ProviderKind kind) + { + var options = new ParseOptions() + { + UsageWriter = new UsageWriter() + { + ExecutableName = _executableName, + } + }; + + var target = CreateParser(kind, options); + string actual = target.GetUsage(options.UsageWriter); + Assert.AreEqual(_expectedEmptyLineDefaultUsage, actual); + + options.UsageWriter.IndentAfterEmptyLine = true; + actual = target.GetUsage(options.UsageWriter); + Assert.AreEqual(_expectedEmptyLineIndentAfterBlankLineUsage, actual); + } + [TestMethod] [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] public void TestStaticParse(ProviderKind kind) diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index 02e4e6c..a2d1c24 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -481,6 +481,29 @@ public bool IncludeExecutableExtension /// public string? CommandName { get; set; } + /// + /// Gets or sets a value which indicates whether a line after an empty line should have + /// indentation. + /// + /// + /// if a line after am empty line should be indented; otherwise, + /// . The default value is . + /// + /// + /// + /// By default, the class will start lines that follow an empty line + /// at the beginning of the line, regardless of the value of the , + /// , or + /// property. Set this property to to apply indentation even to lines + /// following an empty line. + /// + /// + /// This can be useful if you have argument descriptions that contain blank lines when + /// argument descriptions are indented, such as in the default format. + /// + /// + public bool IndentAfterEmptyLine { get; set; } + /// /// Gets or sets a value that indicates whether the usage help should use color. /// @@ -2195,6 +2218,7 @@ private void RunOperation(UsageHelpRequest request) { try { + Writer.IndentAfterEmptyLine = IndentAfterEmptyLine; if (_parser == null) { WriteCommandListUsageCore(); From 0a5376c12a154c6c8e4824db54c65c1809cc1b3f Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 16:04:39 -0800 Subject: [PATCH 11/54] Simplify some state management. --- src/Ookii.CommandLine/CommandLineParser.cs | 35 +++++-------- src/Ookii.CommandLine/ParseOptions.cs | 25 ++++++--- src/Ookii.CommandLine/UsageWriter.cs | 59 +++++++--------------- 3 files changed, 50 insertions(+), 69 deletions(-) diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index 8cceac9..e2ab8ff 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -1292,33 +1292,22 @@ internal static bool ShouldIndent(LineWrappingTextWriter writer) internal static void WriteError(ParseOptions options, string message, TextFormat color, bool blankLine = false) { using var errorVtSupport = options.EnableErrorColor(); - try + using var error = DisposableWrapper.Create(options.Error, LineWrappingTextWriter.ForConsoleError); + if (errorVtSupport.IsSupported) { - using var error = DisposableWrapper.Create(options.Error, LineWrappingTextWriter.ForConsoleError); - if (options.UseErrorColor ?? false) - { - error.Inner.Write(color); - } - - error.Inner.Write(message); - if (options.UseErrorColor ?? false) - { - error.Inner.Write(options.UsageWriter.ColorReset); - } + error.Inner.Write(color); + } - error.Inner.WriteLine(); - if (blankLine) - { - error.Inner.WriteLine(); - } + error.Inner.Write(message); + if (errorVtSupport.IsSupported) + { + error.Inner.Write(options.UsageWriter.ColorReset); } - finally + + error.Inner.WriteLine(); + if (blankLine) { - // Reset UseErrorColor if it was changed. - if (errorVtSupport != null) - { - options.UseErrorColor = null; - } + error.Inner.WriteLine(); } } diff --git a/src/Ookii.CommandLine/ParseOptions.cs b/src/Ookii.CommandLine/ParseOptions.cs index 8d2615f..fe7bdc2 100644 --- a/src/Ookii.CommandLine/ParseOptions.cs +++ b/src/Ookii.CommandLine/ParseOptions.cs @@ -809,15 +809,28 @@ public void Merge(ParseOptionsAttribute attribute) ValueDescriptionTransform ??= attribute.ValueDescriptionTransform; } - internal VirtualTerminalSupport? EnableErrorColor() + internal VirtualTerminalSupport EnableErrorColor() { - if (Error == null && UseErrorColor == null) + if (UseErrorColor is bool useErrorColor) { - var support = VirtualTerminal.EnableColor(StandardStream.Error); - UseErrorColor = support.IsSupported; - return support; + // Colors are forced on or off; don't change terminal mode but return the explicit + // support value. + return new VirtualTerminalSupport(useErrorColor); } - return null; + if (Error == null) + { + // Enable for stderr if no custom error writer. + return VirtualTerminal.EnableColor(StandardStream.Error); + } + + if (Error?.GetStandardStream() is StandardStream stream) + { + // Try to enable it for the std stream associated with the custom writer. + return new VirtualTerminalSupport(stream); + } + + // No std stream, no automatic color. + return new VirtualTerminalSupport(false); } } diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index a2d1c24..8848e00 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -96,8 +96,10 @@ protected enum Operation private const char OptionalStart = '['; private const char OptionalEnd = ']'; + private readonly LineWrappingTextWriter? _customWriter; private LineWrappingTextWriter? _writer; - private bool? _useColor; + private readonly bool? _useColor; + private bool _autoColor; private CommandLineParser? _parser; private CommandManager? _commandManager; private string? _executableName; @@ -127,7 +129,7 @@ protected enum Operation /// public UsageWriter(LineWrappingTextWriter? writer = null, bool? useColor = null) { - _writer = writer; + _customWriter = writer; _useColor = useColor; } @@ -510,7 +512,7 @@ public bool IncludeExecutableExtension /// /// to enable color output; otherwise, . /// - protected bool UseColor => _useColor ?? false; + protected bool UseColor => _useColor ?? _autoColor; /// /// Gets or sets the color applied by the base implementation of the @@ -2138,7 +2140,7 @@ private void WriteLine(string format, object? arg0, object? arg1, object? arg2) if (_useColor == null && _writer == null) { var support = VirtualTerminal.EnableColor(StandardStream.Output); - _useColor = support.IsSupported; + _autoColor = support.IsSupported; return support; } @@ -2172,46 +2174,21 @@ private int WriteAliasHelper(string prefix, IEnumerable? aliases, int coun private void WriteUsageInternal(UsageHelpRequest request = UsageHelpRequest.Full) { - bool restoreColor = _useColor == null; - bool restoreWriter = _writer == null; - try - { - using var support = EnableColor(); - using var writer = DisposableWrapper.Create(_writer, LineWrappingTextWriter.ForConsoleOut); - _writer = writer.Inner; - Writer.ResetIndent(); - Writer.Indent = 0; - RunOperation(request); - } - finally - { - if (restoreColor) - { - _useColor = null; - } - - if (restoreWriter) - { - _writer = null; - } - } + using var support = EnableColor(); + using var writer = DisposableWrapper.Create(_customWriter, LineWrappingTextWriter.ForConsoleOut); + _writer = writer.Inner; + Writer.ResetIndent(); + Writer.Indent = 0; + RunOperation(request); } private string GetUsageInternal(int maximumLineLength = 0, UsageHelpRequest request = UsageHelpRequest.Full) { - var originalWriter = _writer; - try - { - using var writer = LineWrappingTextWriter.ForStringWriter(maximumLineLength); - _writer = writer; - RunOperation(request); - writer.Flush(); - return writer.BaseWriter.ToString()!; - } - finally - { - _writer = originalWriter; - } + using var writer = LineWrappingTextWriter.ForStringWriter(maximumLineLength); + _writer = writer; + RunOperation(request); + writer.Flush(); + return writer.BaseWriter.ToString()!; } private void RunOperation(UsageHelpRequest request) @@ -2232,6 +2209,8 @@ private void RunOperation(UsageHelpRequest request) { _parser = null; _commandManager = null; + _writer = null; + _autoColor = false; } } From 19bc07936c46b98d99b319e42353ba8470e949d7 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 16:11:14 -0800 Subject: [PATCH 12/54] Remove unnecessary null check. --- src/Ookii.CommandLine/ParseOptions.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Ookii.CommandLine/ParseOptions.cs b/src/Ookii.CommandLine/ParseOptions.cs index fe7bdc2..8b23138 100644 --- a/src/Ookii.CommandLine/ParseOptions.cs +++ b/src/Ookii.CommandLine/ParseOptions.cs @@ -811,22 +811,22 @@ public void Merge(ParseOptionsAttribute attribute) internal VirtualTerminalSupport EnableErrorColor() { + // If colors are forced on or off; don't change terminal mode but return the explicit + // support value. if (UseErrorColor is bool useErrorColor) { - // Colors are forced on or off; don't change terminal mode but return the explicit - // support value. return new VirtualTerminalSupport(useErrorColor); } + // Enable for stderr if no custom error writer. if (Error == null) { - // Enable for stderr if no custom error writer. return VirtualTerminal.EnableColor(StandardStream.Error); } - if (Error?.GetStandardStream() is StandardStream stream) + // Try to enable it for the std stream associated with the custom writer. + if (Error.GetStandardStream() is StandardStream stream) { - // Try to enable it for the std stream associated with the custom writer. return new VirtualTerminalSupport(stream); } From a2e2e9b124781c39e909138cc9577541737a7536 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 1 Dec 2023 13:34:48 -0800 Subject: [PATCH 13/54] Improved auto color logic for usage help. --- src/Ookii.CommandLine/UsageWriter.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index 8848e00..0e3d820 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -2137,10 +2137,19 @@ private void WriteLine(string format, object? arg0, object? arg1, object? arg2) private VirtualTerminalSupport? EnableColor() { - if (_useColor == null && _writer == null) + if (_useColor == null) { - var support = VirtualTerminal.EnableColor(StandardStream.Output); - _autoColor = support.IsSupported; + VirtualTerminalSupport? support = null; + if (_customWriter == null) + { + support = VirtualTerminal.EnableColor(StandardStream.Output); + } + else if (_customWriter.GetStandardStream() is StandardStream stream) + { + support = VirtualTerminal.EnableColor(stream); + } + + _autoColor = support?.IsSupported ?? false; return support; } From 4839361510881191118a595aa7f7b1822a8c1758 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 30 Nov 2023 18:05:08 -0800 Subject: [PATCH 14/54] Support custom format strings for default values. --- .../Ookii.CommandLine.Tests.Commands.csproj | 4 +-- src/Ookii.CommandLine.Tests/ArgumentTypes.cs | 12 +++++++ .../CommandLineParserTest.Usage.cs | 32 +++++++++++++++++++ .../CommandLineParserTest.cs | 22 +++++++++++++ src/Ookii.CommandLine/CommandLineArgument.cs | 16 ++++++++++ .../CommandLineArgumentAttribute.cs | 19 +++++++++++ src/Ookii.CommandLine/UsageWriter.cs | 25 +++++++++++++-- 7 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/Ookii.CommandLine.Tests.Commands/Ookii.CommandLine.Tests.Commands.csproj b/src/Ookii.CommandLine.Tests.Commands/Ookii.CommandLine.Tests.Commands.csproj index bafddff..8ce6f07 100644 --- a/src/Ookii.CommandLine.Tests.Commands/Ookii.CommandLine.Tests.Commands.csproj +++ b/src/Ookii.CommandLine.Tests.Commands/Ookii.CommandLine.Tests.Commands.csproj @@ -1,10 +1,10 @@  - net7.0;net6.0;net48 + net8.0;net7.0;net6.0;net48 enable enable - 11.0 + 12.0 true ookii.snk false diff --git a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs index 9013fe7..9b7450f 100644 --- a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs +++ b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs @@ -697,3 +697,15 @@ partial class EmptyLineDescriptionArguments [Description("A description with\n\na blank line.")] public string Argument { get; set; } } + +[GeneratedParser] +partial class DefaultValueFormatArguments +{ + [CommandLineArgument(DefaultValue = 1.5, DefaultValueFormat = "({0:0.00})")] + [Description("An argument.")] + public double Argument { get; set; } + + [CommandLineArgument(DefaultValue = 3.5)] + [Description("Another argument.")] + public double Argument2 { get; set; } +} diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs index b064d5b..69e7c9d 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs @@ -697,6 +697,38 @@ Displays this help message. -Version [] Displays version information. +".ReplaceLineEndings(); + + private static readonly string _expectedDefaultValueFormatUsage = @"Usage: test [-Argument ] [-Argument2 ] [-Help] [-Version] + + -Argument + An argument. Default value: (1.50). + + -Argument2 + Another argument. Default value: 3.5. + + -Help [] (-?, -h) + Displays this help message. + + -Version [] + Displays version information. + +".ReplaceLineEndings(); + + private static readonly string _expectedDefaultValueFormatCultureUsage = @"Usage: test [-Argument ] [-Argument2 ] [-Help] [-Version] + + -Argument + An argument. Default value: (1,50). + + -Argument2 + Another argument. Default value: 3,5. + + -Help [] (-?, -h) + Displays this help message. + + -Version [] + Displays version information. + ".ReplaceLineEndings(); } diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index b45e9e5..fc4f749 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -480,6 +480,28 @@ public void TestWriteUsageIndentAfterBlankLine(ProviderKind kind) Assert.AreEqual(_expectedEmptyLineIndentAfterBlankLineUsage, actual); } + [TestMethod] + [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] + public void TestWriteUsageDefaultValueFormat(ProviderKind kind) + { + var options = new ParseOptions() + { + UsageWriter = new UsageWriter() + { + ExecutableName = _executableName, + } + }; + + var parser = CreateParser(kind, options); + string actual = parser.GetUsage(); + Assert.AreEqual(_expectedDefaultValueFormatUsage, actual); + + // Stream culture should be ignored for the default value in favor of the parser culture. + options.Culture = CultureInfo.GetCultureInfo("nl-NL"); + actual = parser.GetUsage(); + Assert.AreEqual(_expectedDefaultValueFormatCultureUsage, actual); + } + [TestMethod] [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] public void TestStaticParse(ProviderKind kind) diff --git a/src/Ookii.CommandLine/CommandLineArgument.cs b/src/Ookii.CommandLine/CommandLineArgument.cs index fe1df5f..7219877 100644 --- a/src/Ookii.CommandLine/CommandLineArgument.cs +++ b/src/Ookii.CommandLine/CommandLineArgument.cs @@ -292,6 +292,7 @@ internal struct ArgumentInfo public bool IsRequiredProperty { get; set; } public object? DefaultValue { get; set; } public bool IncludeDefaultValueInHelp { get; set; } + public string? DefaultValueFormat { get; set; } public string? Description { get; set; } public string? ValueDescription { get; set; } public bool AllowNull { get; set; } @@ -385,6 +386,7 @@ internal CommandLineArgument(ArgumentInfo info) _converter = info.Converter; _defaultValue = ConvertToArgumentTypeInvariant(info.DefaultValue); IncludeDefaultInUsageHelp = info.IncludeDefaultValueInHelp; + DefaultValueFormat = info.DefaultValueFormat; _valueDescription = info.ValueDescription; _allowNull = info.AllowNull; DictionaryInfo = info.DictionaryInfo; @@ -687,6 +689,19 @@ public object? DefaultValue get { return _defaultValue; } } + /// + /// Gets the compound formatting string that is used to format the default value for display in + /// the usage help. + /// + /// + /// A compound formatting string, or if the default format is used. + /// + /// +#if NET7_0_OR_GREATER + [StringSyntax(StringSyntaxAttribute.CompositeFormat)] +#endif + public string? DefaultValueFormat { get; } + /// /// Gets a value that indicates whether the default value should be included in the argument's /// description in the usage help. @@ -1165,6 +1180,7 @@ internal static ArgumentInfo CreateArgumentInfo(CommandLineParser parser, ShortAliases = GetShortAliases(shortAliasAttributes, argumentName), DefaultValue = attribute.DefaultValue, IncludeDefaultValueInHelp = attribute.IncludeDefaultInUsageHelp, + DefaultValueFormat = attribute.DefaultValueFormat, IsRequired = attribute.IsRequired || requiredProperty, IsRequiredProperty = requiredProperty, MemberName = memberName, diff --git a/src/Ookii.CommandLine/CommandLineArgumentAttribute.cs b/src/Ookii.CommandLine/CommandLineArgumentAttribute.cs index e5e9e80..26dece2 100644 --- a/src/Ookii.CommandLine/CommandLineArgumentAttribute.cs +++ b/src/Ookii.CommandLine/CommandLineArgumentAttribute.cs @@ -1,5 +1,6 @@ using Ookii.CommandLine.Commands; using System; +using System.Diagnostics.CodeAnalysis; namespace Ookii.CommandLine; @@ -315,6 +316,24 @@ public bool IsPositional /// public bool IncludeDefaultInUsageHelp { get; set; } = true; + /// + /// Gets or sets a compound formatting string that is used to format the default value for + /// display in the usage help. + /// + /// + /// A compound formatting string, or to use the default format. + /// + /// + /// + /// This value must be a compound formatting string containing exactly one placeholder, + /// e.g "0x{0:x}". + /// + /// +#if NET7_0_OR_GREATER + [StringSyntax(StringSyntaxAttribute.CompositeFormat)] +#endif + public string? DefaultValueFormat { get; set; } + /// /// Gets or sets a value that indicates whether argument parsing should be canceled if /// this argument is encountered. diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index 0e3d820..82f2bae 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -1462,7 +1462,15 @@ protected virtual void WriteArgumentDescriptionBody(CommandLineArgument argument if (IncludeDefaultValueInDescription && argument.IncludeDefaultInUsageHelp && argument.DefaultValue != null) { - WriteDefaultValue(argument.DefaultValue); + var defaultValue = argument.DefaultValue; + if (argument.DefaultValueFormat != null) + { + // Use the parser's culture so the format matches the format the user should use + // for values. + defaultValue = string.Format(argument.Parser.Culture, argument.DefaultValueFormat, defaultValue); + } + + WriteDefaultValue(defaultValue); } WriteLine(); @@ -1659,9 +1667,22 @@ protected virtual void WriteArgumentValidators(CommandLineArgument argument) /// and the property /// is not . /// + /// + /// If the + /// property for the argument is not , then the base implementation of + /// the method will use the formatted string, + /// rather than the original default value, for the + /// parameter. + /// + /// + /// The default implementation formats the argument using the culture specified by the + /// property, rather than the + /// culture used by the output , so that the displayed format will match + /// the format the user should use for argument values. + /// /// protected virtual void WriteDefaultValue(object defaultValue) - => Write(Resources.DefaultDefaultValueFormat, defaultValue); + => Write(string.Format(Parser.Culture, Resources.DefaultDefaultValueFormat, defaultValue)); /// /// Writes a message telling to user how to get more detailed help. From 7721d52ca9bd02686d49b83dc6e0b9319b41c03b Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 1 Dec 2023 15:15:06 -0800 Subject: [PATCH 15/54] Add separate singular and plural messages for ValidateCount. --- .../LocalizedStringProvider.Validators.cs | 12 +++++-- .../Properties/Resources.Designer.cs | 32 +++++++++++++++---- .../Properties/Resources.resx | 16 +++++++--- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/Ookii.CommandLine/LocalizedStringProvider.Validators.cs b/src/Ookii.CommandLine/LocalizedStringProvider.Validators.cs index 5128524..2d1f517 100644 --- a/src/Ookii.CommandLine/LocalizedStringProvider.Validators.cs +++ b/src/Ookii.CommandLine/LocalizedStringProvider.Validators.cs @@ -251,11 +251,19 @@ public virtual string ValidateCountFailed(string argumentName, ValidateCountAttr { if (attribute.Maximum == int.MaxValue) { - return Format(Resources.ValidateCountMinFormat, argumentName, attribute.Minimum); + var format = attribute.Minimum == 1 + ? Resources.ValidateCountMinSingularFormat + : Resources.ValidateCountMinPluralFormat; + + return Format(format, argumentName, attribute.Minimum); } else if (attribute.Minimum <= 0) { - return Format(Resources.ValidateCountMaxFormat, argumentName, attribute.Maximum); + var format = attribute.Maximum == 1 + ? Resources.ValidateCountMaxSingularFormat + : Resources.ValidateCountMaxPluralFormat; + + return Format(format, argumentName, attribute.Maximum); } else { diff --git a/src/Ookii.CommandLine/Properties/Resources.Designer.cs b/src/Ookii.CommandLine/Properties/Resources.Designer.cs index db9fc38..e07aa88 100644 --- a/src/Ookii.CommandLine/Properties/Resources.Designer.cs +++ b/src/Ookii.CommandLine/Properties/Resources.Designer.cs @@ -655,7 +655,7 @@ internal static string UsageWriterPropertyNotAvailable { } /// - /// Looks up a localized string similar to The argument '{0}' must have between {1} and {2} items.. + /// Looks up a localized string similar to The argument '{0}' must have between {1} and {2} values.. /// internal static string ValidateCountBothFormat { get { @@ -664,20 +664,38 @@ internal static string ValidateCountBothFormat { } /// - /// Looks up a localized string similar to The argument '{0}' must have at most {1} item(s).. + /// Looks up a localized string similar to The argument '{0}' must have at most {1} values.. /// - internal static string ValidateCountMaxFormat { + internal static string ValidateCountMaxPluralFormat { get { - return ResourceManager.GetString("ValidateCountMaxFormat", resourceCulture); + return ResourceManager.GetString("ValidateCountMaxPluralFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The argument '{0}' must have at least {1} item(s).. + /// Looks up a localized string similar to The argument '{0}' must have at most {1} value.. /// - internal static string ValidateCountMinFormat { + internal static string ValidateCountMaxSingularFormat { get { - return ResourceManager.GetString("ValidateCountMinFormat", resourceCulture); + return ResourceManager.GetString("ValidateCountMaxSingularFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument '{0}' must have at least {1} values.. + /// + internal static string ValidateCountMinPluralFormat { + get { + return ResourceManager.GetString("ValidateCountMinPluralFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument '{0}' must have at least {1} value.. + /// + internal static string ValidateCountMinSingularFormat { + get { + return ResourceManager.GetString("ValidateCountMinSingularFormat", resourceCulture); } } diff --git a/src/Ookii.CommandLine/Properties/Resources.resx b/src/Ookii.CommandLine/Properties/Resources.resx index 43987fd..d39a787 100644 --- a/src/Ookii.CommandLine/Properties/Resources.resx +++ b/src/Ookii.CommandLine/Properties/Resources.resx @@ -256,13 +256,13 @@ The 'minimum' and 'maximum' parameters cannot both be null. - The argument '{0}' must have between {1} and {2} items. + The argument '{0}' must have between {1} and {2} values. - - The argument '{0}' must have at most {1} item(s). + + The argument '{0}' must have at most {1} values. - - The argument '{0}' must have at least {1} item(s). + + The argument '{0}' must have at least {1} values. The argument '{0}' must not be empty. @@ -411,4 +411,10 @@ Invalid StandardStream value. + + The argument '{0}' must have at most {1} value. + + + The argument '{0}' must have at least {1} value. + \ No newline at end of file From 3b6a849a5a479a99c405c3add72cd0c6264c99d9 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 4 Dec 2023 16:10:56 -0800 Subject: [PATCH 16/54] Add localizable strings used by UsageWriter to LocalizedStringProvider. --- .../LocalizedStringProvider.Usage.cs | 64 +++++++++++++++++++ .../LocalizedStringProvider.cs | 11 ++++ src/Ookii.CommandLine/UsageWriter.cs | 42 ++++++++---- 3 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs diff --git a/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs b/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs new file mode 100644 index 0000000..ba19467 --- /dev/null +++ b/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs @@ -0,0 +1,64 @@ +using Ookii.CommandLine.Properties; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ookii.CommandLine; + +partial class LocalizedStringProvider +{ + /// + /// Gets the default prefix for usage syntax, used by the class. + /// + /// The string. + public virtual string UsageSyntaxPrefix() => Resources.DefaultUsagePrefix; + + /// + /// Gets the default suffix for usage syntax when creating command list usage help, used by the + /// class. + /// + /// The string. + public virtual string CommandUsageSuffix() => Resources.DefaultCommandUsageSuffix; + + /// + /// Gets the default suffix for usage syntax to indicate more arguments are available if the + /// syntax is abbreviated, used by the class. + /// + /// The string. + public virtual string UsageAbbreviatedRemainingArguments() => Resources.DefaultAbbreviatedRemainingArguments; + + /// + /// Gets the default header to print above the list of available commands, used by the + /// class. + /// + /// The string. + public virtual string UsageAvailableCommandsHeader() => Resources.DefaultAvailableCommandsHeader; + + /// + /// Gets the text to use to display a default value in the usage help, used by the + /// class. + /// + /// The argument's default value. + /// + /// An object that provides culture-specific format information for the default value. This + /// will be the value of the property, to ensure the + /// format used matches the format used when parsing arguments. + /// + /// The string. + public virtual string UsageDefaultValue(object defaultValue, IFormatProvider formatProvider) + => string.Format(formatProvider, Resources.DefaultDefaultValueFormat, defaultValue); + + /// + /// Gets a message telling the user how to get more detailed help, used by the + /// class. + /// + /// + /// The application's executable name, optionally including the command name. + /// + /// The name of the help argument, including prefix. + /// The string. + public virtual string UsageMoreInfoMessage(string executableName, string helpArgumentName) + => Format(Resources.MoreInfoOnErrorFormat, executableName, helpArgumentName); +} diff --git a/src/Ookii.CommandLine/LocalizedStringProvider.cs b/src/Ookii.CommandLine/LocalizedStringProvider.cs index b2ccec0..bc69a81 100644 --- a/src/Ookii.CommandLine/LocalizedStringProvider.cs +++ b/src/Ookii.CommandLine/LocalizedStringProvider.cs @@ -1,5 +1,6 @@ using Ookii.CommandLine.Commands; using Ookii.CommandLine.Properties; +using System; using System.Globalization; using System.Reflection; @@ -123,6 +124,16 @@ public virtual string ApplicationNameAndVersion(Assembly assembly, string friend public virtual string? ApplicationCopyright(Assembly assembly) => assembly.GetCustomAttribute()?.Copyright; + /// + /// Gets an instruction on how to get help on a command, used by the + /// class. + /// + /// The application and command name. + /// The argument name prefix for the help argument. + /// The help argument name. + public virtual string UsageCommandHelpInstruction(string name, string argumentNamePrefix, string argumentName) + => Format(Resources.CommandHelpInstructionFormat, name, argumentNamePrefix, argumentName); + private static string Format(string format, object? arg0) => string.Format(CultureInfo.CurrentCulture, format, arg0); diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index 82f2bae..9d956be 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -674,6 +674,9 @@ protected LineWrappingTextWriter Writer /// /// Gets the that usage is being written for. /// + /// + /// An instance of the class. + /// /// /// A operation is not in progress. /// @@ -681,14 +684,31 @@ protected CommandLineParser Parser => _parser ?? throw new InvalidOperationException(Resources.UsageWriterPropertyNotAvailable); /// - /// Gets the that usage is being written for. + /// Gets the that usage is being written for. /// + /// + /// An instance of the class. + /// /// /// A operation is not in progress. /// protected CommandManager CommandManager => _commandManager ?? throw new InvalidOperationException(Resources.UsageWriterPropertyNotAvailable); + /// + /// Gets the implementation used to get strings for + /// error messages and usage help. + /// + /// + /// An instance of a class inheriting from the class. + /// + /// + /// A operation is not in progress. + /// + protected LocalizedStringProvider StringProvider + => _parser?.StringProvider ?? _commandManager?.Options.StringProvider + ?? throw new InvalidOperationException(Resources.UsageWriterPropertyNotAvailable); + /// /// Indicates what operation is currently in progress. /// @@ -1020,7 +1040,7 @@ protected virtual void WriteParserUsageSyntax() protected virtual void WriteUsageSyntaxPrefix() { WriteColor(UsagePrefixColor); - Write(Resources.DefaultUsagePrefix); + Write(StringProvider.UsageSyntaxPrefix()); ResetColor(); Write(' '); Write(ExecutableName); @@ -1048,7 +1068,7 @@ protected virtual void WriteUsageSyntaxSuffix() { if (OperationInProgress == Operation.CommandListUsage) { - WriteLine(Resources.DefaultCommandUsageSuffix); + WriteLine(StringProvider.CommandUsageSuffix()); } } @@ -1220,7 +1240,7 @@ protected virtual void WriteValueDescription(string valueDescription) /// /// protected virtual void WriteAbbreviatedRemainingArguments() - => Write(Resources.DefaultAbbreviatedRemainingArguments); + => Write(StringProvider.UsageAbbreviatedRemainingArguments()); /// /// Writes a suffix that indicates an argument is a multi-value argument. @@ -1463,11 +1483,11 @@ protected virtual void WriteArgumentDescriptionBody(CommandLineArgument argument if (IncludeDefaultValueInDescription && argument.IncludeDefaultInUsageHelp && argument.DefaultValue != null) { var defaultValue = argument.DefaultValue; - if (argument.DefaultValueFormat != null) + if (argument.DefaultValueFormat != null) { // Use the parser's culture so the format matches the format the user should use - // for values. - defaultValue = string.Format(argument.Parser.Culture, argument.DefaultValueFormat, defaultValue); + // for values. + defaultValue = string.Format(argument.Parser.Culture, argument.DefaultValueFormat, defaultValue); } WriteDefaultValue(defaultValue); @@ -1682,7 +1702,7 @@ protected virtual void WriteArgumentValidators(CommandLineArgument argument) /// /// protected virtual void WriteDefaultValue(object defaultValue) - => Write(string.Format(Parser.Culture, Resources.DefaultDefaultValueFormat, defaultValue)); + => Write(StringProvider.UsageDefaultValue(defaultValue, Parser.Culture)); /// /// Writes a message telling to user how to get more detailed help. @@ -1712,7 +1732,7 @@ protected virtual void WriteMoreInfoMessage() name += " " + CommandName; } - WriteLine(Resources.MoreInfoOnErrorFormat, name, arg.ArgumentNameWithPrefix); + WriteLine(StringProvider.UsageMoreInfoMessage(name, arg.ArgumentNameWithPrefix)); } } @@ -1843,7 +1863,7 @@ protected virtual void WriteCommandListUsageSyntax() /// protected virtual void WriteAvailableCommandsHeader() { - WriteLine(Resources.DefaultAvailableCommandsHeader); + WriteLine(StringProvider.UsageAvailableCommandsHeader()); WriteLine(); } @@ -2021,7 +2041,7 @@ protected virtual void WriteCommandDescription(string description) /// protected virtual void WriteCommandHelpInstruction(string name, string argumentNamePrefix, string argumentName) { - WriteLine(Resources.CommandHelpInstructionFormat, name, argumentNamePrefix, argumentName); + WriteLine(StringProvider.UsageCommandHelpInstruction(name, argumentNamePrefix, argumentName)); } #endregion From bcd4591b17f8f6f57d9f6ac7eb7d0ad73c59a9ba Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 4 Dec 2023 16:35:20 -0800 Subject: [PATCH 17/54] Support adding a footer to the usage help. --- .../CommandLineParserTest.Usage.cs | 39 +++++++ .../CommandLineParserTest.cs | 17 +++ .../CustomUsageWriter.cs | 21 ++++ .../SubCommandTest.Usage.cs | 18 +++ src/Ookii.CommandLine.Tests/SubCommandTest.cs | 19 ++++ src/Ookii.CommandLine/UsageWriter.cs | 103 ++++++++++++------ 6 files changed, 181 insertions(+), 36 deletions(-) create mode 100644 src/Ookii.CommandLine.Tests/CustomUsageWriter.cs diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs index 69e7c9d..a445093 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs @@ -731,4 +731,43 @@ Displays version information. ".ReplaceLineEndings(); + private static readonly string _expectedFooterUsage = @"Test arguments description. + +Usage: test [-arg1] [[-other] ] [[-notSwitch] ] [[-Arg5] ] [[-other2] ] [[-Arg8] ...] -Arg6 [-Arg10...] [-Arg11] [-Arg12 ...] [-Arg13 ...] [-Arg14 ...] [-Arg15 >] [-Arg3 ] [-Arg7] [-Arg9 ] [-Help] [-Version] + + -arg1 + Arg1 description. + + -other + Arg2 description. Default value: 42. + + -notSwitch + Default value: False. + + -Arg5 + Arg5 description. + + -other2 + Arg4 description. Default value: 47. + + -Arg6 (-Alias1, -Alias2) + Arg6 description. + + -Arg12 + Default value: 42. + + -Arg7 [] (-Alias3) + + + -Arg9 + Must be between 0 and 100. + + -Help [] (-?, -h) + Displays this help message. + + -Version [] + Displays version information. + +This is a custom footer. +".ReplaceLineEndings(); } diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index fc4f749..af69b77 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -502,6 +502,23 @@ public void TestWriteUsageDefaultValueFormat(ProviderKind kind) Assert.AreEqual(_expectedDefaultValueFormatCultureUsage, actual); } + [TestMethod] + [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] + public void TestWriteUsageFooter(ProviderKind kind) + { + var options = new ParseOptions() + { + UsageWriter = new CustomUsageWriter() + { + ExecutableName = _executableName + }, + }; + + var target = CreateParser(kind, options); + string actual = target.GetUsage(); + Assert.AreEqual(_expectedFooterUsage, actual); + } + [TestMethod] [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] public void TestStaticParse(ProviderKind kind) diff --git a/src/Ookii.CommandLine.Tests/CustomUsageWriter.cs b/src/Ookii.CommandLine.Tests/CustomUsageWriter.cs new file mode 100644 index 0000000..7cb5b61 --- /dev/null +++ b/src/Ookii.CommandLine.Tests/CustomUsageWriter.cs @@ -0,0 +1,21 @@ +using System; + +namespace Ookii.CommandLine.Tests; + +internal class CustomUsageWriter : UsageWriter +{ + public CustomUsageWriter() { } + + public CustomUsageWriter(LineWrappingTextWriter writer) : base(writer) { } + + protected override void WriteParserUsageFooter() + { + WriteLine("This is a custom footer."); + } + + protected override void WriteCommandListUsageFooter() + { + base.WriteCommandListUsageFooter(); + WriteLine("This is the command list footer."); + } +} diff --git a/src/Ookii.CommandLine.Tests/SubCommandTest.Usage.cs b/src/Ookii.CommandLine.Tests/SubCommandTest.Usage.cs index f527172..5e19fa1 100644 --- a/src/Ookii.CommandLine.Tests/SubCommandTest.Usage.cs +++ b/src/Ookii.CommandLine.Tests/SubCommandTest.Usage.cs @@ -151,5 +151,23 @@ Other parent command description. -Help [] (-?, -h) Displays this help message. +".ReplaceLineEndings(); + + public static readonly string _expectedUsageFooter = @"Usage: test [arguments] + +The following commands are available: + + AnotherSimpleCommand, alias + + custom + Custom parsing command. + + test + Test command description. + + version + Displays version information. + +This is the command list footer. ".ReplaceLineEndings(); } diff --git a/src/Ookii.CommandLine.Tests/SubCommandTest.cs b/src/Ookii.CommandLine.Tests/SubCommandTest.cs index 6091d71..f020d27 100644 --- a/src/Ookii.CommandLine.Tests/SubCommandTest.cs +++ b/src/Ookii.CommandLine.Tests/SubCommandTest.cs @@ -251,6 +251,25 @@ public void TestWriteUsageApplicationDescription(ProviderKind kind) Assert.AreEqual(_expectedUsageWithDescription, writer.BaseWriter.ToString()); } + [TestMethod] + [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] + public void TestWriteUsageFooter(ProviderKind kind) + { + using var writer = LineWrappingTextWriter.ForStringWriter(0); + var options = new CommandOptions() + { + Error = writer, + UsageWriter = new CustomUsageWriter(writer) + { + ExecutableName = _executableName, + } + }; + + var manager = CreateManager(kind, options); + manager.WriteUsage(); + Assert.AreEqual(_expectedUsageFooter, writer.BaseWriter.ToString()); + } + [TestMethod] [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] public void TestCommandUsage(ProviderKind kind) diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index 9d956be..d5bb4e2 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -927,6 +927,7 @@ protected virtual void WriteParserUsageCore(UsageHelpRequest request) WriteArgumentDescriptions(); Writer.Indent = 0; + WriteParserUsageFooter(); } else { @@ -1736,6 +1737,24 @@ protected virtual void WriteMoreInfoMessage() } } + /// + /// Writes a footer under the usage help. + /// + /// + /// + /// This method is called by the base implementation of + /// only if the requested help is . + /// + /// + /// The base implementation does nothing; this function exists to allow derived classes to + /// easily add a footer to the help. + /// + /// + protected virtual void WriteParserUsageFooter() + { + // Nothing + } + /// /// Gets the parser's arguments filtered according to the /// property and sorted according to the property. @@ -1786,8 +1805,9 @@ protected virtual IEnumerable GetArgumentsInDescriptionOrde /// /// /// The base implementation writes the application description, followed by the list - /// of commands, followed by a message indicating how to get help on a command. Which - /// elements are included exactly can be influenced by the properties of this class. + /// of commands, followed by a footer, which may include a message indicating how to get help + /// on a command. Which elements are included exactly can be influenced by the properties of + /// this class. /// /// protected virtual void WriteCommandListUsageCore() @@ -1808,25 +1828,8 @@ protected virtual void WriteCommandListUsageCore() WriteAvailableCommandsHeader(); WriteCommandDescriptions(); - - if (CheckShowCommandHelpInstruction()) - { - var prefix = CommandManager.Options.Mode == ParsingMode.LongShort - ? (CommandManager.Options.LongArgumentNamePrefixOrDefault) - : (CommandManager.Options.ArgumentNamePrefixes?.FirstOrDefault() ?? CommandLineParser.GetDefaultArgumentNamePrefixes()[0]); - - var transform = CommandManager.Options.ArgumentNameTransformOrDefault; - var argumentName = transform.Apply(CommandManager.Options.StringProvider.AutomaticHelpName()); - - Writer.Indent = 0; - var name = ExecutableName; - if (CommandName != null) - { - name += " " + CommandName; - } - - WriteCommandHelpInstruction(name, prefix, argumentName); - } + Writer.Indent = 0; + WriteCommandListUsageFooter(); } /// @@ -2022,6 +2025,39 @@ protected virtual void WriteCommandAliases(IEnumerable aliases) protected virtual void WriteCommandDescription(string description) => Write(description); + /// + /// Writes a footer underneath the command list usage. + /// + /// + /// + /// The base implementation calls if the help + /// instruction is explicitly or automatically enabled. + /// + /// + /// This method is called by the base implementation of the + /// method. + /// + /// + protected virtual void WriteCommandListUsageFooter() + { + if (CheckShowCommandHelpInstruction()) + { + var prefix = CommandManager.Options.Mode == ParsingMode.LongShort + ? (CommandManager.Options.LongArgumentNamePrefixOrDefault) + : (CommandManager.Options.ArgumentNamePrefixes?.FirstOrDefault() ?? CommandLineParser.GetDefaultArgumentNamePrefixes()[0]); + + var transform = CommandManager.Options.ArgumentNameTransformOrDefault; + var argumentName = transform.Apply(CommandManager.Options.StringProvider.AutomaticHelpName()); + var name = ExecutableName; + if (CommandName != null) + { + name += " " + CommandName; + } + + WriteCommandHelpInstruction(name, prefix, argumentName); + } + } + /// /// Writes an instruction on how to get help on a command. /// @@ -2034,7 +2070,7 @@ protected virtual void WriteCommandDescription(string description) /// information on a command." /// /// - /// This method is called by the base implementation of the + /// This method is called by the base implementation of the /// method if the property is , /// or if it is and all commands meet the requirements. /// @@ -2100,6 +2136,15 @@ protected virtual void WriteSpacing(int count) /// protected virtual void WriteLine() => Writer.WriteLine(); + /// + /// Writes a string to the , followed by a line break. + /// + /// The string to write. + protected void WriteLine(string? value) + { + Write(value); + WriteLine(); + } /// /// Writes a string with virtual terminal sequences only if color is enabled. @@ -2162,20 +2207,6 @@ internal string GetArgumentUsage(CommandLineArgument argument) return writer.BaseWriter.ToString()!; } - private void WriteLine(string? value) - { - Write(value); - WriteLine(); - } - - private void Write(string format, object? arg0) => Write(string.Format(Writer.FormatProvider, format, arg0)); - - private void WriteLine(string format, object? arg0, object? arg1) - => WriteLine(string.Format(Writer.FormatProvider, format, arg0, arg1)); - - private void WriteLine(string format, object? arg0, object? arg1, object? arg2) - => WriteLine(string.Format(Writer.FormatProvider, format, arg0, arg1, arg2)); - private VirtualTerminalSupport? EnableColor() { if (_useColor == null) From f12a187d32bf435d435ae74c296769edaac7cd40 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 4 Dec 2023 17:17:59 -0800 Subject: [PATCH 18/54] Add CommandLineArgument.Member property. --- .../CommandLineParserTest.cs | 10 +++++++++ src/Ookii.CommandLine/CommandLineArgument.cs | 21 +++++++++++++------ src/Ookii.CommandLine/CommandLineParser.cs | 3 +++ .../Support/ArgumentProvider.cs | 10 ++++++++- .../Support/GeneratedArgument.cs | 6 ++++++ .../Support/GeneratedArgumentProvider.cs | 7 ++++++- .../Support/ReflectionArgument.cs | 2 ++ 7 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index af69b77..dea694d 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -1390,6 +1390,7 @@ public ExpectedArgument(string name, Type type, ArgumentKind kind = ArgumentKind Name = name; Type = type; Kind = kind; + } public string Name { get; set; } @@ -1433,6 +1434,15 @@ private static void VerifyArgument(CommandLineArgument? argument, ExpectedArgume Assert.IsFalse(argument.HasValue); CollectionAssert.AreEqual(expected.Aliases ?? Array.Empty(), argument.Aliases); CollectionAssert.AreEqual(expected.ShortAliases ?? Array.Empty(), argument.ShortAliases); + if (argument.MemberName.StartsWith("Automatic")) + { + Assert.IsNull(argument.Member); + } + else + { + Assert.IsNotNull(argument.Member); + Assert.AreSame(argument.Parser.ArgumentsType.GetMember(argument.MemberName)[0], argument.Member); + } } private static void VerifyArguments(IEnumerable arguments, ExpectedArgument[] expected) diff --git a/src/Ookii.CommandLine/CommandLineArgument.cs b/src/Ookii.CommandLine/CommandLineArgument.cs index 7219877..c5e9e75 100644 --- a/src/Ookii.CommandLine/CommandLineArgument.cs +++ b/src/Ookii.CommandLine/CommandLineArgument.cs @@ -185,6 +185,8 @@ public HelpArgument(CommandLineParser parser, string argumentName, char shortNam { } + public override MemberInfo? Member => null; + protected override bool CanSetProperty => false; private static ArgumentInfo CreateInfo(CommandLineParser parser, string argumentName, char shortName, char shortAlias) @@ -242,6 +244,8 @@ public VersionArgument(CommandLineParser parser, string argumentName) { } + public override MemberInfo? Member => null; + protected override bool CanSetProperty => false; private static ArgumentInfo CreateInfo(CommandLineParser parser, string argumentName) @@ -317,7 +321,6 @@ internal struct ArgumentInfo private readonly Type _elementTypeWithNullable; private readonly string? _description; private readonly bool _isRequired; - private readonly string _memberName; private readonly object? _defaultValue; private readonly ArgumentKind _argumentKind; private readonly bool _allowNull; @@ -332,7 +335,7 @@ internal CommandLineArgument(ArgumentInfo info) { // If this method throws anything other than a NotSupportedException, it constitutes a bug in the Ookii.CommandLine library. _parser = info.Parser; - _memberName = info.MemberName; + MemberName = info.MemberName; _argumentName = info.ArgumentName; if (_parser.Mode == ParsingMode.LongShort) { @@ -411,10 +414,16 @@ internal CommandLineArgument(ArgumentInfo info) /// /// The name of the property or method that defined this command line argument. /// - public string MemberName - { - get { return _memberName; } - } + public string MemberName { get; } + + /// + /// Gets the for the member that defined this argument. + /// + /// + /// An instance of the or class, or + /// if this is the automatic version or help argument. + /// + public abstract MemberInfo? Member { get; } /// /// Gets the name of this argument. diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index e2ab8ff..6955601 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -451,6 +451,9 @@ public CommandLineParser(ArgumentProvider provider, ParseOptions? options = null /// /// The that was used to define the arguments. /// +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] +#endif public Type ArgumentsType => _provider.ArgumentsType; /// diff --git a/src/Ookii.CommandLine/Support/ArgumentProvider.cs b/src/Ookii.CommandLine/Support/ArgumentProvider.cs index c40bd54..4324bfb 100644 --- a/src/Ookii.CommandLine/Support/ArgumentProvider.cs +++ b/src/Ookii.CommandLine/Support/ArgumentProvider.cs @@ -1,6 +1,7 @@ using Ookii.CommandLine.Validation; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Ookii.CommandLine.Support; @@ -26,7 +27,11 @@ public abstract class ArgumentProvider /// if there is none. /// /// The class validators for the arguments type. - protected ArgumentProvider(Type argumentsType, ParseOptionsAttribute? options, IEnumerable? validators) + protected ArgumentProvider( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + Type argumentsType, ParseOptionsAttribute? options, IEnumerable? validators) { ArgumentsType = argumentsType ?? throw new ArgumentNullException(nameof(argumentsType)); OptionsAttribute = options; @@ -47,6 +52,9 @@ protected ArgumentProvider(Type argumentsType, ParseOptionsAttribute? options, I /// /// The of the class that will hold the argument values. /// +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] +#endif public Type ArgumentsType { get; } /// diff --git a/src/Ookii.CommandLine/Support/GeneratedArgument.cs b/src/Ookii.CommandLine/Support/GeneratedArgument.cs index d866abc..de41395 100644 --- a/src/Ookii.CommandLine/Support/GeneratedArgument.cs +++ b/src/Ookii.CommandLine/Support/GeneratedArgument.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Reflection; namespace Ookii.CommandLine.Support; @@ -22,6 +23,7 @@ public class GeneratedArgument : CommandLineArgument private readonly Func? _callMethod; private readonly string _defaultValueDescription; private readonly string? _defaultKeyDescription; + private MemberInfo? _member; private GeneratedArgument(ArgumentInfo info, Action? setProperty, Func? getProperty, Func? callMethod, string defaultValueDescription, string? defaultKeyDescription) : base(info) @@ -137,6 +139,10 @@ public static GeneratedArgument Create(CommandLineParser parser, return new GeneratedArgument(info, setProperty, getProperty, callMethod, defaultValueDescription, defaultKeyDescription); } + /// + public override MemberInfo? Member => _member ??= (MemberInfo?)Parser.ArgumentsType.GetProperty(MemberName) + ?? Parser.ArgumentsType.GetMethod(MemberName, BindingFlags.Public | BindingFlags.Static); + /// protected override bool CanSetProperty => _setProperty != null; diff --git a/src/Ookii.CommandLine/Support/GeneratedArgumentProvider.cs b/src/Ookii.CommandLine/Support/GeneratedArgumentProvider.cs index e2d4b44..09be869 100644 --- a/src/Ookii.CommandLine/Support/GeneratedArgumentProvider.cs +++ b/src/Ookii.CommandLine/Support/GeneratedArgumentProvider.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace Ookii.CommandLine.Support; @@ -36,7 +37,11 @@ public abstract class GeneratedArgumentProvider : ArgumentProvider /// The for the arguments type, or if /// there is none. /// - protected GeneratedArgumentProvider(Type argumentsType, + protected GeneratedArgumentProvider( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + Type argumentsType, ParseOptionsAttribute? options = null, IEnumerable? validators = null, ApplicationFriendlyNameAttribute? friendlyName = null, diff --git a/src/Ookii.CommandLine/Support/ReflectionArgument.cs b/src/Ookii.CommandLine/Support/ReflectionArgument.cs index f01b610..bdfa219 100644 --- a/src/Ookii.CommandLine/Support/ReflectionArgument.cs +++ b/src/Ookii.CommandLine/Support/ReflectionArgument.cs @@ -37,6 +37,8 @@ private ReflectionArgument(ArgumentInfo info, PropertyInfo? property, MethodArgu _method = method; } + public override MemberInfo? Member => (MemberInfo?)_property ?? _method?.Method; + protected override bool CanSetProperty => _property?.GetSetMethod() != null; protected override void SetProperty(object target, object? value) From e988ffb3f1e554027da5ed5d1f74ba47012412d5 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 4 Dec 2023 17:36:40 -0800 Subject: [PATCH 19/54] EnumConverter checks ValidateEnumValueAttribute.IncludeValuesInErrorMessage. The default value for IncludeValuesInErrorMessage is now true. --- .../Conversion/EnumConverter.cs | 19 +++++++++++++------ .../Validation/ValidateEnumValueAttribute.cs | 12 ++++++------ src/Samples/Parser/ProgramArguments.cs | 3 +++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Ookii.CommandLine/Conversion/EnumConverter.cs b/src/Ookii.CommandLine/Conversion/EnumConverter.cs index d51f9a5..b737ff7 100644 --- a/src/Ookii.CommandLine/Conversion/EnumConverter.cs +++ b/src/Ookii.CommandLine/Conversion/EnumConverter.cs @@ -1,5 +1,7 @@ -using System; +using Ookii.CommandLine.Validation; +using System; using System.Globalization; +using System.Linq; namespace Ookii.CommandLine.Conversion; @@ -19,8 +21,11 @@ namespace Ookii.CommandLine.Conversion; /// attribute. /// /// -/// If conversion fails, this converter will provide an error message that includes all the -/// allowed values for the enumeration. +/// If conversion fails, the error message will check the +/// +/// property to see whether or not the enumeration's defined values should be listed in the +/// error message. If the argument does not have the +/// attribute applied, the values will be listed. /// /// /// @@ -126,9 +131,11 @@ public EnumConverter(Type enumType) } #endif - private Exception CreateException(string value, Exception inner, CommandLineArgument argument) + private CommandLineArgumentException CreateException(string value, Exception inner, CommandLineArgument argument) { - var message = argument.Parser.StringProvider.ValidateEnumValueFailed(argument.ArgumentName, EnumType, value, true); - return new CommandLineArgumentException(message, argument.ArgumentName, CommandLineArgumentErrorCategory.ArgumentValueConversion, inner); + var attribute = argument.Validators.OfType().FirstOrDefault(); + var includeValues = attribute?.IncludeValuesInErrorMessage ?? true; + var message = argument.Parser.StringProvider.ValidateEnumValueFailed(argument.ArgumentName, EnumType, value, includeValues); + return new(message, argument.ArgumentName,CommandLineArgumentErrorCategory.ArgumentValueConversion, inner); } } diff --git a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs index 409a34c..952ef0e 100644 --- a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs +++ b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs @@ -60,17 +60,17 @@ public override bool IsValid(CommandLineArgument argument, object? value) /// should be included in the error message if validation fails. /// /// - /// to include the values; otherwise, . + /// to include the values; otherwise, . The + /// default value is . /// /// /// - /// This property is only used if the validation fails, which only the case for - /// undefined numerical values. Other strings that don't match the name of one of the - /// defined constants use the error message from the converter, which in the case of - /// the always shows the possible values. + /// This property is used when validation fails, and is also checked by the + /// class, which is the default converter for enumeration types, + /// when conversion fails due to an invalid string value. /// /// - public bool IncludeValuesInErrorMessage { get; set; } + public bool IncludeValuesInErrorMessage { get; set; } = true; /// /// diff --git a/src/Samples/Parser/ProgramArguments.cs b/src/Samples/Parser/ProgramArguments.cs index 8b1d33a..836e66b 100644 --- a/src/Samples/Parser/ProgramArguments.cs +++ b/src/Samples/Parser/ProgramArguments.cs @@ -140,6 +140,9 @@ partial class ProgramArguments // though DayOfWeek has no member with the value 9. The ValidateEnumValueAttribute makes sure // that only defined enum values are allowed. As a bonus, it also adds all the possible values // to the usage help. + // + // If conversion fails, the error message also lists all the possible values. If you don't want + // that, you can use [ValidateEnumValue(IncludeValuesInErrorMessage = false)] [CommandLineArgument] [Description("This is an argument using an enumeration type.")] [ValidateEnumValue] From 4d6aedaf1b30622b2a7834bb44beb835e82ddfc6 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 4 Dec 2023 18:05:37 -0800 Subject: [PATCH 20/54] Add ValidateEnumValue.CaseSensitive property. --- src/Ookii.CommandLine.Tests/ArgumentTypes.cs | 2 +- .../CommandLineParserTest.cs | 299 +++++++++--------- .../Conversion/EnumConverter.cs | 30 +- .../Validation/ValidateEnumValueAttribute.cs | 75 ++++- 4 files changed, 222 insertions(+), 184 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs index 9b7450f..6131fa8 100644 --- a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs +++ b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs @@ -451,7 +451,7 @@ public static void Arg3(int value) [CommandLineArgument] [Description("Day2 description.")] - [ValidateEnumValue] + [ValidateEnumValue(CaseSensitive = true, AllowNonDefinedValues = true)] public DayOfWeek? Day2 { get; set; } [CommandLineArgument] diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index dea694d..2da96ec 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -44,11 +44,11 @@ public void ConstructorEmptyArgumentsTest(ProviderKind kind) Assert.AreEqual("Ookii.CommandLine Unit Tests", target.ApplicationFriendlyName); Assert.AreEqual(string.Empty, target.Description); Assert.AreEqual(2, target.Arguments.Length); - VerifyArguments(target.Arguments, new[] - { - new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + VerifyArguments(target.Arguments, + [ + new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("Version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -67,15 +67,15 @@ public void ConstructorTest(ProviderKind kind) Assert.AreEqual("Friendly name", target.ApplicationFriendlyName); Assert.AreEqual("Test arguments description.", target.Description); Assert.AreEqual(18, target.Arguments.Length); - VerifyArguments(target.Arguments, new[] - { + VerifyArguments(target.Arguments, + [ new ExpectedArgument("arg1", typeof(string)) { MemberName = "Arg1", Position = 0, IsRequired = true, Description = "Arg1 description." }, new ExpectedArgument("other", typeof(int)) { MemberName = "Arg2", Position = 1, DefaultValue = 42, Description = "Arg2 description.", ValueDescription = "Number" }, new ExpectedArgument("notSwitch", typeof(bool)) { MemberName = "NotSwitch", Position = 2, DefaultValue = false }, new ExpectedArgument("Arg5", typeof(float)) { Position = 3, Description = "Arg5 description.", DefaultValue = 1.0f }, new ExpectedArgument("other2", typeof(int)) { MemberName = "Arg4", Position = 4, DefaultValue = 47, Description = "Arg4 description.", ValueDescription = "Number" }, new ExpectedArgument("Arg8", typeof(DayOfWeek[]), ArgumentKind.MultiValue) { ElementType = typeof(DayOfWeek), Position = 5 }, - new ExpectedArgument("Arg6", typeof(string)) { Position = null, IsRequired = true, Description = "Arg6 description.", Aliases = new[] { "Alias1", "Alias2" } }, + new ExpectedArgument("Arg6", typeof(string)) { Position = null, IsRequired = true, Description = "Arg6 description.", Aliases = ["Alias1", "Alias2"] }, new ExpectedArgument("Arg10", typeof(bool[]), ArgumentKind.MultiValue) { ElementType = typeof(bool), Position = null, IsSwitch = true }, new ExpectedArgument("Arg11", typeof(bool?)) { ElementType = typeof(bool), Position = null, ValueDescription = "Boolean", IsSwitch = true }, new ExpectedArgument("Arg12", typeof(Collection), ArgumentKind.MultiValue) { ElementType = typeof(int), Position = null, DefaultValue = 42 }, @@ -83,11 +83,11 @@ public void ConstructorTest(ProviderKind kind) new ExpectedArgument("Arg14", typeof(IDictionary), ArgumentKind.Dictionary) { ElementType = typeof(KeyValuePair), ValueDescription = "String=Int32" }, new ExpectedArgument("Arg15", typeof(KeyValuePair)) { ValueDescription = "KeyValuePair" }, new ExpectedArgument("Arg3", typeof(string)) { Position = null }, - new ExpectedArgument("Arg7", typeof(bool)) { Position = null, IsSwitch = true, Aliases = new[] { "Alias3" } }, + new ExpectedArgument("Arg7", typeof(bool)) { Position = null, IsSwitch = true, Aliases = ["Alias3"] }, new ExpectedArgument("Arg9", typeof(int?)) { ElementType = typeof(int), Position = null, ValueDescription = "Int32" }, - new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("Version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -117,21 +117,21 @@ public void ParseTest(ProviderKind kind) // All positional arguments except array TestParse(target, "val1 2 true 5.5 4 -arg6 arg6", "val1", 2, true, arg4: 4, arg5: 5.5f, arg6: "arg6"); // All positional arguments including array - TestParse(target, "val1 2 true 5.5 4 -arg6 arg6 Monday Tuesday", "val1", 2, true, arg4: 4, arg5: 5.5f, arg6: "arg6", arg8: new[] { DayOfWeek.Monday, DayOfWeek.Tuesday }); + TestParse(target, "val1 2 true 5.5 4 -arg6 arg6 Monday Tuesday", "val1", 2, true, arg4: 4, arg5: 5.5f, arg6: "arg6", arg8: [DayOfWeek.Monday, DayOfWeek.Tuesday]); // All positional arguments including array, which is specified by name first and then by position - TestParse(target, "val1 2 true 5.5 4 -arg6 arg6 -arg8 Monday Tuesday", "val1", 2, true, arg4: 4, arg5: 5.5f, arg6: "arg6", arg8: new[] { DayOfWeek.Monday, DayOfWeek.Tuesday }); + TestParse(target, "val1 2 true 5.5 4 -arg6 arg6 -arg8 Monday Tuesday", "val1", 2, true, arg4: 4, arg5: 5.5f, arg6: "arg6", arg8: [DayOfWeek.Monday, DayOfWeek.Tuesday]); // Some positional arguments using names, in order TestParse(target, "-arg1 val1 2 true -arg5 5.5 4 -arg6 arg6", "val1", 2, true, arg4: 4, arg5: 5.5f, arg6: "arg6"); // Some position arguments using names, out of order (also uses : and - for one of them to mix things up) TestParse(target, "-other 2 val1 -arg5:5.5 true 4 -arg6 arg6", "val1", 2, true, arg4: 4, arg5: 5.5f, arg6: "arg6"); // All arguments - TestParse(target, "val1 2 true -arg3 val3 -other2:4 5.5 -arg6 val6 -arg7 -arg8 Monday -arg8 Tuesday -arg9 9 -arg10 -arg10 -arg10:false -arg11:false -arg12 12 -arg12 13 -arg13 foo=13 -arg13 bar=14 -arg14 hello=1 -arg14 bye=2 -arg15 something=5", "val1", 2, true, "val3", 4, 5.5f, "val6", true, new[] { DayOfWeek.Monday, DayOfWeek.Tuesday }, 9, new[] { true, true, false }, false, new[] { 12, 13 }, new Dictionary() { { "foo", 13 }, { "bar", 14 } }, new Dictionary() { { "hello", 1 }, { "bye", 2 } }, new KeyValuePair("something", 5)); + TestParse(target, "val1 2 true -arg3 val3 -other2:4 5.5 -arg6 val6 -arg7 -arg8 Monday -arg8 Tuesday -arg9 9 -arg10 -arg10 -arg10:false -arg11:false -arg12 12 -arg12 13 -arg13 foo=13 -arg13 bar=14 -arg14 hello=1 -arg14 bye=2 -arg15 something=5", "val1", 2, true, "val3", 4, 5.5f, "val6", true, [DayOfWeek.Monday, DayOfWeek.Tuesday], 9, [true, true, false], false, [12, 13], new Dictionary() { { "foo", 13 }, { "bar", 14 } }, new Dictionary() { { "hello", 1 }, { "bye", 2 } }, new KeyValuePair("something", 5)); // Using aliases TestParse(target, "val1 2 -alias1 valalias6 -alias3", "val1", 2, arg6: "valalias6", arg7: true); // Long prefix cannot be used - CheckThrows(target, new[] { "val1", "2", "--arg6", "val6" }, CommandLineArgumentErrorCategory.UnknownArgument, "-arg6", remainingArgumentCount: 2); + CheckThrows(target, ["val1", "2", "--arg6", "val6"], CommandLineArgumentErrorCategory.UnknownArgument, "-arg6", remainingArgumentCount: 2); // Short name cannot be used - CheckThrows(target, new[] { "val1", "2", "-arg6", "val6", "-a:5.5" }, CommandLineArgumentErrorCategory.UnknownArgument, "a", remainingArgumentCount: 1); + CheckThrows(target, ["val1", "2", "-arg6", "val6", "-a:5.5"], CommandLineArgumentErrorCategory.UnknownArgument, "a", remainingArgumentCount: 1); } [TestMethod] @@ -140,7 +140,7 @@ public void ParseTestEmptyArguments(ProviderKind kind) { var target = CreateParser(kind); // This test was added because version 2.0 threw an IndexOutOfRangeException when you tried to specify a positional argument when there were no positional arguments defined. - CheckThrows(target, new[] { "Foo", "Bar" }, CommandLineArgumentErrorCategory.TooManyArguments, remainingArgumentCount: 2); + CheckThrows(target, ["Foo", "Bar"], CommandLineArgumentErrorCategory.TooManyArguments, remainingArgumentCount: 2); } [TestMethod] @@ -150,7 +150,7 @@ public void ParseTestTooManyArguments(ProviderKind kind) var target = CreateParser(kind); // Only accepts one positional argument. - CheckThrows(target, new[] { "Foo", "Bar" }, CommandLineArgumentErrorCategory.TooManyArguments, remainingArgumentCount: 1); + CheckThrows(target, ["Foo", "Bar"], CommandLineArgumentErrorCategory.TooManyArguments, remainingArgumentCount: 1); } [TestMethod] @@ -161,7 +161,7 @@ public void ParseTestPropertySetterThrows(ProviderKind kind) // No remaining arguments; exception happens after parsing finishes. CheckThrows(target, - new[] { "-ThrowingArgument", "-5" }, + ["-ThrowingArgument", "-5"], CommandLineArgumentErrorCategory.ApplyValueError, "ThrowingArgument", typeof(ArgumentOutOfRangeException)); @@ -186,14 +186,14 @@ public void ParseTestDuplicateDictionaryKeys(ProviderKind kind) { var target = CreateParser(kind); - var args = target.Parse(new[] { "-DuplicateKeys", "Foo=1", "-DuplicateKeys", "Bar=2", "-DuplicateKeys", "Foo=3" }); + var args = target.Parse(["-DuplicateKeys", "Foo=1", "-DuplicateKeys", "Bar=2", "-DuplicateKeys", "Foo=3"]); Assert.IsNotNull(args); Assert.AreEqual(2, args.DuplicateKeys.Count); Assert.AreEqual(3, args.DuplicateKeys["Foo"]); Assert.AreEqual(2, args.DuplicateKeys["Bar"]); CheckThrows(target, - new[] { "-NoDuplicateKeys", "Foo=1", "-NoDuplicateKeys", "Bar=2", "-NoDuplicateKeys", "Foo=3" }, + ["-NoDuplicateKeys", "Foo=1", "-NoDuplicateKeys", "Bar=2", "-NoDuplicateKeys", "Foo=3"], CommandLineArgumentErrorCategory.InvalidDictionaryValue, "NoDuplicateKeys", typeof(ArgumentException), @@ -206,7 +206,7 @@ public void ParseTestMultiValueSeparator(ProviderKind kind) { var target = CreateParser(kind); - var args = target.Parse(new[] { "-NoSeparator", "Value1,Value2", "-NoSeparator", "Value3", "-Separator", "Value1,Value2", "-Separator", "Value3" }); + var args = target.Parse(["-NoSeparator", "Value1,Value2", "-NoSeparator", "Value3", "-Separator", "Value1,Value2", "-Separator", "Value3"]); Assert.IsNotNull(args); CollectionAssert.AreEqual(new[] { "Value1,Value2", "Value3" }, args.NoSeparator); CollectionAssert.AreEqual(new[] { "Value1", "Value2", "Value3" }, args.Separator); @@ -218,18 +218,18 @@ public void ParseTestNameValueSeparator(ProviderKind kind) { var target = CreateParser(kind); CollectionAssert.AreEquivalent(new[] { ':', '=' }, target.NameValueSeparators); - var args = CheckSuccess(target, new[] { "-Argument1:test", "-Argument2:foo:bar" }); + var args = CheckSuccess(target, ["-Argument1:test", "-Argument2:foo:bar"]); Assert.IsNotNull(args); Assert.AreEqual("test", args.Argument1); Assert.AreEqual("foo:bar", args.Argument2); - args = CheckSuccess(target, new[] { "-Argument1=test", "-Argument2=foo:bar" }); + args = CheckSuccess(target, ["-Argument1=test", "-Argument2=foo:bar"]); Assert.AreEqual("test", args.Argument1); Assert.AreEqual("foo:bar", args.Argument2); - args = CheckSuccess(target, new[] { "-Argument2:foo=bar" }); + args = CheckSuccess(target, ["-Argument2:foo=bar"]); Assert.AreEqual("foo=bar", args.Argument2); CheckThrows(target, - new[] { "-Argument1>test" }, + ["-Argument1>test"], CommandLineArgumentErrorCategory.UnknownArgument, "Argument1>test", remainingArgumentCount: 1); @@ -240,18 +240,18 @@ public void ParseTestNameValueSeparator(ProviderKind kind) }; target = CreateParser(kind, options); - args = target.Parse(new[] { "-Argument1>test", "-Argument2>foo>bar" }); + args = target.Parse(["-Argument1>test", "-Argument2>foo>bar"]); Assert.IsNotNull(args); Assert.AreEqual("test", args.Argument1); Assert.AreEqual("foo>bar", args.Argument2); CheckThrows(target, - new[] { "-Argument1:test" }, + ["-Argument1:test"], CommandLineArgumentErrorCategory.UnknownArgument, "Argument1:test", remainingArgumentCount: 1); CheckThrows(target, - new[] { "-Argument1=test" }, + ["-Argument1=test"], CommandLineArgumentErrorCategory.UnknownArgument, "Argument1=test", remainingArgumentCount: 1); @@ -267,11 +267,11 @@ public void ParseTestKeyValueSeparator(ProviderKind kind) Assert.AreEqual("<=>", target.GetArgument("CustomSeparator")!.DictionaryInfo!.KeyValueSeparator); Assert.AreEqual("String<=>String", target.GetArgument("CustomSeparator")!.ValueDescription); - var result = CheckSuccess(target, new[] { "-CustomSeparator", "foo<=>bar", "-CustomSeparator", "baz<=>contains<=>separator", "-CustomSeparator", "hello<=>" }); + var result = CheckSuccess(target, ["-CustomSeparator", "foo<=>bar", "-CustomSeparator", "baz<=>contains<=>separator", "-CustomSeparator", "hello<=>"]); Assert.IsNotNull(result); CollectionAssert.AreEquivalent(new[] { KeyValuePair.Create("foo", "bar"), KeyValuePair.Create("baz", "contains<=>separator"), KeyValuePair.Create("hello", "") }, result.CustomSeparator); CheckThrows(target, - new[] { "-CustomSeparator", "foo=bar" }, + ["-CustomSeparator", "foo=bar"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "CustomSeparator", typeof(FormatException), @@ -280,7 +280,7 @@ public void ParseTestKeyValueSeparator(ProviderKind kind) // Inner exception is FormatException because what throws here is trying to convert // ">bar" to int. CheckThrows(target, - new[] { "-DefaultSeparator", "foo<=>bar" }, + ["-DefaultSeparator", "foo<=>bar"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "DefaultSeparator", typeof(FormatException), @@ -537,7 +537,7 @@ public void TestStaticParse(ProviderKind kind) } }; - var result = StaticParse(kind, new[] { "foo", "-Arg6", "bar" }, options); + var result = StaticParse(kind, ["foo", "-Arg6", "bar"], options); Assert.IsNotNull(result); Assert.AreEqual("foo", result.Arg1); Assert.AreEqual("bar", result.Arg6); @@ -551,7 +551,7 @@ public void TestStaticParse(ProviderKind kind) output.GetStringBuilder().Clear(); error.GetStringBuilder().Clear(); - result = StaticParse(kind, new[] { "-Help" }, options); + result = StaticParse(kind, ["-Help"], options); Assert.IsNull(result); Assert.AreEqual(0, error.ToString().Length); Assert.AreEqual(_expectedDefaultUsage, output.ToString()); @@ -575,7 +575,7 @@ public void TestStaticParse(ProviderKind kind) // Still get full help with -Help arg. output.GetStringBuilder().Clear(); error.GetStringBuilder().Clear(); - result = StaticParse(kind, new[] { "-Help" }, options); + result = StaticParse(kind, ["-Help"], options); Assert.IsNull(result); Assert.AreEqual(0, error.ToString().Length); Assert.AreEqual(_expectedDefaultUsage, output.ToString()); @@ -588,7 +588,7 @@ public void TestCancelParsing(ProviderKind kind) var parser = CreateParser(kind); // Don't cancel if -DoesCancel not specified. - var result = parser.Parse(new[] { "-Argument1", "foo", "-DoesNotCancel", "-Argument2", "bar" }); + var result = parser.Parse(["-Argument1", "foo", "-DoesNotCancel", "-Argument2", "bar"]); Assert.IsNotNull(result); Assert.IsFalse(parser.HelpRequested); Assert.IsTrue(result.DoesNotCancel); @@ -600,7 +600,7 @@ public void TestCancelParsing(ProviderKind kind) Assert.AreEqual(0, parser.ParseResult.RemainingArguments.Length); // Cancel if -DoesCancel specified. - result = parser.Parse(new[] { "-Argument1", "foo", "-DoesCancel", "-Argument2", "bar" }); + result = parser.Parse(["-Argument1", "foo", "-DoesCancel", "-Argument2", "bar"]); Assert.IsNull(result); Assert.IsTrue(parser.HelpRequested); Assert.AreEqual(ParseStatus.Canceled, parser.ParseResult.Status); @@ -626,7 +626,7 @@ static void handler1(object? sender, ArgumentParsedEventArgs e) } parser.ArgumentParsed += handler1; - result = parser.Parse(new[] { "-Argument1", "foo", "-DoesNotCancel", "-Argument2", "bar" }); + result = parser.Parse(["-Argument1", "foo", "-DoesNotCancel", "-Argument2", "bar"]); Assert.IsNull(result); Assert.AreEqual(ParseStatus.Canceled, parser.ParseResult.Status); Assert.IsNull(parser.ParseResult.LastException); @@ -654,7 +654,7 @@ static void handler2(object? sender, ArgumentParsedEventArgs e) } parser.ArgumentParsed += handler2; - result = parser.Parse(new[] { "-Argument1", "foo", "-DoesCancel", "-Argument2", "bar" }); + result = parser.Parse(["-Argument1", "foo", "-DoesCancel", "-Argument2", "bar"]); Assert.AreEqual(ParseStatus.Success, parser.ParseResult.Status); Assert.IsNull(parser.ParseResult.ArgumentName); Assert.AreEqual(0, parser.ParseResult.RemainingArguments.Length); @@ -666,7 +666,7 @@ static void handler2(object? sender, ArgumentParsedEventArgs e) Assert.AreEqual("bar", result.Argument2); // Automatic help argument should cancel. - result = parser.Parse(new[] { "-Help" }); + result = parser.Parse(["-Help"]); Assert.AreEqual(ParseStatus.Canceled, parser.ParseResult.Status); Assert.IsNull(parser.ParseResult.LastException); Assert.AreEqual("Help", parser.ParseResult.ArgumentName); @@ -680,7 +680,7 @@ static void handler2(object? sender, ArgumentParsedEventArgs e) public void TestCancelParsingSuccess(ProviderKind kind) { var parser = CreateParser(kind); - var result = parser.Parse(new[] { "-Argument1", "foo", "-DoesCancelWithSuccess", "-Argument2", "bar" }); + var result = parser.Parse(["-Argument1", "foo", "-DoesCancelWithSuccess", "-Argument2", "bar"]); Assert.AreEqual(ParseStatus.Success, parser.ParseResult.Status); Assert.AreEqual("DoesCancelWithSuccess", parser.ParseResult.ArgumentName); AssertSpanEqual(new[] { "-Argument2", "bar" }.AsSpan(), parser.ParseResult.RemainingArguments.Span); @@ -693,7 +693,7 @@ public void TestCancelParsingSuccess(ProviderKind kind) Assert.IsNull(result.Argument2); // No remaining arguments. - result = parser.Parse(new[] { "-Argument1", "foo", "-DoesCancelWithSuccess" }); + result = parser.Parse(["-Argument1", "foo", "-DoesCancelWithSuccess"]); Assert.AreEqual(ParseStatus.Success, parser.ParseResult.Status); Assert.AreEqual("DoesCancelWithSuccess", parser.ParseResult.ArgumentName); Assert.AreEqual(0, parser.ParseResult.RemainingArguments.Length); @@ -753,19 +753,19 @@ public void TestParseOptionsAttribute(ProviderKind kind) [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] public void TestCulture(ProviderKind kind) { - var result = StaticParse(kind, new[] { "-Argument", "5.5" }); + var result = StaticParse(kind, ["-Argument", "5.5"]); Assert.IsNotNull(result); Assert.AreEqual(5.5, result.Argument); - result = StaticParse(kind, new[] { "-Argument", "5,5" }); + result = StaticParse(kind, ["-Argument", "5,5"]); Assert.IsNotNull(result); // , was interpreted as a thousands separator. Assert.AreEqual(55, result.Argument); var options = new ParseOptions { Culture = new CultureInfo("nl-NL") }; - result = StaticParse(kind, new[] { "-Argument", "5,5" }, options); + result = StaticParse(kind, ["-Argument", "5,5"], options); Assert.IsNotNull(result); Assert.AreEqual(5.5, result.Argument); - result = StaticParse(kind, new[] { "-Argument", "5,5" }); + result = StaticParse(kind, ["-Argument", "5,5"]); Assert.IsNotNull(result); // . was interpreted as a thousands separator. Assert.AreEqual(55, result.Argument); @@ -790,7 +790,7 @@ public void TestLongShortMode(ProviderKind kind) Assert.AreEqual('\0', parser.GetArgument("bar")!.ShortName); Assert.IsFalse(parser.GetArgument("bar")!.HasShortName); - var result = CheckSuccess(parser, new[] { "-f", "5", "--bar", "6", "-a", "7", "--arg1", "8", "-s" }); + var result = CheckSuccess(parser, ["-f", "5", "--bar", "6", "-a", "7", "--arg1", "8", "-s"]); Assert.AreEqual(5, result.Foo); Assert.AreEqual(6, result.Bar); Assert.AreEqual(7, result.Arg2); @@ -800,26 +800,26 @@ public void TestLongShortMode(ProviderKind kind) Assert.IsFalse(result.Switch3); // Combine switches. - result = CheckSuccess(parser, new[] { "-su" }); + result = CheckSuccess(parser, ["-su"]); Assert.IsTrue(result.Switch1); Assert.IsFalse(LongShortArguments.Switch2Value); Assert.IsTrue(result.Switch3); // Use a short alias. - result = CheckSuccess(parser, new[] { "-b", "5" }); + result = CheckSuccess(parser, ["-b", "5"]); Assert.AreEqual(5, result.Arg2); // Combining non-switches is an error. - CheckThrows(parser, new[] { "-sf" }, CommandLineArgumentErrorCategory.CombinedShortNameNonSwitch, "sf", remainingArgumentCount: 1); + CheckThrows(parser, ["-sf"], CommandLineArgumentErrorCategory.CombinedShortNameNonSwitch, "sf", remainingArgumentCount: 1); // Can't use long argument prefix with short names. - CheckThrows(parser, new[] { "--s" }, CommandLineArgumentErrorCategory.UnknownArgument, "s", remainingArgumentCount: 1); + CheckThrows(parser, ["--s"], CommandLineArgumentErrorCategory.UnknownArgument, "s", remainingArgumentCount: 1); // And vice versa. - CheckThrows(parser, new[] { "-Switch1" }, CommandLineArgumentErrorCategory.UnknownArgument, "w", remainingArgumentCount: 1); + CheckThrows(parser, ["-Switch1"], CommandLineArgumentErrorCategory.UnknownArgument, "w", remainingArgumentCount: 1); // Short alias is ignored on an argument without a short name. - CheckThrows(parser, new[] { "-c" }, CommandLineArgumentErrorCategory.UnknownArgument, "c", remainingArgumentCount: 1); + CheckThrows(parser, ["-c"], CommandLineArgumentErrorCategory.UnknownArgument, "c", remainingArgumentCount: 1); } [TestMethod] @@ -833,45 +833,45 @@ public void TestMethodArguments(ProviderKind kind) Assert.IsNull(parser.GetArgument("NotStatic")); Assert.IsNull(parser.GetArgument("NotPublic")); - CheckSuccess(parser, new[] { "-NoCancel" }); + CheckSuccess(parser, ["-NoCancel"]); Assert.AreEqual(nameof(MethodArguments.NoCancel), MethodArguments.CalledMethodName); - CheckCanceled(parser, new[] { "-Cancel", "Foo" }, "Cancel", false, 1); + CheckCanceled(parser, ["-Cancel", "Foo"], "Cancel", false, 1); Assert.AreEqual(nameof(MethodArguments.Cancel), MethodArguments.CalledMethodName); - CheckCanceled(parser, new[] { "-CancelWithHelp" }, "CancelWithHelp", true, 0); + CheckCanceled(parser, ["-CancelWithHelp"], "CancelWithHelp", true, 0); Assert.AreEqual(nameof(MethodArguments.CancelWithHelp), MethodArguments.CalledMethodName); - CheckSuccess(parser, new[] { "-CancelWithValue", "1" }); + CheckSuccess(parser, ["-CancelWithValue", "1"]); Assert.AreEqual(nameof(MethodArguments.CancelWithValue), MethodArguments.CalledMethodName); Assert.AreEqual(1, MethodArguments.Value); - CheckCanceled(parser, new[] { "-CancelWithValue", "-1" }, "CancelWithValue", false); + CheckCanceled(parser, ["-CancelWithValue", "-1"], "CancelWithValue", false); Assert.AreEqual(nameof(MethodArguments.CancelWithValue), MethodArguments.CalledMethodName); Assert.AreEqual(-1, MethodArguments.Value); - CheckSuccess(parser, new[] { "-CancelWithValueAndHelp", "1" }); + CheckSuccess(parser, ["-CancelWithValueAndHelp", "1"]); Assert.AreEqual(nameof(MethodArguments.CancelWithValueAndHelp), MethodArguments.CalledMethodName); Assert.AreEqual(1, MethodArguments.Value); - CheckCanceled(parser, new[] { "-CancelWithValueAndHelp", "-1", "bar" }, "CancelWithValueAndHelp", true, 1); + CheckCanceled(parser, ["-CancelWithValueAndHelp", "-1", "bar"], "CancelWithValueAndHelp", true, 1); Assert.AreEqual(nameof(MethodArguments.CancelWithValueAndHelp), MethodArguments.CalledMethodName); Assert.AreEqual(-1, MethodArguments.Value); - CheckSuccess(parser, new[] { "-NoReturn" }); + CheckSuccess(parser, ["-NoReturn"]); Assert.AreEqual(nameof(MethodArguments.NoReturn), MethodArguments.CalledMethodName); - CheckSuccess(parser, new[] { "42" }); + CheckSuccess(parser, ["42"]); Assert.AreEqual(nameof(MethodArguments.Positional), MethodArguments.CalledMethodName); Assert.AreEqual(42, MethodArguments.Value); - CheckCanceled(parser, new[] { "-CancelModeAbort", "Foo" }, "CancelModeAbort", false, 1); + CheckCanceled(parser, ["-CancelModeAbort", "Foo"], "CancelModeAbort", false, 1); Assert.AreEqual(nameof(MethodArguments.CancelModeAbort), MethodArguments.CalledMethodName); - CheckSuccess(parser, new[] { "-CancelModeSuccess", "Foo" }, "CancelModeSuccess", 1); + CheckSuccess(parser, ["-CancelModeSuccess", "Foo"], "CancelModeSuccess", 1); Assert.AreEqual(nameof(MethodArguments.CancelModeSuccess), MethodArguments.CalledMethodName); - CheckSuccess(parser, new[] { "-CancelModeNone" }); + CheckSuccess(parser, ["-CancelModeNone"]); Assert.AreEqual(nameof(MethodArguments.CancelModeNone), MethodArguments.CalledMethodName); } @@ -917,15 +917,15 @@ public void TestNameTransformPascalCase(ProviderKind kind) }; var parser = CreateParser(kind, options); - VerifyArguments(parser.Arguments, new[] - { + VerifyArguments(parser.Arguments, + [ new ExpectedArgument("TestArg", typeof(string)) { MemberName = "testArg", Position = 0, IsRequired = true }, new ExpectedArgument("ExplicitName", typeof(int)) { MemberName = "Explicit" }, - new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("TestArg2", typeof(int)) { MemberName = "TestArg2" }, new ExpectedArgument("TestArg3", typeof(int)) { MemberName = "__test__arg3__" }, new ExpectedArgument("Version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -938,15 +938,15 @@ public void TestNameTransformCamelCase(ProviderKind kind) }; var parser = CreateParser(kind, options); - VerifyArguments(parser.Arguments, new[] - { + VerifyArguments(parser.Arguments, + [ new ExpectedArgument("testArg", typeof(string)) { MemberName = "testArg", Position = 0, IsRequired = true }, new ExpectedArgument("ExplicitName", typeof(int)) { MemberName = "Explicit" }, - new ExpectedArgument("help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("testArg2", typeof(int)) { MemberName = "TestArg2" }, new ExpectedArgument("testArg3", typeof(int)) { MemberName = "__test__arg3__" }, new ExpectedArgument("version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -959,15 +959,15 @@ public void TestNameTransformSnakeCase(ProviderKind kind) }; var parser = CreateParser(kind, options); - VerifyArguments(parser.Arguments, new[] - { + VerifyArguments(parser.Arguments, + [ new ExpectedArgument("test_arg", typeof(string)) { MemberName = "testArg", Position = 0, IsRequired = true }, new ExpectedArgument("ExplicitName", typeof(int)) { MemberName = "Explicit" }, - new ExpectedArgument("help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("test_arg2", typeof(int)) { MemberName = "TestArg2" }, new ExpectedArgument("test_arg3", typeof(int)) { MemberName = "__test__arg3__" }, new ExpectedArgument("version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -980,15 +980,15 @@ public void TestNameTransformDashCase(ProviderKind kind) }; var parser = CreateParser(kind, options); - VerifyArguments(parser.Arguments, new[] - { + VerifyArguments(parser.Arguments, + [ new ExpectedArgument("test-arg", typeof(string)) { MemberName = "testArg", Position = 0, IsRequired = true }, new ExpectedArgument("ExplicitName", typeof(int)) { MemberName = "Explicit" }, - new ExpectedArgument("help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("test-arg2", typeof(int)) { MemberName = "TestArg2" }, new ExpectedArgument("test-arg3", typeof(int)) { MemberName = "__test__arg3__" }, new ExpectedArgument("version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -1001,13 +1001,13 @@ public void TestValueDescriptionTransform(ProviderKind kind) }; var parser = CreateParser(kind, options); - VerifyArguments(parser.Arguments, new[] - { + VerifyArguments(parser.Arguments, + [ new ExpectedArgument("Arg1", typeof(FileInfo)) { ValueDescription = "file-info" }, new ExpectedArgument("Arg2", typeof(int)) { ValueDescription = "int32" }, - new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { ValueDescription = "boolean", MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { ValueDescription = "boolean", MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("Version", typeof(bool), ArgumentKind.Method) { ValueDescription = "boolean", MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -1019,59 +1019,64 @@ public void TestValidation(ProviderKind kind) var parser = CreateParser(kind); // Range validator on property - CheckThrows(parser, new[] { "-Arg1", "0" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg1", remainingArgumentCount: 2); - var result = CheckSuccess(parser, new[] { "-Arg1", "1" }); + CheckThrows(parser, ["-Arg1", "0"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg1", remainingArgumentCount: 2); + var result = CheckSuccess(parser, ["-Arg1", "1"]); Assert.AreEqual(1, result.Arg1); - result = CheckSuccess(parser, new[] { "-Arg1", "5" }); + result = CheckSuccess(parser, ["-Arg1", "5"]); Assert.AreEqual(5, result.Arg1); - CheckThrows(parser, new[] { "-Arg1", "6" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg1", remainingArgumentCount: 2); + CheckThrows(parser, ["-Arg1", "6"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg1", remainingArgumentCount: 2); // Not null or empty on ctor parameter - CheckThrows(parser, new[] { "" }, CommandLineArgumentErrorCategory.ValidationFailed, "arg2", remainingArgumentCount: 1); - result = CheckSuccess(parser, new[] { " " }); + CheckThrows(parser, [""], CommandLineArgumentErrorCategory.ValidationFailed, "arg2", remainingArgumentCount: 1); + result = CheckSuccess(parser, [" "]); Assert.AreEqual(" ", result.Arg2); // Multiple validators on method - CheckThrows(parser, new[] { "-Arg3", "1238" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg3", remainingArgumentCount: 2); + CheckThrows(parser, ["-Arg3", "1238"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg3", remainingArgumentCount: 2); Assert.AreEqual(0, ValidationArguments.Arg3Value); - CheckThrows(parser, new[] { "-Arg3", "123" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg3", remainingArgumentCount: 2); + CheckThrows(parser, ["-Arg3", "123"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg3", remainingArgumentCount: 2); Assert.AreEqual(0, ValidationArguments.Arg3Value); - CheckThrows(parser, new[] { "-Arg3", "7001" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg3", remainingArgumentCount: 2); + CheckThrows(parser, ["-Arg3", "7001"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg3", remainingArgumentCount: 2); // Range validation is done after setting the value, so this was set! Assert.AreEqual(7001, ValidationArguments.Arg3Value); - CheckSuccess(parser, new[] { "-Arg3", "1023" }); + CheckSuccess(parser, ["-Arg3", "1023"]); Assert.AreEqual(1023, ValidationArguments.Arg3Value); // Validator on multi-value argument - CheckThrows(parser, new[] { "-Arg4", "foo;bar;bazz" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg4", remainingArgumentCount: 2); - CheckThrows(parser, new[] { "-Arg4", "foo", "-Arg4", "bar", "-Arg4", "bazz" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg4", remainingArgumentCount: 2); - result = CheckSuccess(parser, new[] { "-Arg4", "foo;bar" }); + CheckThrows(parser, ["-Arg4", "foo;bar;bazz"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg4", remainingArgumentCount: 2); + CheckThrows(parser, ["-Arg4", "foo", "-Arg4", "bar", "-Arg4", "bazz"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg4", remainingArgumentCount: 2); + result = CheckSuccess(parser, ["-Arg4", "foo;bar"]); CollectionAssert.AreEqual(new[] { "foo", "bar" }, result.Arg4); - result = CheckSuccess(parser, new[] { "-Arg4", "foo", "-Arg4", "bar" }); + result = CheckSuccess(parser, ["-Arg4", "foo", "-Arg4", "bar"]); CollectionAssert.AreEqual(new[] { "foo", "bar" }, result.Arg4); // Count validator // No remaining arguments because validation happens after parsing. - CheckThrows(parser, new[] { "-Arg4", "foo" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg4"); - CheckThrows(parser, new[] { "-Arg4", "foo;bar;baz;ban;bap" }, CommandLineArgumentErrorCategory.ValidationFailed, "Arg4"); - result = CheckSuccess(parser, new[] { "-Arg4", "foo;bar;baz;ban" }); + CheckThrows(parser, ["-Arg4", "foo"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg4"); + CheckThrows(parser, ["-Arg4", "foo;bar;baz;ban;bap"], CommandLineArgumentErrorCategory.ValidationFailed, "Arg4"); + result = CheckSuccess(parser, ["-Arg4", "foo;bar;baz;ban"]); CollectionAssert.AreEqual(new[] { "foo", "bar", "baz", "ban" }, result.Arg4); // Enum validator - CheckThrows(parser, new[] { "-Day", "foo" }, CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day", typeof(ArgumentException), remainingArgumentCount: 2); - CheckThrows(parser, new[] { "-Day", "9" }, CommandLineArgumentErrorCategory.ValidationFailed, "Day", remainingArgumentCount: 2); - CheckThrows(parser, new[] { "-Day", "" }, CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day", typeof(ArgumentException), remainingArgumentCount: 2); - result = CheckSuccess(parser, new[] { "-Day", "1" }); + CheckThrows(parser, ["-Day", "foo"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day", typeof(ArgumentException), remainingArgumentCount: 2); + CheckThrows(parser, ["-Day", "9"], CommandLineArgumentErrorCategory.ValidationFailed, "Day", remainingArgumentCount: 2); + CheckThrows(parser, ["-Day", ""], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day", typeof(ArgumentException), remainingArgumentCount: 2); + result = CheckSuccess(parser, ["-Day", "1"]); Assert.AreEqual(DayOfWeek.Monday, result.Day); - CheckThrows(parser, new[] { "-Day2", "foo" }, CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day2", typeof(ArgumentException), remainingArgumentCount: 2); - CheckThrows(parser, new[] { "-Day2", "9" }, CommandLineArgumentErrorCategory.ValidationFailed, "Day2", remainingArgumentCount: 2); - result = CheckSuccess(parser, new[] { "-Day2", "1" }); + CheckThrows(parser, ["-Day2", "foo"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day2", typeof(ArgumentException), remainingArgumentCount: 2); + CheckSuccess(parser, ["-Day2", "9"]); // This one allows it. + result = CheckSuccess(parser, ["-Day2", "1"]); Assert.AreEqual(DayOfWeek.Monday, result.Day2); - result = CheckSuccess(parser, new[] { "-Day2", "" }); + result = CheckSuccess(parser, ["-Day2", ""]); Assert.IsNull(result.Day2); + // Case sensitive enums + CheckSuccess(parser, ["-Day", "tuesday"]); + CheckSuccess(parser, ["-Day2", "Tuesday"]); + CheckThrows(parser, ["-Day2", "tuesday"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day2", typeof(ArgumentException), remainingArgumentCount: 2); + // NotNull validator with Nullable. - CheckThrows(parser, new[] { "-NotNull", "" }, CommandLineArgumentErrorCategory.ValidationFailed, "NotNull", remainingArgumentCount: 2); + CheckThrows(parser, ["-NotNull", ""], CommandLineArgumentErrorCategory.ValidationFailed, "NotNull", remainingArgumentCount: 2); } [TestMethod] @@ -1081,16 +1086,16 @@ public void TestRequires(ProviderKind kind) var parser = CreateParser(kind); // None of these have remaining arguments because validation happens after parsing. - var result = CheckSuccess(parser, new[] { "-Address", "127.0.0.1" }); + var result = CheckSuccess(parser, ["-Address", "127.0.0.1"]); Assert.AreEqual(IPAddress.Loopback, result.Address); - CheckThrows(parser, new[] { "-Port", "9000" }, CommandLineArgumentErrorCategory.DependencyFailed, "Port"); - result = CheckSuccess(parser, new[] { "-Address", "127.0.0.1", "-Port", "9000" }); + CheckThrows(parser, ["-Port", "9000"], CommandLineArgumentErrorCategory.DependencyFailed, "Port"); + result = CheckSuccess(parser, ["-Address", "127.0.0.1", "-Port", "9000"]); Assert.AreEqual(IPAddress.Loopback, result.Address); Assert.AreEqual(9000, result.Port); - CheckThrows(parser, new[] { "-Protocol", "1" }, CommandLineArgumentErrorCategory.DependencyFailed, "Protocol"); - CheckThrows(parser, new[] { "-Address", "127.0.0.1", "-Protocol", "1" }, CommandLineArgumentErrorCategory.DependencyFailed, "Protocol"); - CheckThrows(parser, new[] { "-Throughput", "10", "-Protocol", "1" }, CommandLineArgumentErrorCategory.DependencyFailed, "Protocol"); - result = CheckSuccess(parser, new[] { "-Protocol", "1", "-Address", "127.0.0.1", "-Throughput", "10" }); + CheckThrows(parser, ["-Protocol", "1"], CommandLineArgumentErrorCategory.DependencyFailed, "Protocol"); + CheckThrows(parser, ["-Address", "127.0.0.1", "-Protocol", "1"], CommandLineArgumentErrorCategory.DependencyFailed, "Protocol"); + CheckThrows(parser, ["-Throughput", "10", "-Protocol", "1"], CommandLineArgumentErrorCategory.DependencyFailed, "Protocol"); + result = CheckSuccess(parser, ["-Protocol", "1", "-Address", "127.0.0.1", "-Throughput", "10"]); Assert.AreEqual(IPAddress.Loopback, result.Address); Assert.AreEqual(10, result.Throughput); Assert.AreEqual(1, result.Protocol); @@ -1102,10 +1107,10 @@ public void TestProhibits(ProviderKind kind) { var parser = CreateParser(kind); - var result = CheckSuccess(parser, new[] { "-Path", "test" }); + var result = CheckSuccess(parser, ["-Path", "test"]); Assert.AreEqual("test", result.Path.Name); // No remaining arguments because validation happens after parsing. - CheckThrows(parser, new[] { "-Path", "test", "-Address", "127.0.0.1" }, CommandLineArgumentErrorCategory.DependencyFailed, "Path"); + CheckThrows(parser, ["-Path", "test", "-Address", "127.0.0.1"], CommandLineArgumentErrorCategory.DependencyFailed, "Path"); } [TestMethod] @@ -1165,19 +1170,19 @@ public void TestMultiValueWhiteSpaceSeparator(ProviderKind kind) Assert.IsFalse(parser.GetArgument("MultiSwitch")!.MultiValueInfo!.AllowWhiteSpaceSeparator); Assert.IsNull(parser.GetArgument("Other")!.MultiValueInfo); - var result = CheckSuccess(parser, new[] { "1", "-Multi", "2", "3", "4", "-Other", "5", "6" }); + var result = CheckSuccess(parser, ["1", "-Multi", "2", "3", "4", "-Other", "5", "6"]); Assert.AreEqual(result.Arg1, 1); Assert.AreEqual(result.Arg2, 6); Assert.AreEqual(result.Other, 5); CollectionAssert.AreEqual(new[] { 2, 3, 4 }, result.Multi); - result = CheckSuccess(parser, new[] { "-Multi", "1", "-Multi", "2" }); + result = CheckSuccess(parser, ["-Multi", "1", "-Multi", "2"]); CollectionAssert.AreEqual(new[] { 1, 2 }, result.Multi); - CheckThrows(parser, new[] { "1", "-Multi", "-Other", "5", "6" }, CommandLineArgumentErrorCategory.MissingNamedArgumentValue, "Multi", remainingArgumentCount: 4); - CheckThrows(parser, new[] { "-MultiSwitch", "true", "false" }, CommandLineArgumentErrorCategory.ArgumentValueConversion, "Arg1", typeof(FormatException), remainingArgumentCount: 2); + CheckThrows(parser, ["1", "-Multi", "-Other", "5", "6"], CommandLineArgumentErrorCategory.MissingNamedArgumentValue, "Multi", remainingArgumentCount: 4); + CheckThrows(parser, ["-MultiSwitch", "true", "false"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Arg1", typeof(FormatException), remainingArgumentCount: 2); parser.Options.AllowWhiteSpaceValueSeparator = false; - CheckThrows(parser, new[] { "1", "-Multi:2", "2", "3", "4", "-Other", "5", "6" }, CommandLineArgumentErrorCategory.TooManyArguments, remainingArgumentCount: 5); + CheckThrows(parser, ["1", "-Multi:2", "2", "3", "4", "-Other", "5", "6"], CommandLineArgumentErrorCategory.TooManyArguments, remainingArgumentCount: 5); } [TestMethod] @@ -1185,7 +1190,7 @@ public void TestMultiValueWhiteSpaceSeparator(ProviderKind kind) public void TestInjection(ProviderKind kind) { var parser = CreateParser(kind); - var result = CheckSuccess(parser, new[] { "-Arg", "1" }); + var result = CheckSuccess(parser, ["-Arg", "1"]); Assert.AreSame(parser, result.Parser); Assert.AreEqual(1, result.Arg); } @@ -1195,9 +1200,9 @@ public void TestInjection(ProviderKind kind) public void TestDuplicateArguments(ProviderKind kind) { var parser = CreateParser(kind); - CheckThrows(parser, new[] { "-Argument1", "foo", "-Argument1", "bar" }, CommandLineArgumentErrorCategory.DuplicateArgument, "Argument1", remainingArgumentCount: 2); + CheckThrows(parser, ["-Argument1", "foo", "-Argument1", "bar"], CommandLineArgumentErrorCategory.DuplicateArgument, "Argument1", remainingArgumentCount: 2); parser.Options.DuplicateArguments = ErrorMode.Allow; - var result = CheckSuccess(parser, new[] { "-Argument1", "foo", "-Argument1", "bar" }); + var result = CheckSuccess(parser, ["-Argument1", "foo", "-Argument1", "bar"]); Assert.AreEqual("bar", result.Argument1); bool handlerCalled = false; @@ -1218,12 +1223,12 @@ void handler(object? sender, DuplicateArgumentEventArgs e) // Handler is not called when duplicates not allowed. parser.Options.DuplicateArguments = ErrorMode.Error; - CheckThrows(parser, new[] { "-Argument1", "foo", "-Argument1", "bar" }, CommandLineArgumentErrorCategory.DuplicateArgument, "Argument1", remainingArgumentCount: 2); + CheckThrows(parser, ["-Argument1", "foo", "-Argument1", "bar"], CommandLineArgumentErrorCategory.DuplicateArgument, "Argument1", remainingArgumentCount: 2); Assert.IsFalse(handlerCalled); // Now it is called. parser.Options.DuplicateArguments = ErrorMode.Allow; - result = CheckSuccess(parser, new[] { "-Argument1", "foo", "-Argument1", "bar" }); + result = CheckSuccess(parser, ["-Argument1", "foo", "-Argument1", "bar"]); Assert.AreEqual("bar", result.Argument1); Assert.IsTrue(handlerCalled); @@ -1231,7 +1236,7 @@ void handler(object? sender, DuplicateArgumentEventArgs e) parser.Options.DuplicateArguments = ErrorMode.Warning; handlerCalled = false; keepOldValue = true; - result = CheckSuccess(parser, new[] { "-Argument1", "foo", "-Argument1", "bar" }); + result = CheckSuccess(parser, ["-Argument1", "foo", "-Argument1", "bar"]); Assert.AreEqual("foo", result.Argument1); Assert.IsTrue(handlerCalled); } @@ -1254,7 +1259,7 @@ public void TestConversion(ProviderKind kind) Assert.AreEqual(10, result.NullableMulti[1]!.Value); Assert.AreEqual(11, result.Nullable); - result = CheckSuccess(parser, new[] { "-ParseNullable", "", "-NullableMulti", "1", "", "2", "-ParseNullableMulti", "3", "", "4" }); + result = CheckSuccess(parser, ["-ParseNullable", "", "-NullableMulti", "1", "", "2", "-ParseNullableMulti", "3", "", "4"]); Assert.IsNull(result.ParseNullable); Assert.AreEqual(1, result.NullableMulti[0]!.Value); Assert.IsNull(result.NullableMulti[1]); @@ -1272,8 +1277,8 @@ public void TestConversion(ProviderKind kind) public void TestConversionInvalid(ProviderKind kind) { var parser = CreateParser(kind); - CheckThrows(parser, new[] { "-Nullable", "abc" }, CommandLineArgumentErrorCategory.ArgumentValueConversion, "Nullable", typeof(FormatException), 2); - CheckThrows(parser, new[] { "-Nullable", "12345678901234567890" }, CommandLineArgumentErrorCategory.ArgumentValueConversion, "Nullable", typeof(OverflowException), 2); + CheckThrows(parser, ["-Nullable", "abc"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Nullable", typeof(FormatException), 2); + CheckThrows(parser, ["-Nullable", "12345678901234567890"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Nullable", typeof(OverflowException), 2); } [TestMethod] @@ -1283,13 +1288,13 @@ public void TestDerivedClass(ProviderKind kind) var parser = CreateParser(kind); Assert.AreEqual("Base class attribute.", parser.Description); Assert.AreEqual(4, parser.Arguments.Length); - VerifyArguments(parser.Arguments, new[] - { + VerifyArguments(parser.Arguments, + [ new ExpectedArgument("BaseArg", typeof(string), ArgumentKind.SingleValue), new ExpectedArgument("DerivedArg", typeof(int), ArgumentKind.SingleValue), - new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("Version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); } [TestMethod] @@ -1319,27 +1324,27 @@ public void TestAutoPrefixAliases(ProviderKind kind) var parser = CreateParser(kind); // Shortest possible prefixes - var result = parser.Parse(new[] { "-pro", "foo", "-Po", "5", "-e" }); + var result = parser.Parse(["-pro", "foo", "-Po", "5", "-e"]); Assert.IsNotNull(result); Assert.AreEqual("foo", result.Protocol); Assert.AreEqual(5, result.Port); Assert.IsTrue(result.EnablePrefix); // Ambiguous prefix - CheckThrows(parser, new[] { "-p", "foo" }, CommandLineArgumentErrorCategory.UnknownArgument, "p", remainingArgumentCount: 2); + CheckThrows(parser, ["-p", "foo"], CommandLineArgumentErrorCategory.UnknownArgument, "p", remainingArgumentCount: 2); // Ambiguous due to alias. - CheckThrows(parser, new[] { "-pr", "foo" }, CommandLineArgumentErrorCategory.UnknownArgument, "pr", remainingArgumentCount: 2); + CheckThrows(parser, ["-pr", "foo"], CommandLineArgumentErrorCategory.UnknownArgument, "pr", remainingArgumentCount: 2); // Prefix of an alias. - result = parser.Parse(new[] { "-pre" }); + result = parser.Parse(["-pre"]); Assert.IsNotNull(result); Assert.IsTrue(result.EnablePrefix); // Disable auto prefix aliases. var options = new ParseOptions() { AutoPrefixAliases = false }; parser = CreateParser(kind, options); - CheckThrows(parser, new[] { "-pro", "foo", "-Po", "5", "-e" }, CommandLineArgumentErrorCategory.UnknownArgument, "pro", remainingArgumentCount: 5); + CheckThrows(parser, ["-pro", "foo", "-Po", "5", "-e"], CommandLineArgumentErrorCategory.UnknownArgument, "pro", remainingArgumentCount: 5); } [TestMethod] @@ -1361,17 +1366,17 @@ public void TestApplicationFriendlyName(ProviderKind kind) public void TestAutoPosition() { var parser = AutoPositionArguments.CreateParser(); - VerifyArguments(parser.Arguments, new[] - { + VerifyArguments(parser.Arguments, + [ new ExpectedArgument("BaseArg1", typeof(string), ArgumentKind.SingleValue) { Position = 0, IsRequired = true }, new ExpectedArgument("BaseArg2", typeof(int), ArgumentKind.SingleValue) { Position = 1 }, new ExpectedArgument("Arg1", typeof(string), ArgumentKind.SingleValue) { Position = 2 }, new ExpectedArgument("Arg2", typeof(int), ArgumentKind.SingleValue) { Position = 3 }, new ExpectedArgument("Arg3", typeof(int), ArgumentKind.SingleValue), new ExpectedArgument("BaseArg3", typeof(int), ArgumentKind.SingleValue), - new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = new[] { "?", "h" } }, + new ExpectedArgument("Help", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticHelp", Description = "Displays this help message.", IsSwitch = true, Aliases = ["?", "h"] }, new ExpectedArgument("Version", typeof(bool), ArgumentKind.Method) { MemberName = "AutomaticVersion", Description = "Displays version information.", IsSwitch = true }, - }); + ]); try { @@ -1613,7 +1618,7 @@ public static IEnumerable ProviderKinds => new[] { new object[] { ProviderKind.Reflection }, - new object[] { ProviderKind.Generated } + [ProviderKind.Generated] }; public static void AssertSpanEqual(ReadOnlySpan expected, ReadOnlySpan actual) diff --git a/src/Ookii.CommandLine/Conversion/EnumConverter.cs b/src/Ookii.CommandLine/Conversion/EnumConverter.cs index b737ff7..c2a07c5 100644 --- a/src/Ookii.CommandLine/Conversion/EnumConverter.cs +++ b/src/Ookii.CommandLine/Conversion/EnumConverter.cs @@ -80,22 +80,9 @@ public EnumConverter(Type enumType) /// The value was not valid for the enumeration type. /// public override object? Convert(string value, CultureInfo culture, CommandLineArgument argument) - { - try - { - return Enum.Parse(EnumType, value, true); - } - catch (ArgumentException ex) - { - throw CreateException(value, ex, argument); - } - catch (OverflowException ex) - { - throw CreateException(value, ex, argument); - } - } - #if NET6_0_OR_GREATER + => Convert(value.AsSpan(), culture, argument); + /// /// Converts a string span to the enumeration type. /// @@ -115,25 +102,26 @@ public EnumConverter(Type enumType) /// The value was not valid for the enumeration type. /// public override object? Convert(ReadOnlySpan value, CultureInfo culture, CommandLineArgument argument) +#endif { + var attribute = argument.Validators.OfType().FirstOrDefault(); try { - return Enum.Parse(EnumType, value, true); + return Enum.Parse(EnumType, value, !attribute?.CaseSensitive ?? true); } catch (ArgumentException ex) { - throw CreateException(value.ToString(), ex, argument); + throw CreateException(value.ToString(), ex, argument, attribute); } catch (OverflowException ex) { - throw CreateException(value.ToString(), ex, argument); + throw CreateException(value.ToString(), ex, argument, attribute); } } -#endif - private CommandLineArgumentException CreateException(string value, Exception inner, CommandLineArgument argument) + private CommandLineArgumentException CreateException(string value, Exception inner, CommandLineArgument argument, + ValidateEnumValueAttribute? attribute) { - var attribute = argument.Validators.OfType().FirstOrDefault(); var includeValues = attribute?.IncludeValuesInErrorMessage ?? true; var message = argument.Parser.StringProvider.ValidateEnumValueFailed(argument.ArgumentName, EnumType, value, includeValues); return new(message, argument.ArgumentName,CommandLineArgumentErrorCategory.ArgumentValueConversion, inner); diff --git a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs index 952ef0e..cca0715 100644 --- a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs +++ b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs @@ -39,21 +39,28 @@ namespace Ookii.CommandLine.Validation; /// public class ValidateEnumValueAttribute : ArgumentValidationWithHelpAttribute { - /// - /// Determines if the argument's value is defined. - /// - /// is not an argument with an enumeration type. - /// - public override bool IsValid(CommandLineArgument argument, object? value) - { - if (!argument.ElementType.IsEnum) - { - throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, - Properties.Resources.ArgumentNotEnumFormat, argument.ArgumentName)); - } - - return value == null || argument.ElementType.IsEnumDefined(value); - } + /// + /// Gets or sets a value that indicates whether values that do not match one of the + /// enumeration's defined values are allowed. + /// + /// + /// if values that are not defined by the enumeration are allowed; + /// otherwise, . The default value is . + /// + /// + /// + /// Non-defined values can be provided using the underlying numeric type of the enumeration. + /// If this property is , this validator will not check whether a value + /// provided in such a way actually is actually one of the enumeration's defined values. + /// + /// + /// Setting this to essentially makes this validator do nothing. It + /// is useful if you want to use it solely to list defined values in the usage help, or if + /// you want to use one of the other properties that affect the + /// without also checking for defined values. + /// + /// + public bool AllowNonDefinedValues { get; set; } /// /// Gets or sets a value that indicates whether the possible values of the enumeration @@ -72,6 +79,44 @@ public override bool IsValid(CommandLineArgument argument, object? value) /// public bool IncludeValuesInErrorMessage { get; set; } = true; + /// + /// Gets or sets a value that indicates whether enumeration value conversion is case sensitive. + /// + /// + /// if conversion is case sensitive; otherwise, . + /// The default value is . + /// + /// + /// + /// This property is not used by the class itself, + /// but by the class. Therefore, this property may not work if + /// a custom argument converter is used, unless that custom converter also checks this + /// property. + /// + /// + public bool CaseSensitive { get; set; } + + /// + /// Determines if the argument's value is defined. + /// + /// is not an argument with an enumeration type. + /// + public override bool IsValid(CommandLineArgument argument, object? value) + { + if (!argument.ElementType.IsEnum) + { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, + Properties.Resources.ArgumentNotEnumFormat, argument.ArgumentName)); + } + + if (AllowNonDefinedValues) + { + return true; + } + + return value == null || argument.ElementType.IsEnumDefined(value); + } + /// /// /// From 543ed9fca15b8e5dc3c3374ae9bbd78cdf8dfa59 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 5 Dec 2023 17:30:24 -0800 Subject: [PATCH 21/54] More enumeration validation options. --- src/Ookii.CommandLine.Tests/ArgumentTypes.cs | 7 +- .../CommandLineParserTest.cs | 9 ++ .../Conversion/EnumConverter.cs | 84 ++++++++++------- .../Validation/ValidateEnumValueAttribute.cs | 94 +++++++++++++++++-- 4 files changed, 149 insertions(+), 45 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs index 6131fa8..ff34dc7 100644 --- a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs +++ b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs @@ -451,9 +451,14 @@ public static void Arg3(int value) [CommandLineArgument] [Description("Day2 description.")] - [ValidateEnumValue(CaseSensitive = true, AllowNonDefinedValues = true)] + [ValidateEnumValue(CaseSensitive = true, AllowNonDefinedValues = true, AllowCommaSeparatedValues = false)] public DayOfWeek? Day2 { get; set; } + [CommandLineArgument(IsHidden = true)] + [Description("Day3 description.")] + [ValidateEnumValue(AllowNumericValues = false)] + public DayOfWeek Day3 { get; set; } + [CommandLineArgument] [Description("NotNull description.")] [ValidateNotNull] diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index 2da96ec..bba7192 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -1075,6 +1075,15 @@ public void TestValidation(ProviderKind kind) CheckSuccess(parser, ["-Day2", "Tuesday"]); CheckThrows(parser, ["-Day2", "tuesday"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day2", typeof(ArgumentException), remainingArgumentCount: 2); + // Disallow commas. + result = CheckSuccess(parser, ["-Day3", "Monday,Tuesday"]); + Assert.AreEqual(DayOfWeek.Wednesday, result.Day3); + CheckThrows(parser, ["-Day2", "Monday,Tuesday"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day2", remainingArgumentCount: 2); + + // Disallow numbers. + CheckThrows(parser, ["-Day3", "5"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day3", remainingArgumentCount: 2); + CheckThrows(parser, ["-Day3", "Tuesday,5"], CommandLineArgumentErrorCategory.ArgumentValueConversion, "Day3", remainingArgumentCount: 2); + // NotNull validator with Nullable. CheckThrows(parser, ["-NotNull", ""], CommandLineArgumentErrorCategory.ValidationFailed, "NotNull", remainingArgumentCount: 2); } diff --git a/src/Ookii.CommandLine/Conversion/EnumConverter.cs b/src/Ookii.CommandLine/Conversion/EnumConverter.cs index c2a07c5..bf30be4 100644 --- a/src/Ookii.CommandLine/Conversion/EnumConverter.cs +++ b/src/Ookii.CommandLine/Conversion/EnumConverter.cs @@ -11,14 +11,20 @@ namespace Ookii.CommandLine.Conversion; /// /// /// This converter performs a case insensitive conversion, and accepts the name of an enumeration -/// value, or its underlying value. In the latter case, the value does not need to be one of the -/// defined values of the enumeration; use the -/// attribute to ensure only defined enumeration values can be used. +/// value or a number representing the underlying type of the enumeration. Comma-separated values +/// that will be combined using bitwise-or are also excepted, regardless of whether the +/// enumeration uses the attribute. When using a numeric value, the +/// value does not need to be one of the defined values of the enumeration. /// /// -/// A comma-separated list of values is also accepted, which will be combined using a bitwise-or -/// operation. This is accepted regardless of whether the enumeration uses the -/// attribute. +/// Use the attribute to alter these behaviors. Applying +/// that attribute will ensure that only defined values are allowed. The +/// +/// property can be used to control the use of multiple values, and the +/// property +/// controls the use of numbers instead of names. Set the +/// property to +/// to enable case sensitive conversion. /// /// /// If conversion fails, the error message will check the @@ -79,32 +85,41 @@ public EnumConverter(Type enumType) /// /// The value was not valid for the enumeration type. /// - public override object? Convert(string value, CultureInfo culture, CommandLineArgument argument) + public override object? Convert(string value, CultureInfo culture, CommandLineArgument argument) +#if NET6_0_OR_GREATER + => Convert(value.AsSpan(), culture, argument); + + /// + /// Converts a string span to the enumeration type. + /// + /// The containing the string to convert. + /// The culture to use for the conversion. + /// + /// The that will use the converted value. + /// + /// An object representing the converted value. + /// + /// + /// This method performs the conversion using the + /// method. + /// + /// + /// + /// The value was not valid for the enumeration type. + /// + public override object? Convert(ReadOnlySpan value, CultureInfo culture, CommandLineArgument argument) +#endif + { + var attribute = argument.Validators.OfType().FirstOrDefault(); #if NET6_0_OR_GREATER - => Convert(value.AsSpan(), culture, argument); - - /// - /// Converts a string span to the enumeration type. - /// - /// The containing the string to convert. - /// The culture to use for the conversion. - /// - /// The that will use the converted value. - /// - /// An object representing the converted value. - /// - /// - /// This method performs the conversion using the - /// method. - /// - /// - /// - /// The value was not valid for the enumeration type. - /// - public override object? Convert(ReadOnlySpan value, CultureInfo culture, CommandLineArgument argument) + if (attribute != null && !attribute.ValidateBeforeConversion(argument, value)) +#else + if (attribute != null && !attribute.ValidateBeforeConversion(argument, value.AsSpan())) #endif - { - var attribute = argument.Validators.OfType().FirstOrDefault(); + { + throw CreateException(value.ToString(), null, argument, attribute); + } + try { return Enum.Parse(EnumType, value, !attribute?.CaseSensitive ?? true); @@ -119,11 +134,12 @@ public EnumConverter(Type enumType) } } - private CommandLineArgumentException CreateException(string value, Exception inner, CommandLineArgument argument, + private CommandLineArgumentException CreateException(string value, Exception? inner, CommandLineArgument argument, ValidateEnumValueAttribute? attribute) { - var includeValues = attribute?.IncludeValuesInErrorMessage ?? true; - var message = argument.Parser.StringProvider.ValidateEnumValueFailed(argument.ArgumentName, EnumType, value, includeValues); - return new(message, argument.ArgumentName,CommandLineArgumentErrorCategory.ArgumentValueConversion, inner); + string message = attribute?.GetErrorMessage(argument, value) + ?? argument.Parser.StringProvider.ValidateEnumValueFailed(argument.ArgumentName, EnumType, value, true); + + return new(message, argument.ArgumentName, CommandLineArgumentErrorCategory.ArgumentValueConversion, inner); } } diff --git a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs index cca0715..c39742e 100644 --- a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs +++ b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs @@ -6,7 +6,7 @@ namespace Ookii.CommandLine.Validation; /// /// Validates whether the value of an enumeration type is one of the defined values for that -/// type. +/// type, and provides additional conversion options for enumeration types. /// /// /// @@ -25,11 +25,18 @@ namespace Ookii.CommandLine.Validation; /// enumeration, by using the method. /// /// +/// This validator can also alter the behavior of the class, through +/// the , , and +/// properties. These properties are only effective if +/// the default class is used, or a custom converter that also checks +/// them. +/// +/// /// In addition, this validator provides usage help listing all the possible values. If the /// enumeration has a lot of values, you may wish to turn this off by setting the -/// property to -/// . Similarly, you can avoid listing all the values in the error -/// message by setting the property to +/// +/// property to . Similarly, you can avoid listing all the values in the +/// error message by setting the property to /// . /// /// @@ -96,6 +103,42 @@ public class ValidateEnumValueAttribute : ArgumentValidationWithHelpAttribute /// public bool CaseSensitive { get; set; } + /// + /// Gets or sets a value that indicates whether the value provided by the user can use commas + /// to provide multiple values that will be combined with bitwise-or. + /// + /// + /// if comma-separated values are allowed; otherwise, + /// . The default value is . + /// + /// + /// + /// This property is not used by the class itself, + /// but by the class. Therefore, this property may not work if + /// a custom argument converter is used, unless that custom converter also checks this + /// property. + /// + /// + public bool AllowCommaSeparatedValues { get; set; } = true; + + /// + /// Gets or sets a value that indicates whether the value provided by the user can the + /// underlying numeric type of the enumeration. + /// + /// + /// if numeric values are allowed; otherwise, to + /// allow only value names. The default value is . + /// + /// + /// + /// This property is not used by the class itself, + /// but by the class. Therefore, this property may not work if + /// a custom argument converter is used, unless that custom converter also checks this + /// property. + /// + /// + public bool AllowNumericValues { get; set; } = true; + /// /// Determines if the argument's value is defined. /// @@ -109,12 +152,7 @@ public override bool IsValid(CommandLineArgument argument, object? value) Properties.Resources.ArgumentNotEnumFormat, argument.ArgumentName)); } - if (AllowNonDefinedValues) - { - return true; - } - - return value == null || argument.ElementType.IsEnumDefined(value); + return AllowNonDefinedValues || value == null || argument.ElementType.IsEnumDefined(value); } /// @@ -139,4 +177,40 @@ protected override string GetUsageHelpCore(CommandLineArgument argument) public override string GetErrorMessage(CommandLineArgument argument, object? value) => argument.Parser.StringProvider.ValidateEnumValueFailed(argument.ArgumentName, argument.ElementType, value, IncludeValuesInErrorMessage); + + internal bool ValidateBeforeConversion(CommandLineArgument argument, ReadOnlySpan value) + { + if (!AllowCommaSeparatedValues && value.IndexOf(',') >= 0) + { + return false; + } + + if (!AllowNumericValues) + { + var current = value; + while (current.Length > 0) + { + if (char.IsDigit(current[0]) || current.StartsWith(argument.Parser.Culture.NumberFormat.NegativeSign.AsSpan())) + { + return false; + } + + if (!AllowCommaSeparatedValues) + { + // There can't be any commas, checked above. + break; + } + + var index = current.IndexOf(','); + if (index < 0) + { + break; + } + + current = current.Slice(index + 1); + } + } + + return true; + } } From e7edca548707dcc27dd5f897181be3bdd5bddf3f Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Wed, 6 Dec 2023 16:58:32 -0800 Subject: [PATCH 22/54] Add a warning and code fix to add the GeneratedParserAttribute. --- .../AnalyzerReleases.Shipped.md | 3 + .../AnalyzerReleases.Unshipped.md | 8 ++ .../Diagnostics.cs | 31 ++++++-- .../Ookii.CommandLine.Generator.csproj | 3 +- .../ParserAnalyzer.cs | 63 ++++++++++++++++ .../ParserCodeFixProvider.cs | 74 +++++++++++++++++++ .../Properties/Resources.Designer.cs | 27 +++++++ .../Properties/Resources.resx | 9 +++ src/Ookii.CommandLine.Tests/ArgumentTypes.cs | 2 +- src/Ookii.CommandLine.Tests/CommandTypes.cs | 6 +- 10 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md create mode 100644 src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md create mode 100644 src/Ookii.CommandLine.Generator/ParserAnalyzer.cs create mode 100644 src/Ookii.CommandLine.Generator/ParserCodeFixProvider.cs diff --git a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..f673512 --- /dev/null +++ b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..ffe6df6 --- /dev/null +++ b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md @@ -0,0 +1,8 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +OCL0040 | Ookii.CommandLine | Warning | Diagnostics \ No newline at end of file diff --git a/src/Ookii.CommandLine.Generator/Diagnostics.cs b/src/Ookii.CommandLine.Generator/Diagnostics.cs index dae08fc..abbf4fb 100644 --- a/src/Ookii.CommandLine.Generator/Diagnostics.cs +++ b/src/Ookii.CommandLine.Generator/Diagnostics.cs @@ -366,15 +366,30 @@ public static Diagnostic UnsupportedInitializerSyntax(ISymbol symbol, Location l location, symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + public static readonly DiagnosticDescriptor ParserShouldBeGeneratedDescriptor = CreateDiagnosticDescriptor( + "OCL0040", + nameof(Resources.ParserShouldBeGeneratedTitle), + nameof(Resources.ParserShouldBeGeneratedMessageFormat), + DiagnosticSeverity.Warning); + + public static Diagnostic ParserShouldBeGenerated(ISymbol symbol) + => Diagnostic.Create( + ParserShouldBeGeneratedDescriptor, + symbol.Locations.FirstOrDefault(), + symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + private static Diagnostic CreateDiagnostic(string id, string titleResource, string messageResource, DiagnosticSeverity severity, Location? location, params object?[]? messageArgs) => Diagnostic.Create( - new DiagnosticDescriptor( - id, - new LocalizableResourceString(titleResource, Resources.ResourceManager, typeof(Resources)), - new LocalizableResourceString(messageResource, Resources.ResourceManager, typeof(Resources)), - Category, - severity, - isEnabledByDefault: true, - helpLinkUri: $"https://www.ookii.org/Link/CommandLineGeneratorError#{id.ToLowerInvariant()}"), + CreateDiagnosticDescriptor(id, titleResource, messageResource, severity), location, messageArgs); + + private static DiagnosticDescriptor CreateDiagnosticDescriptor(string id, string titleResource, string messageResource, DiagnosticSeverity severity) + => new( + id, + new LocalizableResourceString(titleResource, Resources.ResourceManager, typeof(Resources)), + new LocalizableResourceString(messageResource, Resources.ResourceManager, typeof(Resources)), + Category, + severity, + isEnabledByDefault: true, + helpLinkUri: $"https://www.ookii.org/Link/CommandLineGeneratorError#{id.ToLowerInvariant()}"); } diff --git a/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj b/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj index 2df9996..22e639d 100644 --- a/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj +++ b/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj @@ -3,7 +3,7 @@ netstandard2.0 false - 11.0 + 12.0 enable enable true @@ -19,6 +19,7 @@ + diff --git a/src/Ookii.CommandLine.Generator/ParserAnalyzer.cs b/src/Ookii.CommandLine.Generator/ParserAnalyzer.cs new file mode 100644 index 0000000..93c60be --- /dev/null +++ b/src/Ookii.CommandLine.Generator/ParserAnalyzer.cs @@ -0,0 +1,63 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; + +namespace Ookii.CommandLine.Generator; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class ParserAnalyzer : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Diagnostics.ParserShouldBeGeneratedDescriptor); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); + } + + private void AnalyzeNamedType(SymbolAnalysisContext context) + { + var symbol = (INamedTypeSymbol)context.Symbol; + var tree = symbol.Locations[0].SourceTree; + var languageVersion = (tree?.Options as CSharpParseOptions)?.LanguageVersion ?? LanguageVersion.CSharp1; + if (languageVersion < LanguageVersion.CSharp8 || + symbol.IsAbstract || + symbol.ContainingType != null || + symbol.IsGenericType) + { + // Unsupported. + return; + } + + var typeHelper = new TypeHelper(context.Compilation); + if (typeHelper.GeneratedParserAttribute == null || typeHelper.CommandLineArgumentAttribute == null) + { + // Required types don't exist somehow. + return; + } + + if (symbol.GetAttribute(typeHelper.GeneratedParserAttribute) != null) + { + // Class is already using the attribute. + return; + } + + var argumentAttribute = typeHelper.CommandLineArgumentAttribute; + foreach (var member in symbol.GetMembers()) + { + if (member.DeclaredAccessibility == Accessibility.Public && + member.Kind is SymbolKind.Property or SymbolKind.Method) + { + if (member.GetAttribute(argumentAttribute) != null) + { + // Found a member with the CommandLineArgumentAttribute on a type that doesn't + // have the GeneratedParserAttribute. + context.ReportDiagnostic(Diagnostics.ParserShouldBeGenerated(symbol)); + break; + } + } + } + } +} diff --git a/src/Ookii.CommandLine.Generator/ParserCodeFixProvider.cs b/src/Ookii.CommandLine.Generator/ParserCodeFixProvider.cs new file mode 100644 index 0000000..ded0b5e --- /dev/null +++ b/src/Ookii.CommandLine.Generator/ParserCodeFixProvider.cs @@ -0,0 +1,74 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using System.Collections.Immutable; +using System.Composition; + +namespace Ookii.CommandLine.Generator; +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ParserCodeFixProvider)), Shared] +public class ParserCodeFixProvider : CodeFixProvider +{ + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(Diagnostics.ParserShouldBeGeneratedDescriptor.Id); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var span = diagnostic.Location.SourceSpan; + + // Find the type declaration. + var declaration = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType().FirstOrDefault(); + if (declaration == null) + { + return; + } + + context.RegisterCodeFix( + CodeAction.Create( + Properties.Resources.GeneratedParserCodeFixTitle, + (token) => AddGeneratedParserAttribute(context.Document, declaration, token), + nameof(Properties.Resources.GeneratedParserCodeFixTitle)), + diagnostic); + } + + private static async Task AddGeneratedParserAttribute(Document document, TypeDeclarationSyntax declaration, + CancellationToken token) + { + var attr = SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("GeneratedParser")); + var attrList = SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(attr)); + + // Add the attribute. + var newDeclaration = declaration.AddAttributeLists(attrList); + + // Add partial keyword if not already there. + if (!newDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + { + newDeclaration = newDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + } + + newDeclaration = newDeclaration.WithAdditionalAnnotations(Formatter.Annotation); + if (await document.GetSyntaxRootAsync(token).ConfigureAwait(false) is not CompilationUnitSyntax oldRoot) + { + return document; + } + + var newRoot = oldRoot.ReplaceNode(declaration, newDeclaration); + + // Add a using statement if needed. + if (!oldRoot.Usings.Any(u => u.Name.ToString() == "Ookii.CommandLine")) + { + newRoot = newRoot.AddUsings( + SyntaxFactory.UsingDirective( + SyntaxFactory.QualifiedName( + SyntaxFactory.IdentifierName("Ookii"), + SyntaxFactory.IdentifierName("CommandLine")))); + } + + return document.WithSyntaxRoot(newRoot); + } +} diff --git a/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs b/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs index 7b474f6..8d937ce 100644 --- a/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs +++ b/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs @@ -312,6 +312,15 @@ internal static string GeneratedCustomParsingCommandTitle { } } + /// + /// Looks up a localized string similar to Add GeneratedParserAttribute. + /// + internal static string GeneratedParserCodeFixTitle { + get { + return ResourceManager.GetString("GeneratedParserCodeFixTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to The {0} attribute is ignored for the dictionary argument defined by {1} that has the ArgumentConverterAttribute attribute.. /// @@ -672,6 +681,24 @@ internal static string ParentCommandStringNotSupportedTitle { } } + /// + /// Looks up a localized string similar to The command line arguments class '{0}' should use the GeneratedParserAttribute.. + /// + internal static string ParserShouldBeGeneratedMessageFormat { + get { + return ResourceManager.GetString("ParserShouldBeGeneratedMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The command line arguments class should use the GeneratedParserAttribute.. + /// + internal static string ParserShouldBeGeneratedTitle { + get { + return ResourceManager.GetString("ParserShouldBeGeneratedTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to The positional argument defined by {0} comes after {1}, which is a multi-value argument and must come last.. /// diff --git a/src/Ookii.CommandLine.Generator/Properties/Resources.resx b/src/Ookii.CommandLine.Generator/Properties/Resources.resx index 40bd379..4be5a10 100644 --- a/src/Ookii.CommandLine.Generator/Properties/Resources.resx +++ b/src/Ookii.CommandLine.Generator/Properties/Resources.resx @@ -201,6 +201,9 @@ The GeneratedParserAttribute cannot be used with a class that implements the ICommandWithCustomParsing interface. + + Add GeneratedParserAttribute + The {0} attribute is ignored for the dictionary argument defined by {1} that has the ArgumentConverterAttribute attribute. @@ -321,6 +324,12 @@ The ParentCommandAttribute must use the typeof keyword. + + The command line arguments class '{0}' should use the GeneratedParserAttribute. + + + The command line arguments class should use the GeneratedParserAttribute. + The positional argument defined by {0} comes after {1}, which is a multi-value argument and must come last. diff --git a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs index ff34dc7..e8bdbbe 100644 --- a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs +++ b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs @@ -14,7 +14,7 @@ #nullable disable // We deliberately have some properties and methods that cause warnings, so disable those. -#pragma warning disable OCL0017,OCL0018,OCL0020,OCL0023,OCL0029,OCL0033,OCL0038,OCL0039 +#pragma warning disable OCL0017,OCL0018,OCL0020,OCL0023,OCL0029,OCL0033,OCL0038,OCL0039,OCL0040 namespace Ookii.CommandLine.Tests; diff --git a/src/Ookii.CommandLine.Tests/CommandTypes.cs b/src/Ookii.CommandLine.Tests/CommandTypes.cs index d9681ce..8a2bab7 100644 --- a/src/Ookii.CommandLine.Tests/CommandTypes.cs +++ b/src/Ookii.CommandLine.Tests/CommandTypes.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -#pragma warning disable OCL0033,OCL0034 +#pragma warning disable OCL0033,OCL0034,OCL0040 namespace Ookii.CommandLine.Tests; @@ -78,7 +78,7 @@ public int Run() // Not generated to test registration of plain commands without generation. [Command(IsHidden = true)] [Description("Async command description.")] -partial class AsyncCommand : IAsyncCommand +class AsyncCommand : IAsyncCommand { [CommandLineArgument(Position = 0)] [Description("Argument description.")] @@ -99,7 +99,7 @@ public Task RunAsync() [Command(IsHidden = true)] [Description("Async command description.")] -partial class AsyncCancelableCommand : AsyncCancelableCommandBase +class AsyncCancelableCommand : AsyncCancelableCommandBase { [CommandLineArgument(Position = 0)] [Description("Argument description.")] From 04785e15c4db33b8ed7d2e320eb62d68e9af2972 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 7 Dec 2023 13:22:27 -0800 Subject: [PATCH 23/54] Change how IAsyncCancelableCommand works. --- src/Ookii.CommandLine.Tests/CommandTypes.cs | 6 +- .../Commands/AsyncCancelableCommandBase.cs | 37 ---------- .../Commands/AsyncCommandBase.cs | 16 ++--- .../Commands/CommandManager.cs | 67 ++++++++++--------- .../Commands/IAsyncCancelableCommand.cs | 53 ++++++--------- .../Commands/IAsyncCommand.cs | 2 +- 6 files changed, 66 insertions(+), 115 deletions(-) delete mode 100644 src/Ookii.CommandLine/Commands/AsyncCancelableCommandBase.cs diff --git a/src/Ookii.CommandLine.Tests/CommandTypes.cs b/src/Ookii.CommandLine.Tests/CommandTypes.cs index 8a2bab7..3ff4d22 100644 --- a/src/Ookii.CommandLine.Tests/CommandTypes.cs +++ b/src/Ookii.CommandLine.Tests/CommandTypes.cs @@ -99,15 +99,15 @@ public Task RunAsync() [Command(IsHidden = true)] [Description("Async command description.")] -class AsyncCancelableCommand : AsyncCancelableCommandBase +class AsyncCancelableCommand : AsyncCommandBase { [CommandLineArgument(Position = 0)] [Description("Argument description.")] public int Value { get; set; } - public override async Task RunAsync(CancellationToken cancellationToken) + public override async Task RunAsync() { - await Task.Delay(Value, cancellationToken); + await Task.Delay(Value, CancellationToken); return Value; } } diff --git a/src/Ookii.CommandLine/Commands/AsyncCancelableCommandBase.cs b/src/Ookii.CommandLine/Commands/AsyncCancelableCommandBase.cs deleted file mode 100644 index 7a4995d..0000000 --- a/src/Ookii.CommandLine/Commands/AsyncCancelableCommandBase.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Ookii.CommandLine.Commands; - -/// -/// Base class for asynchronous commands with cancellation support that want the -/// method to invoke the -/// method. -/// -/// -/// -/// This class is provided for convenience for creating asynchronous commands without having to -/// implement the method. -/// -/// -/// This class implements the interface, which can use the -/// cancellation token passed to the -/// method. -/// -/// -/// If you do not need the cancellation token, you can implement the -/// interface or derive from the class instead. -/// -/// -/// -public abstract class AsyncCancelableCommandBase : IAsyncCancelableCommand -{ - /// - /// Calls the method and waits synchronously for it to complete. - /// - /// The exit code of the command. - public virtual int Run() => Task.Run(() => RunAsync(default)).ConfigureAwait(false).GetAwaiter().GetResult(); - - /// - public abstract Task RunAsync(CancellationToken token); -} diff --git a/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs b/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs index 96dc75f..b51ff5e 100644 --- a/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs +++ b/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Ookii.CommandLine.Commands; @@ -11,16 +12,13 @@ namespace Ookii.CommandLine.Commands; /// This class is provided for convenience for creating asynchronous commands without having to /// implement the method. /// -/// -/// If you want to use the cancellation token passed to the -/// -/// method, you should instead implement the interface or -/// derive from the class. -/// /// /// -public abstract class AsyncCommandBase : IAsyncCommand +public abstract class AsyncCommandBase : IAsyncCancelableCommand { + /// + public CancellationToken CancellationToken { get; set; } + /// /// Calls the method and waits synchronously for it to complete. /// @@ -29,4 +27,6 @@ public abstract class AsyncCommandBase : IAsyncCommand /// public abstract Task RunAsync(); + + } diff --git a/src/Ookii.CommandLine/Commands/CommandManager.cs b/src/Ookii.CommandLine/Commands/CommandManager.cs index d8fc055..006ce32 100644 --- a/src/Ookii.CommandLine/Commands/CommandManager.cs +++ b/src/Ookii.CommandLine/Commands/CommandManager.cs @@ -735,8 +735,7 @@ public IEnumerable GetCommands() /// /// /// A task representing the asynchronous run operation. The result is the value returned - /// by , - /// , + /// by or /// , or if the command /// could not be created. /// @@ -744,10 +743,10 @@ public IEnumerable GetCommands() /// /// This function creates the command by invoking the /// method. If the command implements the interface, it - /// invokes the method; if - /// the command implements the interface, it invokes the - /// method; otherwise, it invokes the - /// method on the command. + /// sets the + /// property. If the command implements the interface, it invokes + /// the method; otherwise, it invokes + /// the method on the command. /// /// /// Commands that don't meet the criteria of the @@ -788,8 +787,7 @@ public IEnumerable GetCommands() /// /// /// A task representing the asynchronous run operation. The result is the value returned - /// by , - /// , + /// by or /// , or if the command /// could not be created. /// @@ -797,10 +795,10 @@ public IEnumerable GetCommands() /// /// This function creates the command by invoking the /// method. If the command implements the interface, it - /// invokes the method; if - /// the command implements the interface, it invokes the - /// method; otherwise, it invokes the - /// method on the command. + /// sets the + /// property. If the command implements the interface, it invokes + /// the method; otherwise, it invokes + /// the method on the command. /// /// /// Commands that don't meet the criteria of the @@ -843,8 +841,7 @@ public IEnumerable GetCommands() /// /// /// A task representing the asynchronous run operation. The result is the value returned - /// by , - /// , + /// by or /// , or if the command /// could not be created. /// @@ -852,10 +849,10 @@ public IEnumerable GetCommands() /// /// This function creates the command by invoking the /// method. If the command implements the interface, it - /// invokes the method; if - /// the command implements the interface, it invokes the - /// method; otherwise, it invokes the - /// method on the command. + /// sets the + /// property. If the command implements the interface, it invokes + /// the method; otherwise, it invokes + /// the method on the command. /// /// /// Commands that don't meet the criteria of the @@ -898,8 +895,7 @@ public IEnumerable GetCommands() /// /// /// A task representing the asynchronous run operation. The result is the value returned - /// by , - /// , + /// by or /// , or if the command /// could not be created. /// @@ -907,10 +903,10 @@ public IEnumerable GetCommands() /// /// This function creates the command by invoking the /// method. If the command implements the interface, it - /// invokes the method; if - /// the command implements the interface, it invokes the - /// method; otherwise, it invokes the - /// method on the command. + /// sets the + /// property. If the command implements the interface, it invokes + /// the method; otherwise, it invokes + /// the method on the command. /// /// /// Commands that don't meet the criteria of the @@ -955,10 +951,10 @@ public IEnumerable GetCommands() /// /// This function creates the command by invoking the /// method. If the command implements the interface, it - /// invokes the method; if - /// the command implements the interface, it invokes the - /// method; otherwise, it invokes the - /// method on the command. + /// sets the + /// property. If the command implements the interface, it invokes + /// the method; otherwise, it invokes + /// the method on the command. /// /// /// Commands that don't meet the criteria of the @@ -1087,11 +1083,16 @@ private IEnumerable GetCommandsUnsortedAndFiltered() private static async Task RunCommandAsync(ICommand? command, CancellationToken cancellationToken) { - return command switch + if (command is IAsyncCancelableCommand asyncCancelableCommand) + { + asyncCancelableCommand.CancellationToken = cancellationToken; + return await asyncCancelableCommand.RunAsync(); + } + else if (command is IAsyncCommand asyncCommand) { - IAsyncCancelableCommand asyncCancelableCommand => await asyncCancelableCommand.RunAsync(cancellationToken), - IAsyncCommand asyncCommand => await asyncCommand.RunAsync(), - _ => command?.Run() - }; + return await asyncCommand.RunAsync(); + } + + return command?.Run(); } } diff --git a/src/Ookii.CommandLine/Commands/IAsyncCancelableCommand.cs b/src/Ookii.CommandLine/Commands/IAsyncCancelableCommand.cs index ac7b946..0e94fdd 100644 --- a/src/Ookii.CommandLine/Commands/IAsyncCancelableCommand.cs +++ b/src/Ookii.CommandLine/Commands/IAsyncCancelableCommand.cs @@ -8,48 +8,35 @@ namespace Ookii.CommandLine.Commands; /// /// /// -/// This interface adds a method to the -/// interface, that will be invoked by the -/// method and its overloads. This allows you to write tasks that use asynchronous code. This -/// method receives the that was passed to the -/// method. +/// This interface adds a property to the +/// interface. The +/// method +/// and its overloads will set this property prior to calling the +/// method. /// /// -/// Use the class as a base class for your command to get -/// a default implementation of the -/// -/// -/// If you do not need the cancellation token, you can implement the -/// interface or derive from the class instead. +/// Use the class as a base class for your command to get a default +/// implementation of the method and the property. /// /// -public interface IAsyncCancelableCommand : ICommand +public interface IAsyncCancelableCommand : IAsyncCommand { /// - /// Runs the command asynchronously. - /// - /// - /// The cancellation token that was passed to the - /// + /// Gets or sets the cancellation token that can be used by the /// method. - /// - /// - /// A task that represents the asynchronous run operation. The result of the task is the - /// exit code for the command. - /// + /// + /// + /// A instance, or + /// if none was set. + /// /// /// - /// Typically, your application's Main() method should return the exit code of the - /// command that was executed. - /// - /// - /// This method will only be invoked if you run commands with the - /// - /// method or one of its overloads. Typically, it's recommended to implement the - /// method to invoke this method and wait for - /// it. Use the class for a default implementation - /// that does this. + /// If a was passed to the + /// method, + /// this property will be set to that token prior to the + /// method being called. /// /// - Task RunAsync(CancellationToken cancellationToken); + public CancellationToken CancellationToken { get; set; } } diff --git a/src/Ookii.CommandLine/Commands/IAsyncCommand.cs b/src/Ookii.CommandLine/Commands/IAsyncCommand.cs index 419f4d1..d5a39e2 100644 --- a/src/Ookii.CommandLine/Commands/IAsyncCommand.cs +++ b/src/Ookii.CommandLine/Commands/IAsyncCommand.cs @@ -19,7 +19,7 @@ namespace Ookii.CommandLine.Commands; /// If you want to use the cancellation token passed to the /// /// method, you should instead implement the interface or -/// derive from the class. +/// derive from the class. /// /// public interface IAsyncCommand : ICommand From 2d89a765b17fec6449da4f04e59a53fb16bc4391 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 7 Dec 2023 16:04:33 -0800 Subject: [PATCH 24/54] Don't show warning for value types. --- src/Ookii.CommandLine.Generator/ParserAnalyzer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ookii.CommandLine.Generator/ParserAnalyzer.cs b/src/Ookii.CommandLine.Generator/ParserAnalyzer.cs index 93c60be..e94c890 100644 --- a/src/Ookii.CommandLine.Generator/ParserAnalyzer.cs +++ b/src/Ookii.CommandLine.Generator/ParserAnalyzer.cs @@ -25,7 +25,8 @@ private void AnalyzeNamedType(SymbolAnalysisContext context) if (languageVersion < LanguageVersion.CSharp8 || symbol.IsAbstract || symbol.ContainingType != null || - symbol.IsGenericType) + symbol.IsGenericType || + !symbol.IsReferenceType) { // Unsupported. return; From 6cdc8b740306036a767d9ddac06bfaeb5cc51177 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 7 Dec 2023 17:14:22 -0800 Subject: [PATCH 25/54] Add -- escape support. --- src/Ookii.CommandLine.Tests/ArgumentTypes.cs | 16 ++++++ .../CommandLineParserTest.cs | 53 +++++++++++++++++++ src/Ookii.CommandLine/CommandLineParser.cs | 46 ++++++++++++---- src/Ookii.CommandLine/ParseOptions.cs | 50 +++++++++++++++-- .../ParseOptionsAttribute.cs | 37 +++++++++++-- .../PrefixTerminationMode.cs | 27 ++++++++++ 6 files changed, 213 insertions(+), 16 deletions(-) create mode 100644 src/Ookii.CommandLine/PrefixTerminationMode.cs diff --git a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs index e8bdbbe..a69f747 100644 --- a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs +++ b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs @@ -714,3 +714,19 @@ partial class DefaultValueFormatArguments [Description("Another argument.")] public double Argument2 { get; set; } } + +[GeneratedParser] +partial class PrefixTerminationArguments +{ + [CommandLineArgument(Position = 0)] + public string Arg1 { get; set; } + + [CommandLineArgument(Position = 1)] + public string Arg2 { get; set; } + + [CommandLineArgument(Position = 2)] + public string Arg3 { get; set; } + + [CommandLineArgument(Position = 3)] + public string Arg4 { get; set; } +} diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index bba7192..2711621 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -875,6 +875,59 @@ public void TestMethodArguments(ProviderKind kind) Assert.AreEqual(nameof(MethodArguments.CancelModeNone), MethodArguments.CalledMethodName); } + [TestMethod] + [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] + public void TestPrefixTermination(ProviderKind kind) + { + var options = new ParseOptions() + { + PrefixTermination = PrefixTerminationMode.PositionalOnly + }; + + var parser = CreateParser(kind, options); + Assert.AreEqual("--", parser.LongArgumentNamePrefix); + Assert.AreEqual(ParsingMode.Default, parser.Mode); + var result = CheckSuccess(parser, ["Foo", "--", "-Arg4", "Bar"]); + Assert.AreEqual("Foo", result.Arg1); + Assert.AreEqual("-Arg4", result.Arg2); + Assert.AreEqual("Bar", result.Arg3); + Assert.IsNull(result.Arg4); + options.PrefixTermination = PrefixTerminationMode.CancelWithSuccess; + result = CheckSuccess(parser, ["Foo", "--", "-Arg4", "Bar"], "--", 2); + Assert.AreEqual("Foo", result.Arg1); + Assert.IsNull(result.Arg2); + Assert.IsNull(result.Arg3); + Assert.IsNull(result.Arg4); + options.PrefixTermination = PrefixTerminationMode.CancelWithSuccess; + } + + [TestMethod] + [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] + public void TestPrefixTerminationLongShort(ProviderKind kind) + { + var options = new ParseOptions() + { + IsPosix = true, + PrefixTermination = PrefixTerminationMode.PositionalOnly + }; + + var parser = CreateParser(kind, options); + Assert.AreEqual("--", parser.LongArgumentNamePrefix); + Assert.AreEqual(ParsingMode.LongShort, parser.Mode); + var result = CheckSuccess(parser, ["--arg4", "Foo", "--", "--arg1", "Bar"]); + Assert.AreEqual("Foo", result.Arg4); + Assert.AreEqual("--arg1", result.Arg1); + Assert.AreEqual("Bar", result.Arg2); + Assert.IsNull(result.Arg3); + options.PrefixTermination = PrefixTerminationMode.CancelWithSuccess; + result = CheckSuccess(parser, ["Foo", "--", "--arg4", "Bar"], "--", 2); + Assert.AreEqual("Foo", result.Arg1); + Assert.IsNull(result.Arg2); + Assert.IsNull(result.Arg3); + Assert.IsNull(result.Arg4); + options.PrefixTermination = PrefixTerminationMode.CancelWithSuccess; + } + [TestMethod] [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] public void TestAutomaticArgumentConflict(ProviderKind kind) diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index 6955601..d511db8 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -360,7 +361,7 @@ public CommandLineParser(ArgumentProvider provider, ParseOptions? options = null _argumentNamePrefixes = DetermineArgumentNamePrefixes(_parseOptions); _nameValueSeparators = DetermineNameValueSeparators(_parseOptions); var prefixInfos = _argumentNamePrefixes.Select(p => new PrefixInfo { Prefix = p, Short = true }); - if (_mode == ParsingMode.LongShort) + if (_mode == ParsingMode.LongShort || _parseOptions.PrefixTerminationOrDefault != PrefixTerminationMode.None) { _longArgumentNamePrefix = _parseOptions.LongArgumentNamePrefixOrDefault; if (string.IsNullOrWhiteSpace(_longArgumentNamePrefix)) @@ -368,9 +369,12 @@ public CommandLineParser(ArgumentProvider provider, ParseOptions? options = null throw new ArgumentException(Properties.Resources.EmptyArgumentNamePrefix, nameof(options)); } - var longInfo = new PrefixInfo { Prefix = _longArgumentNamePrefix, Short = false }; - prefixInfos = prefixInfos.Append(longInfo); - _argumentsByShortName = new(new CharComparer(comparison)); + if (_mode == ParsingMode.LongShort) + { + var longInfo = new PrefixInfo { Prefix = _longArgumentNamePrefix, Short = false }; + prefixInfos = prefixInfos.Append(longInfo); + _argumentsByShortName = new(new CharComparer(comparison)); + } } _sortedPrefixes = prefixInfos.OrderByDescending(info => info.Prefix.Length).ToArray(); @@ -431,14 +435,18 @@ public CommandLineParser(ArgumentProvider provider, ParseOptions? options = null /// /// /// The prefix for long argument names, or if the - /// property is not . + /// property is not and the + /// property is + /// . /// /// /// /// The long argument prefix is only used if the property is - /// . See to - /// get the prefixes for short argument names, or for argument names if the - /// property is . + /// , or if the + /// property is not + /// . See to + /// get the prefixes for short argument names, or for all argument names if the + /// property is . /// /// /// @@ -1479,12 +1487,30 @@ private void VerifyPositionalArgumentRules() HelpRequested = false; int positionalArgumentIndex = 0; + bool optionalOnly = false; var cancelParsing = CancelMode.None; CommandLineArgument? lastArgument = null; for (x = 0; x < args.Length; ++x) { string arg = args.Span[x]; - var argumentNamePrefix = CheckArgumentNamePrefix(arg); + if (!optionalOnly && + _parseOptions.PrefixTerminationOrDefault != PrefixTerminationMode.None && + arg == _longArgumentNamePrefix) + { + if (_parseOptions.PrefixTerminationOrDefault == PrefixTerminationMode.PositionalOnly) + { + optionalOnly = true; + continue; + } + else if (_parseOptions.PrefixTerminationOrDefault == PrefixTerminationMode.CancelWithSuccess) + { + cancelParsing = CancelMode.Success; + lastArgument = null; + break; + } + } + + var argumentNamePrefix = optionalOnly ? null : CheckArgumentNamePrefix(arg); if (argumentNamePrefix != null) { // If white space was the value separator, this function returns the index of argument containing the value for the named argument. @@ -1568,7 +1594,7 @@ private void VerifyPositionalArgumentRules() ParseResult = cancelParsing == CancelMode.None ? ParseResult.FromSuccess() - : ParseResult.FromSuccess(lastArgument!.ArgumentName, args.Slice(x + 1)); + : ParseResult.FromSuccess(lastArgument?.ArgumentName ?? LongArgumentNamePrefix, args.Slice(x + 1)); // Reset to false in case it was set by a method argument that didn't cancel parsing. HelpRequested = false; diff --git a/src/Ookii.CommandLine/ParseOptions.cs b/src/Ookii.CommandLine/ParseOptions.cs index 8b23138..20c60b7 100644 --- a/src/Ookii.CommandLine/ParseOptions.cs +++ b/src/Ookii.CommandLine/ParseOptions.cs @@ -222,9 +222,11 @@ public virtual bool IsPosix /// /// /// - /// This property is only used if the if the parsing mode is set to , - /// either using the property or the - /// attribute + /// This property is only used if the property or the + /// is + /// , or if the + /// or property is not + /// . /// /// /// Use the to specify the prefixes for short argument @@ -730,6 +732,47 @@ public LocalizedStringProvider StringProvider /// public NameTransform ValueDescriptionTransformOrDefault => ValueDescriptionTransform ?? NameTransform.None; + /// + /// Gets or sets the behavior when an argument is encountered that consists of only the long + /// argument prefix ("--" by default) by itself, not followed by a name. + /// + /// + /// One of the values of the enumeration, or + /// to use the value from the + /// attribute, or if that is not present, . + /// The default value is . + /// + /// + /// + /// Use this property to allow the use of the long argument prefix by itself to either treat + /// all remaining values as positional argument values, even when they start with an argument + /// prefix, or to cancel parsing with so + /// the remaining values can be inspected using the + /// property. This follows + /// typical POSIX argument parsing conventions. + /// + /// + /// The value of the property is used to identify this + /// special argument, even if the parsing mode is not + /// . + /// + /// + /// If not , this property overrides the + /// property. + /// + /// + public PrefixTerminationMode? PrefixTermination { get; set; } + + /// + /// Gets the behavior when an argument is encountered that consists of only the long argument + /// prefix ("--" by default) by itself, not followed by a name. + /// + /// + /// The value of the property, or + /// if that property is . + /// + public PrefixTerminationMode PrefixTerminationOrDefault => PrefixTermination ?? PrefixTerminationMode.None; + /// /// Gets or sets a value that indicates whether the class /// will use reflection even if the command line arguments type has the @@ -807,6 +850,7 @@ public void Merge(ParseOptionsAttribute attribute) AutoVersionArgument ??= attribute.AutoVersionArgument; AutoPrefixAliases ??= attribute.AutoPrefixAliases; ValueDescriptionTransform ??= attribute.ValueDescriptionTransform; + PrefixTermination ??= attribute.PrefixTermination; } internal VirtualTerminalSupport EnableErrorColor() diff --git a/src/Ookii.CommandLine/ParseOptionsAttribute.cs b/src/Ookii.CommandLine/ParseOptionsAttribute.cs index c28e042..3346265 100644 --- a/src/Ookii.CommandLine/ParseOptionsAttribute.cs +++ b/src/Ookii.CommandLine/ParseOptionsAttribute.cs @@ -155,9 +155,11 @@ public virtual bool IsPosix /// /// /// - /// This property is only used if the property is - /// , or if the parsing mode is set to - /// elsewhere. + /// This property is only used if the property or the + /// is + /// , or if the + /// or property is not + /// . /// /// /// Use the to specify the prefixes for short argument @@ -408,6 +410,35 @@ public virtual bool IsPosix /// public NameTransform ValueDescriptionTransform { get; set; } + /// + /// Gets or sets the behavior when an argument is encountered that consists of only the long + /// argument prefix ("--" by default) by itself, not followed by a name. + /// + /// + /// One of the values of the enumeration. The default value + /// is . + /// + /// + /// + /// Use this property to allow the use of the long argument prefix by itself to either treat + /// all remaining values as positional argument values, even when they start with an argument + /// prefix, or to cancel parsing with so + /// the remaining values can be inspected using the + /// property. This follows + /// typical POSIX argument parsing conventions. + /// + /// + /// The value of the property is used to identify this + /// special argument, even if the parsing mode is not + /// . + /// + /// + /// This value can be overridden by the + /// property. + /// + /// + public PrefixTerminationMode PrefixTermination { get; set; } + internal StringComparison GetStringComparison() { if (CaseSensitive) diff --git a/src/Ookii.CommandLine/PrefixTerminationMode.cs b/src/Ookii.CommandLine/PrefixTerminationMode.cs new file mode 100644 index 0000000..596299e --- /dev/null +++ b/src/Ookii.CommandLine/PrefixTerminationMode.cs @@ -0,0 +1,27 @@ +namespace Ookii.CommandLine; + +/// +/// Indicates the effect of an argument that is just the long argument prefix ("--" by default) +/// by itself, not followed by a name. +/// +/// +/// +public enum PrefixTerminationMode +{ + /// + /// This argument has no special meaning. + /// + None, + /// + /// The argument terminates the used of named arguments. Any following arguments are interpreted + /// as values for positional arguments, even if they begin with an argument name separator. + /// + PositionalOnly, + /// + /// The argument cancels parsing, returning an instance of the arguments type and making the + /// values after this argument available in the + /// property. This is identical to how an argument with + /// behaves. + /// + CancelWithSuccess +} From 87e31adebb65e2b70ef7438f955e9a3f2d6e4a63 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 8 Dec 2023 14:25:54 -0800 Subject: [PATCH 26/54] Add ValidateEnumValue diagnostics. --- .../AnalyzerReleases.Unshipped.md | 4 ++- .../ArgumentAttributes.cs | 16 +++++++-- .../Diagnostics.cs | 17 +++++++++ .../ParserGenerator.cs | 15 ++++++++ .../Properties/Resources.Designer.cs | 36 +++++++++++++++++++ .../Properties/Resources.resx | 12 +++++++ src/Ookii.CommandLine.Generator/TypeHelper.cs | 2 ++ 7 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md index ffe6df6..466167a 100644 --- a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md +++ b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md @@ -1,8 +1,10 @@ ; Unshipped analyzer release ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md +; This is only for rules used by the analyzer; rules for the source generator should not be listed +; here. ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- -OCL0040 | Ookii.CommandLine | Warning | Diagnostics \ No newline at end of file +OCL0040 | Ookii.CommandLine | Warning | ParserShouldBeGenerated diff --git a/src/Ookii.CommandLine.Generator/ArgumentAttributes.cs b/src/Ookii.CommandLine.Generator/ArgumentAttributes.cs index d9e5096..7b080f4 100644 --- a/src/Ookii.CommandLine.Generator/ArgumentAttributes.cs +++ b/src/Ookii.CommandLine.Generator/ArgumentAttributes.cs @@ -14,6 +14,7 @@ internal class ArgumentAttributes private readonly AttributeData? _converterAttribute; private readonly AttributeData? _keyConverterAttribute; private readonly AttributeData? _valueConverterAttribute; + private readonly AttributeData? _validateEnumValue; private readonly List? _aliases; private readonly List? _shortAliases; private readonly List? _validators; @@ -23,7 +24,7 @@ public ArgumentAttributes(ISymbol member, TypeHelper typeHelper, SourceProductio AttributeData? typeConverterAttribute = null; foreach (var attribute in member.GetAttributes()) { - var _ = attribute.CheckType(typeHelper.CommandLineArgumentAttribute, ref _commandLineArgumentAttribute) || + _ = attribute.CheckType(typeHelper.CommandLineArgumentAttribute, ref _commandLineArgumentAttribute) || attribute.CheckType(typeHelper.MultiValueSeparatorAttribute, ref _multiValueSeparator) || attribute.CheckType(typeHelper.DescriptionAttribute, ref _description) || attribute.CheckType(typeHelper.ValueDescriptionAttribute, ref _valueDescription) || @@ -34,11 +35,19 @@ public ArgumentAttributes(ISymbol member, TypeHelper typeHelper, SourceProductio attribute.CheckType(typeHelper.ValueConverterAttribute, ref _valueConverterAttribute) || attribute.CheckType(typeHelper.AliasAttribute, ref _aliases) || attribute.CheckType(typeHelper.ShortAliasAttribute, ref _shortAliases) || - attribute.CheckType(typeHelper.ArgumentValidationAttribute, ref _validators); + attribute.CheckType(typeHelper.ValidateEnumValueAttribute, ref _validateEnumValue) || + attribute.CheckType(typeHelper.ArgumentValidationAttribute, ref _validators) || attribute.CheckType(typeHelper.TypeConverterAttribute, ref typeConverterAttribute); } - // Only warn if the TypeConverterAttribute is present. + // Since it was checked for separately, it won't be in the list. + if (_validateEnumValue != null) + { + _validators ??= []; + _validators.Add(_validateEnumValue); + } + + // Warn if the TypeConverterAttribute is present. if (CommandLineArgument != null && typeConverterAttribute != null) { context.ReportDiagnostic(Diagnostics.IgnoredTypeConverterAttribute(member, typeConverterAttribute)); @@ -57,5 +66,6 @@ public ArgumentAttributes(ISymbol member, TypeHelper typeHelper, SourceProductio public List? Aliases => _aliases; public List? ShortAliases => _shortAliases; public List? Validators => _validators; + public AttributeData? ValidateEnumValue => _validateEnumValue; } diff --git a/src/Ookii.CommandLine.Generator/Diagnostics.cs b/src/Ookii.CommandLine.Generator/Diagnostics.cs index abbf4fb..c26a2db 100644 --- a/src/Ookii.CommandLine.Generator/Diagnostics.cs +++ b/src/Ookii.CommandLine.Generator/Diagnostics.cs @@ -378,6 +378,23 @@ public static Diagnostic ParserShouldBeGenerated(ISymbol symbol) symbol.Locations.FirstOrDefault(), symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + public static Diagnostic ValidateEnumInvalidType(ISymbol symbol, ITypeSymbol elementType) => CreateDiagnostic( + "OCL0041", + nameof(Resources.ValidateEnumInvalidTypeTitle), + nameof(Resources.ValidateEnumInvalidTypeMessageFormat), + DiagnosticSeverity.Warning, + symbol.Locations.FirstOrDefault(), + symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), + elementType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + + public static Diagnostic ValidateEnumWithCustomConverter(ISymbol symbol) => CreateDiagnostic( + "OCL0042", + nameof(Resources.ValidateEnumWithCustomConverterTitle), + nameof(Resources.ValidateEnumWithCustomConverterMessageFormat), + DiagnosticSeverity.Warning, + symbol.Locations.FirstOrDefault(), + symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + private static Diagnostic CreateDiagnostic(string id, string titleResource, string messageResource, DiagnosticSeverity severity, Location? location, params object?[]? messageArgs) => Diagnostic.Create( CreateDiagnosticDescriptor(id, titleResource, messageResource, severity), diff --git a/src/Ookii.CommandLine.Generator/ParserGenerator.cs b/src/Ookii.CommandLine.Generator/ParserGenerator.cs index 3611f4d..788b711 100644 --- a/src/Ookii.CommandLine.Generator/ParserGenerator.cs +++ b/src/Ookii.CommandLine.Generator/ParserGenerator.cs @@ -697,6 +697,21 @@ private bool GenerateArgument(ISymbol member, ref List<(string, string, string)> _context.ReportDiagnostic(Diagnostics.IsShortIgnored(member, attributes.CommandLineArgument)); } + if (attributes.ValidateEnumValue != null) + { + if (elementType.TypeKind != TypeKind.Enum) + { + _context.ReportDiagnostic(Diagnostics.ValidateEnumInvalidType(member, elementType)); + } + else if (attributes.Converter != null && + (attributes.ValidateEnumValue.GetNamedArgument("CaseSensitive") != null || + attributes.ValidateEnumValue.GetNamedArgument("AllowCommaSeparatedValues") != null || + attributes.ValidateEnumValue.GetNamedArgument("AllowNumericValues") != null)) + { + _context.ReportDiagnostic(Diagnostics.ValidateEnumWithCustomConverter(member)); + } + } + return true; } diff --git a/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs b/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs index 8d937ce..c459f09 100644 --- a/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs +++ b/src/Ookii.CommandLine.Generator/Properties/Resources.Designer.cs @@ -842,5 +842,41 @@ internal static string UnsupportedLanguageVersionTitle { return ResourceManager.GetString("UnsupportedLanguageVersionTitle", resourceCulture); } } + + /// + /// Looks up a localized string similar to The argument defined by '{0}' uses the ValidateEnumValueAttribute, but its type '{1}' is not an enumeration.. + /// + internal static string ValidateEnumInvalidTypeMessageFormat { + get { + return ResourceManager.GetString("ValidateEnumInvalidTypeMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ValidateEnumValueAttribute can only be used on arguments with an enum type.. + /// + internal static string ValidateEnumInvalidTypeTitle { + get { + return ResourceManager.GetString("ValidateEnumInvalidTypeTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument defined by '{0}' has the ArgumentConverterAttribute, and uses ValidateEnumValueAttribute properties that may not work with a custom ArgumentConverter.. + /// + internal static string ValidateEnumWithCustomConverterMessageFormat { + get { + return ResourceManager.GetString("ValidateEnumWithCustomConverterMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument uses ValidateEnumValueAttribute properties that may not work with a custom ArgumentConverter.. + /// + internal static string ValidateEnumWithCustomConverterTitle { + get { + return ResourceManager.GetString("ValidateEnumWithCustomConverterTitle", resourceCulture); + } + } } } diff --git a/src/Ookii.CommandLine.Generator/Properties/Resources.resx b/src/Ookii.CommandLine.Generator/Properties/Resources.resx index 4be5a10..bf73989 100644 --- a/src/Ookii.CommandLine.Generator/Properties/Resources.resx +++ b/src/Ookii.CommandLine.Generator/Properties/Resources.resx @@ -378,4 +378,16 @@ Ookii.CommandLine source generation requires at least C# 8.0. + + The argument defined by '{0}' uses the ValidateEnumValueAttribute, but its type '{1}' is not an enumeration. + + + The ValidateEnumValueAttribute can only be used on arguments with an enum type. + + + The argument defined by '{0}' has the ArgumentConverterAttribute, and uses ValidateEnumValueAttribute properties that may not work with a custom ArgumentConverter. + + + The argument uses ValidateEnumValueAttribute properties that may not work with a custom ArgumentConverter. + \ No newline at end of file diff --git a/src/Ookii.CommandLine.Generator/TypeHelper.cs b/src/Ookii.CommandLine.Generator/TypeHelper.cs index 197e00a..7078c12 100644 --- a/src/Ookii.CommandLine.Generator/TypeHelper.cs +++ b/src/Ookii.CommandLine.Generator/TypeHelper.cs @@ -71,6 +71,8 @@ public TypeHelper(Compilation compilation) public INamedTypeSymbol? ClassValidationAttribute => _compilation.GetTypeByMetadataName(NamespacePrefix + "Validation.ClassValidationAttribute"); + public INamedTypeSymbol? ValidateEnumValueAttribute => _compilation.GetTypeByMetadataName(NamespacePrefix + "Validation.ValidateEnumValueAttribute"); + public INamedTypeSymbol? KeyValueSeparatorAttribute => _compilation.GetTypeByMetadataName(NamespacePrefix + "Conversion.KeyValueSeparatorAttribute"); public INamedTypeSymbol? ArgumentConverterAttribute => _compilation.GetTypeByMetadataName(NamespacePrefix + "Conversion.ArgumentConverterAttribute" From 9d9357866d3e323a4e7381b3daaae9449f333a34 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 8 Dec 2023 14:52:10 -0800 Subject: [PATCH 27/54] Source generator applies GeneratedCodeAttribute. --- .../CommandGenerator.cs | 2 ++ .../ConverterGenerator.cs | 1 + .../ParserGenerator.cs | 5 +++++ src/Ookii.CommandLine.Generator/SourceBuilder.cs | 15 +++++++++++++++ 4 files changed, 23 insertions(+) diff --git a/src/Ookii.CommandLine.Generator/CommandGenerator.cs b/src/Ookii.CommandLine.Generator/CommandGenerator.cs index b454c5b..182ee91 100644 --- a/src/Ookii.CommandLine.Generator/CommandGenerator.cs +++ b/src/Ookii.CommandLine.Generator/CommandGenerator.cs @@ -100,6 +100,7 @@ public void Generate() var builder = new SourceBuilder(manager.ContainingNamespace); builder.AppendLine($"partial class {manager.Name} : Ookii.CommandLine.Commands.CommandManager"); builder.OpenBlock(); + builder.AppendGeneratedCodeAttribute(); builder.AppendLine("private class GeneratedProvider : Ookii.CommandLine.Support.CommandProvider"); builder.OpenBlock(); builder.AppendLine("public override Ookii.CommandLine.Support.ProviderKind Kind => Ookii.CommandLine.Support.ProviderKind.Generated;"); @@ -156,6 +157,7 @@ public void Generate() builder.CloseBlock(); // GetCommandsUnsorted builder.CloseBlock(); // provider class builder.AppendLine(); + builder.AppendGeneratedCodeAttribute(); builder.AppendLine($"public {manager.Name}(Ookii.CommandLine.Commands.CommandOptions? options = null)"); builder.AppendLine($" : base(new GeneratedProvider(), options)"); builder.OpenBlock(); diff --git a/src/Ookii.CommandLine.Generator/ConverterGenerator.cs b/src/Ookii.CommandLine.Generator/ConverterGenerator.cs index 5852e95..a62fe5b 100644 --- a/src/Ookii.CommandLine.Generator/ConverterGenerator.cs +++ b/src/Ookii.CommandLine.Generator/ConverterGenerator.cs @@ -184,6 +184,7 @@ private static string GenerateName(ITypeSymbol type) private static void CreateConverter(SourceBuilder builder, ITypeSymbol type, ConverterInfo info) { + builder.AppendGeneratedCodeAttribute(); builder.AppendLine($"internal class {info.Name} : Ookii.CommandLine.Conversion.ArgumentConverter"); builder.OpenBlock(); string inputType = info.UseSpan ? "System.ReadOnlySpan" : "string"; diff --git a/src/Ookii.CommandLine.Generator/ParserGenerator.cs b/src/Ookii.CommandLine.Generator/ParserGenerator.cs index 788b711..4237575 100644 --- a/src/Ookii.CommandLine.Generator/ParserGenerator.cs +++ b/src/Ookii.CommandLine.Generator/ParserGenerator.cs @@ -165,6 +165,7 @@ public ParserGenerator(SourceProductionContext context, INamedTypeSymbol argumen _builder.AppendLine("/// "); _builder.AppendLine($"/// An instance of the class for the class."); _builder.AppendLine("/// "); + _builder.AppendGeneratedCodeAttribute(); _builder.AppendLine($"public static Ookii.CommandLine.CommandLineParser<{_argumentsClass.ToQualifiedName()}> CreateParser(Ookii.CommandLine.ParseOptions? options = null) => new Ookii.CommandLine.CommandLineParser<{_argumentsClass.ToQualifiedName()}>(new OokiiCommandLineArgumentProvider(), options);"); _builder.AppendLine(); var nullableType = _argumentsClass.WithNullableAnnotation(NullableAnnotation.Annotated); @@ -185,6 +186,7 @@ public ParserGenerator(SourceProductionContext context, INamedTypeSymbol argumen _builder.AppendLine($"/// An instance of the class, or if an"); _builder.AppendLine("/// error occurred or argument parsing was canceled."); _builder.AppendLine("/// "); + _builder.AppendGeneratedCodeAttribute(); _builder.AppendLine($"public static {nullableType.ToQualifiedName()} Parse(Ookii.CommandLine.ParseOptions? options = null) => CreateParser(options).ParseWithErrorHandling();"); _builder.AppendLine(); _builder.AppendLine("/// "); @@ -199,6 +201,7 @@ public ParserGenerator(SourceProductionContext context, INamedTypeSymbol argumen _builder.AppendLine($"/// An instance of the class, or if an"); _builder.AppendLine("/// error occurred or argument parsing was canceled."); _builder.AppendLine("/// "); + _builder.AppendGeneratedCodeAttribute(); _builder.AppendLine($"public static {nullableType.ToQualifiedName()} Parse(string[] args, Ookii.CommandLine.ParseOptions? options = null) => CreateParser(options).ParseWithErrorHandling(args);"); _builder.AppendLine(); _builder.AppendLine("/// "); @@ -213,6 +216,7 @@ public ParserGenerator(SourceProductionContext context, INamedTypeSymbol argumen _builder.AppendLine($"/// An instance of the class, or if an"); _builder.AppendLine("/// error occurred or argument parsing was canceled."); _builder.AppendLine("/// "); + _builder.AppendGeneratedCodeAttribute(); _builder.AppendLine($"public static {nullableType.ToQualifiedName()} Parse(System.ReadOnlyMemory args, Ookii.CommandLine.ParseOptions? options = null) => CreateParser(options).ParseWithErrorHandling(args);"); _builder.CloseBlock(); // class } @@ -222,6 +226,7 @@ public ParserGenerator(SourceProductionContext context, INamedTypeSymbol argumen private bool GenerateProvider(ArgumentsClassAttributes attributes, bool isCommand) { + _builder.AppendGeneratedCodeAttribute(); _builder.AppendLine("private class OokiiCommandLineArgumentProvider : Ookii.CommandLine.Support.GeneratedArgumentProvider"); _builder.OpenBlock(); _builder.AppendLine("public OokiiCommandLineArgumentProvider()"); diff --git a/src/Ookii.CommandLine.Generator/SourceBuilder.cs b/src/Ookii.CommandLine.Generator/SourceBuilder.cs index be75d02..1903c4e 100644 --- a/src/Ookii.CommandLine.Generator/SourceBuilder.cs +++ b/src/Ookii.CommandLine.Generator/SourceBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using System.Reflection; using System.Text; namespace Ookii.CommandLine.Generator; @@ -9,6 +10,8 @@ internal class SourceBuilder private int _indentLevel; private bool _startOfLine = true; private bool _needArgumentSeparator; + private string? _toolName; + private string? _toolVersion; public SourceBuilder(INamespaceSymbol ns) : this(ns.IsGlobalNamespace ? null : ns.ToDisplayString()) @@ -85,6 +88,18 @@ public void CloseBlock() AppendLine("}"); } + public void AppendGeneratedCodeAttribute() + { + if (_toolName == null) + { + var assembly = Assembly.GetExecutingAssembly(); + _toolName = assembly.GetName().Name; + _toolVersion = assembly.GetCustomAttribute().InformationalVersion ?? assembly.GetName().Version.ToString(); + } + + AppendLine($"[System.CodeDom.Compiler.GeneratedCode(\"{_toolName}\", \"{_toolVersion}\")]"); + } + public string GetSource() { while (_indentLevel > 0) From c9ce65cccb341214bfa7eee9cd16c252f56e1df7 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 8 Dec 2023 15:23:34 -0800 Subject: [PATCH 28/54] Add new diagnostics docs. --- docs/SourceGenerationDiagnostics.md | 165 +++++++++++++++++++++------- 1 file changed, 123 insertions(+), 42 deletions(-) diff --git a/docs/SourceGenerationDiagnostics.md b/docs/SourceGenerationDiagnostics.md index b88eaf3..5e5592d 100644 --- a/docs/SourceGenerationDiagnostics.md +++ b/docs/SourceGenerationDiagnostics.md @@ -34,7 +34,7 @@ For example, the following code triggers this error: [GeneratedParser] partial struct Arguments // ERROR: The type must be a class. { - [CommandLineAttribute] + [CommandLineArgument] public string? Argument { get; set; } } ``` @@ -52,7 +52,7 @@ For example, the following code triggers this error: [GeneratedParser] class Arguments // ERROR: The class must be partial { - [CommandLineAttribute] + [CommandLineArgument] public string? Argument { get; set; } } ``` @@ -70,7 +70,7 @@ For example, the following code triggers this error: [GeneratedParser] partial class Arguments // ERROR: The class must not be generic { - [CommandLineAttribute] + [CommandLineArgument] public T? Argument { get; set; } } ``` @@ -90,7 +90,7 @@ class SomeClass [GeneratedParser] public partial class Arguments // ERROR: The class must not be nested { - [CommandLineAttribute] + [CommandLineArgument] public T? Argument { get; set; } } } @@ -108,7 +108,7 @@ For example, the following code triggers this error: partial class Arguments { // ERROR: Argument using an array rank other than one. - [CommandLineAttribute] + [CommandLineArgument] public string[,]? Argument { get; set; } } ``` @@ -128,7 +128,7 @@ For example, the following code triggers this error: partial class Arguments { // ERROR: Property must use a public set accessor. - [CommandLineAttribute] + [CommandLineArgument] public string? Argument { get; private set; } } ``` @@ -151,7 +151,7 @@ For example, the following code triggers this error: partial class Arguments { // ERROR: Argument type must have a converter - [CommandLineAttribute] + [CommandLineArgument] public Socket? Argument { get; set; } } ``` @@ -171,7 +171,7 @@ For example, the following code triggers this error: partial class Arguments { // ERROR: the method has an unrecognized parameter - [CommandLineAttribute] + [CommandLineArgument] public static void Argument(string value, int value2); } ``` @@ -191,7 +191,7 @@ For example, the following code triggers this error: partial class Arguments { // ERROR: The property uses init but is not required - [CommandLineAttribute(IsRequired = true)] + [CommandLineArgument(IsRequired = true)] public string? Argument { get; init; } } ``` @@ -203,7 +203,7 @@ To fix this error, either use a regular `set` accessor, or if using .Net 7.0 or [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] public required string Argument { get; init; } } ``` @@ -248,11 +248,11 @@ For example, the following code triggers this error: [GeneratedParser] partial class Arguments { - [CommandLineAttribute(IsPositional = true)] + [CommandLineArgument(IsPositional = true)] public string[]? Argument1 { get; set; } // ERROR: Argument2 comes after Argument1, which is multi-value. - [CommandLineAttribute(IsPositional = true] + [CommandLineArgument(IsPositional = true] public string? Argument2 { get; set; } } ``` @@ -269,11 +269,11 @@ For example, the following code triggers this error: [GeneratedParser] partial class Arguments { - [CommandLineAttribute(IsPositional = true)] + [CommandLineArgument(IsPositional = true)] public string? Argument1 { get; set; } // ERROR: Required argument Argument2 comes after Argument1, which is optional. - [CommandLineAttribute(IsPositional = true)] + [CommandLineArgument(IsPositional = true)] public required string Argument2 { get; set; } } ``` @@ -325,7 +325,7 @@ For example, the following code triggers this error: [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] [ArgumentConverter("MyNamespace.MyConverter")] // ERROR: Can't use a string type name. public CustomType? Argument { get; set; } } @@ -351,7 +351,7 @@ For example, the following code triggers this error: partial class Arguments { // ERROR: No long or short name (IsShort is false by default). - [CommandLineAttribute(IsLong = false)] + [CommandLineArgument(IsLong = false)] public string? Argument { get; set; } } ``` @@ -391,10 +391,10 @@ For example, the following code triggers this error: [GeneratedParser] partial class Arguments { - [CommandLineAttribute(IsPositional = true)] + [CommandLineArgument(IsPositional = true)] public string? Argument1 { get; set; } - [CommandLineAttribute(Position = 0)] + [CommandLineArgument(Position = 0)] public string? Argument2 { get; set; } } ``` @@ -420,7 +420,7 @@ For example, the following code triggers this warning: [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] [TypeConverter(typeof(MyNamespace.MyConverter)] // WARNING: TypeConverterAttribute is not used public CustomType? Argument { get; set; } } @@ -433,7 +433,7 @@ existing [`TypeConverter`][], you can use the [`WrappedTypeConverter`][] clas [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] [ArgumentConverter(typeof(WrappedTypeConverter)] public CustomType? Argument { get; set; } } @@ -453,7 +453,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: the method must be public - [CommandLineAttribute] + [CommandLineArgument] private static void Argument(string value, int value2); } ``` @@ -472,7 +472,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: the property must be public - [CommandLineAttribute] + [CommandLineArgument] private string? Argument { get; set; } } ``` @@ -493,7 +493,7 @@ For example, the following code triggers this warning: [Command] partial class MyCommand // WARNING: The class doesn't implement ICommand { - [CommandLineAttribute] + [CommandLineArgument] public string? Argument { get; set; } } ``` @@ -520,7 +520,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: Default value is unused on a required argument. - [CommandLineAttribute(DefaultValue = "foo")] + [CommandLineArgument(DefaultValue = "foo")] public required string Argument { get; set; } } ``` @@ -541,7 +541,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: the argument will be required regardless of the value of IsRequired. - [CommandLineAttribute(IsRequired = false)] + [CommandLineArgument(IsRequired = false)] public required string Argument { get; set; } } ``` @@ -558,11 +558,11 @@ of the arguments, which should be avoided. [GeneratedParser] partial class Arguments { - [CommandLineAttribute(Position = 0)] + [CommandLineArgument(Position = 0)] public string? Argument1 { get; set; } // WARNING: Argument2 has the same position as Argument1. - [CommandLineAttribute(Position = 0)] + [CommandLineArgument(Position = 0)] public string? Argument2 { get; set; } } ``` @@ -590,7 +590,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: The short alias is not used since the argument has no short name. - [CommandLineAttribute] + [CommandLineArgument] [ShortAlias('a')] public string? Argument { get; set; } } @@ -613,7 +613,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: The long alias is not used since the argument has no long name. - [CommandLineAttribute(IsLong = false, IsShort = true)] + [CommandLineArgument(IsLong = false, IsShort = true)] [Alias("arg")] public string? Argument { get; set; } } @@ -638,7 +638,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: The argument is not hidden because it's positional. - [CommandLineAttribute(IsPositional = true, IsHidden = true)] + [CommandLineArgument(IsPositional = true, IsHidden = true)] public string? Argument { get; set; } } ``` @@ -667,7 +667,7 @@ For example, the following code triggers this warning: [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] [AllowDuplicateDictionaryKeys] // WARNING: Ignored on non-dictionary arguments public string? Argument { get; set; } } @@ -685,7 +685,7 @@ For example, the following code triggers this warning: [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] [ArgumentConverter(typeof(CustomKeyValuePairConverter))] [KeyValueSeparator(":")] // WARNING: Ignored on dictionary arguments with an explicit converter. public Dictionary? Argument { get; set; } @@ -703,7 +703,7 @@ For example, the following code triggers this warning: [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] [MultiValueSeparator(",")] // WARNING: Ignored on non-multi-value arguments public string? Argument { get; set; } } @@ -725,7 +725,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: Name starts with a number. - [CommandLineAttribute("1Arg")] + [CommandLineArgument("1Arg")] public string? Argument { get; set; } } ``` @@ -751,7 +751,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: Argument has a short name so IsShort is ignored. - [CommandLineAttribute(ShortName = 'a', IsShort = false)] + [CommandLineArgument(ShortName = 'a', IsShort = false)] public string? Argument { get; set; } } ``` @@ -772,7 +772,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: No DescriptionAttribute on this member. - [CommandLineAttribute] + [CommandLineArgument] public string? Argument { get; set; } } ``` @@ -784,7 +784,7 @@ To fix this, write a concise description explaining the argument's purpose and u [GeneratedParser] partial class Arguments { - [CommandLineAttribute] + [CommandLineArgument] [Description("A description of the argument.")] public string? Argument { get; set; } } @@ -806,7 +806,7 @@ For example, the following code triggers this warning: [Command] partial class MyCommand : ICommand { - [CommandLineAttribute] + [CommandLineArgument] [Description("A description of the argument.")] public string? Argument { get; set; } } @@ -821,7 +821,7 @@ To fix this, write a concise description explaining the command's purpose, and a [Command] partial class MyCommand : ICommand { - [CommandLineAttribute] + [CommandLineArgument] [Description("A description of the argument.")] public string? Argument { get; set; } } @@ -843,7 +843,7 @@ For example, the following code triggers this warning: [ParentCommand(typeof(SomeCommand))] partial class MyCommand { - [CommandLineAttribute] + [CommandLineArgument] [Description("A description of the argument.")] public string? Argument { get; set; } } @@ -865,7 +865,7 @@ For example, the following code triggers this warning: [Command] partial class MyCommand : ICommand { - [CommandLineAttribute] + [CommandLineArgument] [Description("A description of the argument.")] public string? Argument { get; set; } } @@ -897,7 +897,7 @@ For example, the following code triggers this warning: partial class Arguments { // WARNING: Method call for property initializer is not supported for the usage help. - [CommandLineAttribute] + [CommandLineArgument] public string? Argument { get; set; } = GetDefaultValue(); private static int GetDefaultValue() @@ -922,6 +922,87 @@ Note that default values set by property initializers are only shown in the usag [`GeneratedParserAttribute`][] is used. When reflection is used, only [`CommandLineArgumentAttribute.DefaultValue`][] is supported. +### OCL0040 + +Command line arguments classes should use source generation. + +This warning is emitted for any class that contains members with the +[`CommandLineArgumentAttribute`][], but which does not have the [`GeneratedParserAttribute`][] +applied. Using [source generation](SourceGeneration.md) is recommended in all cases, unless you +cannot meet the requirements. + +This warning is not emitted if the project does not use C# 8 or later, or the class is abstract, +nested in another type, or has generic type arguments. + +For example, the following code triggers this warning: + +```csharp +// WARNING: The "Argument" property has the CommandLineArgumentAttribute, but the class does not +// have the GeneratedParserAttribute. +class Arguments +{ + [CommandLineArgument] + public string? Argument { get; set; } +} +``` + +A code fix is provided that lets you use the lightbulb UI in Visual Studio to quickly add the +[`GeneratedParserAttribute`][] to the class. This will also make the class `partial` if it isn't +already. + +If you cannot use source generation for some reason, you should disable this warning. + +### OCL0041 + +The `ValidateEnumValueAttribute` attribute was applied to an argument whose type is not an +enumeration type, a nullable enumeration type, or an array or collection containing an enumeration +type. + +The `ValidateEnumValueAttribute` attribute only supports enumeration types, and will throw an +exception at runtime if used to validate an argument whose type is not an enumeration. + +For example, the following code triggers this warning: + +```csharp +class Arguments +{ + // WARNING: String isn't an enumeration type. + [CommandLineArgument] + [ValidateEnumValue] + public string? Argument { get; set; } +} +``` + +To fix this warning, either remove the `ValidateEnumValueAttribute` attribute or change the type of +the argument to an enumeration type. + +### OCL0042 + +An argument has the [`ArgumentConverterAttribute`][] set, and uses properties of the +`ValidateEnumValueAttribute` that may not be supported by a custom converter. + +The `CaseSensitive`, `AllowCommaSeparatedValues`, and `AllowNumericValues` properties of the +`ValidateEnumValueAttribute` attribute are not used by the `ValidateEnumValueAttribute` attribute +itself, but instead alter the behavior of the `EnumConverter` class. If an argument uses a custom +converter rather than the `EnumConverter`, it is not guaranteed that these properties will have any +effect. + +For example, the following code triggers this warning: + +```csharp +class Arguments +{ + // WARNING: ValidateEnumValueAttribute.CaseSensitive used with a custom argument converter. + [CommandLineArgument] + [ArgumentConverter(typeof(MyConverter))] + [ValidateEnumValue(CaseSensitive = true)] + public DayOfWeek Argument { get; set; } +} +``` + +To fix this warning, either use the default `EnumConverter`, or remove the properties. If the +custom converter does check the value of those properties, you can disable this warning. + [`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AliasAttribute.htm [`AllowDuplicateDictionaryKeysAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AllowDuplicateDictionaryKeysAttribute.htm [`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm From 65f21d32dd9b990f5561f4826df73d550a69ffd4 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 8 Dec 2023 15:29:12 -0800 Subject: [PATCH 29/54] Update version in SHFB project. --- docs/Ookii.CommandLine.shfbproj | 4 ++-- .../LocalizedStringProvider.Usage.cs | 12 ++++++++++++ src/Ookii.CommandLine/LocalizedStringProvider.cs | 10 ---------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/Ookii.CommandLine.shfbproj b/docs/Ookii.CommandLine.shfbproj index e4b4217..b691949 100644 --- a/docs/Ookii.CommandLine.shfbproj +++ b/docs/Ookii.CommandLine.shfbproj @@ -45,13 +45,13 @@ https://github.com/SvenGroot/Ookii.CommandLine Copyright &#169%3b Sven Groot %28Ookii.org%29 - Ookii.CommandLine 4.0 documentation + Ookii.CommandLine 4.1 documentation MemberName Default2022 C#, Visual Basic, Visual Basic Usage, Managed C++ Blank True - False + True AboveNamespaces True True diff --git a/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs b/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs index ba19467..82ea9da 100644 --- a/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs +++ b/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs @@ -61,4 +61,16 @@ public virtual string UsageDefaultValue(object defaultValue, IFormatProvider for /// The string. public virtual string UsageMoreInfoMessage(string executableName, string helpArgumentName) => Format(Resources.MoreInfoOnErrorFormat, executableName, helpArgumentName); + + /// + /// Gets an instruction on how to get help on a command, used by the + /// class. + /// + /// The application and command name. + /// The argument name prefix for the help argument. + /// The help argument name. + /// The string. + public virtual string UsageCommandHelpInstruction(string name, string argumentNamePrefix, string argumentName) + => Format(Resources.CommandHelpInstructionFormat, name, argumentNamePrefix, argumentName); + } diff --git a/src/Ookii.CommandLine/LocalizedStringProvider.cs b/src/Ookii.CommandLine/LocalizedStringProvider.cs index bc69a81..bef1dc1 100644 --- a/src/Ookii.CommandLine/LocalizedStringProvider.cs +++ b/src/Ookii.CommandLine/LocalizedStringProvider.cs @@ -124,16 +124,6 @@ public virtual string ApplicationNameAndVersion(Assembly assembly, string friend public virtual string? ApplicationCopyright(Assembly assembly) => assembly.GetCustomAttribute()?.Copyright; - /// - /// Gets an instruction on how to get help on a command, used by the - /// class. - /// - /// The application and command name. - /// The argument name prefix for the help argument. - /// The help argument name. - public virtual string UsageCommandHelpInstruction(string name, string argumentNamePrefix, string argumentName) - => Format(Resources.CommandHelpInstructionFormat, name, argumentNamePrefix, argumentName); - private static string Format(string format, object? arg0) => string.Format(CultureInfo.CurrentCulture, format, arg0); From d67d9b8e68c47dde943808db1df7b1bd18f37256 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 8 Dec 2023 15:34:41 -0800 Subject: [PATCH 30/54] Update API link targets. --- docs/Arguments.md | 44 ++++---- docs/ChangeLog.md | 54 ++++----- docs/DefiningArguments.md | 78 ++++++------- docs/Migrating.md | 110 +++++++++--------- docs/ParsingArguments.md | 68 ++++++------ docs/SourceGeneration.md | 38 +++---- docs/SourceGenerationDiagnostics.md | 123 +++++++++++---------- docs/Subcommands.md | 106 +++++++++--------- docs/Tutorial.md | 78 ++++++------- docs/UsageHelp.md | 60 +++++----- docs/Utilities.md | 32 +++--- docs/Validation.md | 78 ++++++------- docs/refs.json | 9 +- src/Samples/ArgumentDependencies/README.md | 12 +- src/Samples/CustomUsage/README.md | 4 +- src/Samples/LongShort/README.md | 2 +- src/Samples/NestedCommands/README.md | 12 +- src/Samples/Parser/README.md | 6 +- src/Samples/Subcommand/README.md | 8 +- src/Samples/TopLevelArguments/README.md | 2 +- src/Samples/Wpf/README.md | 8 +- 21 files changed, 471 insertions(+), 461 deletions(-) diff --git a/docs/Arguments.md b/docs/Arguments.md index b3908b8..4290714 100644 --- a/docs/Arguments.md +++ b/docs/Arguments.md @@ -403,12 +403,12 @@ and case-sensitive argument names. For information on how to set these options, Next, let's take a look at how to [define arguments](DefiningArguments.md). -[`AllowDuplicateDictionaryKeysAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AllowDuplicateDictionaryKeysAttribute.htm -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm -[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm -[`CommandLineArgument.AllowNull`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgument_AllowNull.htm -[`CommandLineArgumentException`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentException.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm +[`AllowDuplicateDictionaryKeysAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_AllowDuplicateDictionaryKeysAttribute.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm +[`CommandLineArgument.AllowNull`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgument_AllowNull.htm +[`CommandLineArgumentException`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentException.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm [`CultureInfo.InvariantCulture`]: https://learn.microsoft.com/dotnet/api/system.globalization.cultureinfo.invariantculture [`CultureInfo`]: https://learn.microsoft.com/dotnet/api/system.globalization.cultureinfo [`DateTime`]: https://learn.microsoft.com/dotnet/api/system.datetime @@ -416,28 +416,28 @@ Next, let's take a look at how to [define arguments](DefiningArguments.md). [`DayOfWeek.Wednesday`]: https://learn.microsoft.com/dotnet/api/system.dayofweek [`DayOfWeek`]: https://learn.microsoft.com/dotnet/api/system.dayofweek [`Enum.Parse()`]: https://learn.microsoft.com/dotnet/api/system.enum.parse -[`EnumConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_EnumConverter.htm +[`EnumConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_EnumConverter.htm [`FileInfo`]: https://learn.microsoft.com/dotnet/api/system.io.fileinfo [`FlagsAttribute`]: https://learn.microsoft.com/dotnet/api/system.flagsattribute [`Int32`]: https://learn.microsoft.com/dotnet/api/system.int32 [`IParsable`]: https://learn.microsoft.com/dotnet/api/system.iparsable-1 [`ISpanParsable`]: https://learn.microsoft.com/dotnet/api/system.ispanparsable-1 -[`KeyConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyConverterAttribute.htm +[`KeyConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyConverterAttribute.htm [`KeyValuePair`]: https://learn.microsoft.com/dotnet/api/system.collections.generic.keyvaluepair-2 -[`KeyValuePairConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyValuePairConverter_2.htm -[`KeyValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyValueSeparatorAttribute.htm -[`MultiValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_MultiValueSeparatorAttribute.htm -[`ParseOptions.AllowWhiteSpaceValueSeparator`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_AllowWhiteSpaceValueSeparator.htm -[`ParseOptions.ArgumentNameComparison`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameComparison.htm -[`ParseOptions.Culture`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_Culture.htm -[`ParseOptions.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_NameValueSeparators.htm -[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptions.htm -[`ParseOptionsAttribute.AllowWhiteSpaceValueSeparator`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_AllowWhiteSpaceValueSeparator.htm -[`ParseOptionsAttribute.CaseSensitive`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive.htm -[`ParseOptionsAttribute.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_NameValueSeparators.htm -[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm +[`KeyValuePairConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyValuePairConverter_2.htm +[`KeyValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyValueSeparatorAttribute.htm +[`MultiValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_MultiValueSeparatorAttribute.htm +[`ParseOptions.AllowWhiteSpaceValueSeparator`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_AllowWhiteSpaceValueSeparator.htm +[`ParseOptions.ArgumentNameComparison`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameComparison.htm +[`ParseOptions.Culture`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_Culture.htm +[`ParseOptions.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_NameValueSeparators.htm +[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm +[`ParseOptionsAttribute.AllowWhiteSpaceValueSeparator`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_AllowWhiteSpaceValueSeparator.htm +[`ParseOptionsAttribute.CaseSensitive`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive.htm +[`ParseOptionsAttribute.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_NameValueSeparators.htm +[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm [`String`]: https://learn.microsoft.com/dotnet/api/system.string [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter [`Uri`]: https://learn.microsoft.com/dotnet/api/system.uri -[`ValueConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ValueConverterAttribute.htm -[NullArgumentValue_0]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm +[`ValueConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ValueConverterAttribute.htm +[NullArgumentValue_0]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index e6e8444..108b5c5 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -218,38 +218,38 @@ and usage. Upgrading an existing project that is using Ookii.CommandLine 1.0 to Ookii.CommandLine 2.0 or newer may require substantial code changes and may change how command lines are parsed. -[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm [`AssemblyTitleAttribute`]: https://learn.microsoft.com/dotnet/api/system.reflection.assemblytitleattribute -[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp.htm -[`CommandLineParser.ParseResult`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineParser_ParseResult.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser_1.htm -[`CommandManager.ParseResult`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandManager_ParseResult.htm -[`CommandOptions.IsPosix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_IsPosix.htm +[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp.htm +[`CommandLineParser.ParseResult`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_ParseResult.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm +[`CommandManager.ParseResult`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandManager_ParseResult.htm +[`CommandOptions.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_IsPosix.htm [`CultureInfo.InvariantCulture`]: https://learn.microsoft.com/dotnet/api/system.globalization.cultureinfo.invariantculture [`Environment.GetCommandLineArgs()`]: https://learn.microsoft.com/dotnet/api/system.environment.getcommandlineargs -[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm [`IParsable`]: https://learn.microsoft.com/dotnet/api/system.iparsable-1 [`ISpanParsable`]: https://learn.microsoft.com/dotnet/api/system.ispanparsable-1 -[`LineWrappingTextWriter.ToString()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_LineWrappingTextWriter_ToString.htm -[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm -[`ParseOptions.IsPosix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_IsPosix.htm -[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptions.htm -[`ParseOptionsAttribute.IsPosix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm -[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm +[`LineWrappingTextWriter.ToString()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ToString.htm +[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm +[`ParseOptions.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_IsPosix.htm +[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm +[`ParseOptionsAttribute.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm +[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm [`ReadOnlySpan`]: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 -[`ResetIndentAsync()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndentAsync.htm +[`ResetIndentAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndentAsync.htm [`StringWriter`]: https://learn.microsoft.com/dotnet/api/system.io.stringwriter [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter -[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm -[`Wrapping`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping.htm -[Flush()_0]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_LineWrappingTextWriter_Flush_1.htm -[Parse()_6]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse.htm -[Parse()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[ParseWithErrorHandling()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm -[UsageWriter_1]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm -[WriteAsync()_4]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_LineWrappingTextWriter_WriteAsync.htm -[WriteLineAsync()_5]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_LineWrappingTextWriter_WriteLineAsync.htm +[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm +[`Wrapping`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping.htm +[Flush()_0]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_Flush_1.htm +[Parse()_6]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse.htm +[Parse()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[ParseWithErrorHandling()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm +[UsageWriter_1]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[WriteAsync()_4]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_LineWrappingTextWriter_WriteAsync.htm +[WriteLineAsync()_5]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_LineWrappingTextWriter_WriteLineAsync.htm diff --git a/docs/DefiningArguments.md b/docs/DefiningArguments.md index 5658ddb..851491e 100644 --- a/docs/DefiningArguments.md +++ b/docs/DefiningArguments.md @@ -624,52 +624,52 @@ disable either automatic argument using the [`ParseOptions`][] class. Next, we'll take a look at how to [parse the arguments we've defined](ParsingArguments.md) -[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AliasAttribute.htm -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm -[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm -[`CancelMode.Abort`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CancelMode.None`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CancelMode`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm -[`CommandLineArgumentAttribute.DefaultValue`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm -[`CommandLineArgumentAttribute.IsLong`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong.htm -[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm -[`CommandLineArgumentAttribute.IsRequired`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsRequired.htm -[`CommandLineArgumentAttribute.IsShort`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsShort.htm -[`CommandLineArgumentAttribute.Position`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_Position.htm -[`CommandLineArgumentAttribute.ShortName`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm -[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm -[`CommandLineParser.HelpRequested`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm -[`DefaultValue`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm +[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_AliasAttribute.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm +[`CancelMode.Abort`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CancelMode.None`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CancelMode`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm +[`CommandLineArgumentAttribute.DefaultValue`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm +[`CommandLineArgumentAttribute.IsLong`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong.htm +[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm +[`CommandLineArgumentAttribute.IsRequired`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsRequired.htm +[`CommandLineArgumentAttribute.IsShort`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsShort.htm +[`CommandLineArgumentAttribute.Position`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_Position.htm +[`CommandLineArgumentAttribute.ShortName`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm +[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm +[`CommandLineParser.HelpRequested`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm +[`DefaultValue`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm [`DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute [`Dictionary`]: https://learn.microsoft.com/dotnet/api/system.collections.generic.dictionary-2 -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm [`ICollection`]: https://learn.microsoft.com/dotnet/api/system.collections.generic.icollection-1 [`IDictionary`]: https://learn.microsoft.com/dotnet/api/system.collections.generic.idictionary-2 -[`IsPositional`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm +[`IsPositional`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm [`List`]: https://learn.microsoft.com/dotnet/api/system.collections.generic.list-1 -[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LocalizedStringProvider.htm -[`ParseOptions.ArgumentNameTransform`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameTransform.htm -[`ParseOptions.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_AutoPrefixAliases.htm -[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptions.htm -[`ParseOptionsAttribute.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_AutoPrefixAliases.htm -[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm -[`ParseResult.RemainingArguments`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseResult_RemainingArguments.htm +[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm +[`ParseOptions.ArgumentNameTransform`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameTransform.htm +[`ParseOptions.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_AutoPrefixAliases.htm +[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm +[`ParseOptionsAttribute.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_AutoPrefixAliases.htm +[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm +[`ParseResult.RemainingArguments`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseResult_RemainingArguments.htm [`ReadOnlySpan`]: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 -[`ShortAliasAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ShortAliasAttribute.htm +[`ShortAliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ShortAliasAttribute.htm [`String`]: https://learn.microsoft.com/dotnet/api/system.string [`System.ComponentModel.DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter [`TypeDescriptor.GetConverter()`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typedescriptor.getconverter -[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm -[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm -[`WrappedDefaultTypeConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_WrappedDefaultTypeConverter_1.htm -[CancelParsing_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm -[DefaultValue_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm -[IsPosix_2]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm -[Parse()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[ParseWithErrorHandling()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm -[Position_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_Position.htm +[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm +[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm +[`WrappedDefaultTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedDefaultTypeConverter_1.htm +[CancelParsing_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm +[DefaultValue_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm +[IsPosix_2]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm +[Parse()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[ParseWithErrorHandling()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm +[Position_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_Position.htm diff --git a/docs/Migrating.md b/docs/Migrating.md index d075041..28c595b 100644 --- a/docs/Migrating.md +++ b/docs/Migrating.md @@ -137,68 +137,68 @@ As of version 3.0, .Net Framework 2.0 is no longer supported. You can still targ - The [`LineWrappingTextWriter`][] class does not count virtual terminal sequences as part of the line length by default. -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm -[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm -[`ArgumentParsed`]: https://www.ookii.org/docs/commandline-4.0/html/E_Ookii_CommandLine_CommandLineParser_ArgumentParsed.htm -[`ArgumentParsedEventArgs`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ArgumentParsedEventArgs.htm -[`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm -[`CancelMode`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm -[`CommandInfo`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandInfo.htm -[`CommandLineArgument.DictionaryInfo`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgument_DictionaryInfo.htm -[`CommandLineArgument.ElementType`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgument_ElementType.htm -[`CommandLineArgument.Kind`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgument_Kind.htm -[`CommandLineArgument.MultiValueInfo`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgument_MultiValueInfo.htm -[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm -[`CommandLineParser.HelpRequested`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_ParseWithErrorHandling.htm -[`CommandLineParser.WriteUsage()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_WriteUsage.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm -[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser_1.htm -[`CommandManager.CreateCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_CreateCommand.htm -[`CommandManager.RunCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm -[`CommandManager.RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm -[`CommandManager`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandManager.htm -[`CommandNameComparison`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandNameComparison.htm -[`CommandOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandOptions.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm +[`ArgumentParsed`]: https://www.ookii.org/docs/commandline-4.1/html/E_Ookii_CommandLine_CommandLineParser_ArgumentParsed.htm +[`ArgumentParsedEventArgs`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ArgumentParsedEventArgs.htm +[`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm +[`CancelMode`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm +[`CommandInfo`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandInfo.htm +[`CommandLineArgument.DictionaryInfo`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgument_DictionaryInfo.htm +[`CommandLineArgument.ElementType`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgument_ElementType.htm +[`CommandLineArgument.Kind`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgument_Kind.htm +[`CommandLineArgument.MultiValueInfo`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgument_MultiValueInfo.htm +[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm +[`CommandLineParser.HelpRequested`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_ParseWithErrorHandling.htm +[`CommandLineParser.WriteUsage()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_WriteUsage.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm +[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm +[`CommandManager.CreateCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_CreateCommand.htm +[`CommandManager.RunCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm +[`CommandManager.RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm +[`CommandManager`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandManager.htm +[`CommandNameComparison`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandNameComparison.htm +[`CommandOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandOptions.htm [`CultureInfo.InvariantCulture`]: https://learn.microsoft.com/dotnet/api/system.globalization.cultureinfo.invariantculture [`CurrentCulture`]: https://learn.microsoft.com/dotnet/api/system.globalization.cultureinfo.currentculture -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm -[`HelpRequested`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm -[`IAsyncCommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_IAsyncCommand.htm -[`ICommand.Run()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm -[`ICommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommand.htm -[`ICommandWithCustomParsing.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_ICommandWithCustomParsing_Parse.htm -[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`HelpRequested`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm +[`IAsyncCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_IAsyncCommand.htm +[`ICommand.Run()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm +[`ICommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommand.htm +[`ICommandWithCustomParsing.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_ICommandWithCustomParsing_Parse.htm +[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm [`IComparer`]: https://learn.microsoft.com/dotnet/api/system.collections.generic.icomparer-1 [`ImmutableArray`]: https://learn.microsoft.com/dotnet/api/system.collections.immutable.immutablearray-1 -[`KeyConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyConverterAttribute.htm -[`KeyValuePairConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyValuePairConverter_2.htm -[`KeyValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyValueSeparatorAttribute.htm -[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm +[`KeyConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyConverterAttribute.htm +[`KeyValuePairConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyValuePairConverter_2.htm +[`KeyValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyValueSeparatorAttribute.htm +[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm [`Nullable`]: https://learn.microsoft.com/dotnet/api/system.nullable-1 -[`Ookii.CommandLine.Commands`]: https://www.ookii.org/docs/commandline-4.0/html/N_Ookii_CommandLine_Commands.htm -[`Ookii.CommandLine.Conversion`]: https://www.ookii.org/docs/commandline-4.0/html/N_Ookii_CommandLine_Conversion.htm -[`ParseOptions.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_NameValueSeparators.htm -[`ParseOptions.ShowUsageOnError`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ShowUsageOnError.htm -[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptions.htm -[`ParseOptionsAttribute.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_NameValueSeparators.htm +[`Ookii.CommandLine.Commands`]: https://www.ookii.org/docs/commandline-4.1/html/N_Ookii_CommandLine_Commands.htm +[`Ookii.CommandLine.Conversion`]: https://www.ookii.org/docs/commandline-4.1/html/N_Ookii_CommandLine_Conversion.htm +[`ParseOptions.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_NameValueSeparators.htm +[`ParseOptions.ShowUsageOnError`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ShowUsageOnError.htm +[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm +[`ParseOptionsAttribute.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_NameValueSeparators.htm [`ReadOnlyCollection`]: https://learn.microsoft.com/dotnet/api/system.collections.objectmodel.readonlycollection-1 [`ReadOnlyMemory`]: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 [`StringComparison`]: https://learn.microsoft.com/dotnet/api/system.stringcomparison -[`TextFormat`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Terminal_TextFormat.htm +[`TextFormat`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_TextFormat.htm [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter [`TypeConverterAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverterattribute -[`UsageHelpRequest.SyntaxOnly`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageHelpRequest.htm -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm -[`ValueConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ValueConverterAttribute.htm -[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm -[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm -[`WrappedDefaultTypeConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_WrappedDefaultTypeConverter_1.htm -[ArgumentNameComparison_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameComparison.htm -[CommandLineParser.Parse()_2]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_Parse.htm -[Parse()_5]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm -[Parse()_6]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_Parse.htm +[`UsageHelpRequest.SyntaxOnly`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageHelpRequest.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[`ValueConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ValueConverterAttribute.htm +[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm +[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm +[`WrappedDefaultTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedDefaultTypeConverter_1.htm +[ArgumentNameComparison_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameComparison.htm +[CommandLineParser.Parse()_2]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_Parse.htm +[Parse()_5]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm +[Parse()_6]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_Parse.htm diff --git a/docs/ParsingArguments.md b/docs/ParsingArguments.md index a547bdd..a486b42 100644 --- a/docs/ParsingArguments.md +++ b/docs/ParsingArguments.md @@ -208,38 +208,38 @@ methods only. Next, we'll take a look at [generating usage help](UsageHelp.md). -[`ArgumentParsed`]: https://www.ookii.org/docs/commandline-4.0/html/E_Ookii_CommandLine_CommandLineParser_ArgumentParsed.htm -[`CancelMode.Abort`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm -[`CommandLineArgumentErrorCategory`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm -[`CommandLineArgumentException.Category`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentException_Category.htm -[`CommandLineArgumentException`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentException.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[`CommandLineParser.ParseResult`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineParser_ParseResult.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser_1.htm +[`ArgumentParsed`]: https://www.ookii.org/docs/commandline-4.1/html/E_Ookii_CommandLine_CommandLineParser_ArgumentParsed.htm +[`CancelMode.Abort`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm +[`CommandLineArgumentErrorCategory`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm +[`CommandLineArgumentException.Category`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentException_Category.htm +[`CommandLineArgumentException`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentException.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[`CommandLineParser.ParseResult`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_ParseResult.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm [`Environment.GetCommandLineArgs()`]: https://learn.microsoft.com/dotnet/api/system.environment.getcommandlineargs -[`Error`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_Error.htm -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm -[`GetArgument`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_GetArgument.htm -[`HelpRequested`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm -[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LocalizedStringProvider.htm -[`ParseOptions.StringProvider`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_StringProvider.htm -[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptions.htm -[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm -[`ParseResult.ArgumentName`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseResult_ArgumentName.htm -[`ParseResult.LastException`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseResult_LastException.htm -[`ParseResult.RemainingArguments`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseResult_RemainingArguments.htm -[`ParseStatus.Canceled`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseStatus.htm -[`ParseStatus.Error`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseStatus.htm -[`ParseStatus.Success`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseStatus.htm -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm -[Arguments_0]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineParser_Arguments.htm -[CreateParser()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm -[DuplicateArgument_0]: https://www.ookii.org/docs/commandline-4.0/html/E_Ookii_CommandLine_CommandLineParser_DuplicateArgument.htm -[Parse()_5]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm -[Parse()_7]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm -[Parse()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[ParseWithErrorHandling()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm +[`Error`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_Error.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`GetArgument`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_GetArgument.htm +[`HelpRequested`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_HelpRequested.htm +[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm +[`ParseOptions.StringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_StringProvider.htm +[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm +[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm +[`ParseResult.ArgumentName`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseResult_ArgumentName.htm +[`ParseResult.LastException`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseResult_LastException.htm +[`ParseResult.RemainingArguments`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseResult_RemainingArguments.htm +[`ParseStatus.Canceled`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseStatus.htm +[`ParseStatus.Error`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseStatus.htm +[`ParseStatus.Success`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseStatus.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[Arguments_0]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_Arguments.htm +[CreateParser()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm +[DuplicateArgument_0]: https://www.ookii.org/docs/commandline-4.1/html/E_Ookii_CommandLine_CommandLineParser_DuplicateArgument.htm +[Parse()_5]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_CommandLineParser_1_Parse.htm +[Parse()_7]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm +[Parse()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[ParseWithErrorHandling()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm diff --git a/docs/SourceGeneration.md b/docs/SourceGeneration.md index 4bd9596..2aa2cd0 100644 --- a/docs/SourceGeneration.md +++ b/docs/SourceGeneration.md @@ -278,23 +278,23 @@ return manager.RunCommand() ?? 1; Next, we will take a look at several [utility classes](Utilities.md) provided, and used, by Ookii.CommandLine. -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm -[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm -[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm -[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser_1.htm -[`CommandManager`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandManager.htm -[`GeneratedCommandManagerAttribute.AssemblyNames`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute_AssemblyNames.htm -[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm -[`GeneratedConverterNamespaceAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_GeneratedConverterNamespaceAttribute.htm -[`GeneratedParserAttribute.GenerateParseMethods`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_GeneratedParserAttribute_GenerateParseMethods.htm -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm -[`IParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_IParser_1.htm -[`IParserProvider`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_IParserProvider_1.htm -[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm +[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm +[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm +[`CommandManager`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandManager.htm +[`GeneratedCommandManagerAttribute.AssemblyNames`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute_AssemblyNames.htm +[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm +[`GeneratedConverterNamespaceAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_GeneratedConverterNamespaceAttribute.htm +[`GeneratedParserAttribute.GenerateParseMethods`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_GeneratedParserAttribute_GenerateParseMethods.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`IParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_IParser_1.htm +[`IParserProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_IParserProvider_1.htm +[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm [`Type`]: https://learn.microsoft.com/dotnet/api/system.type -[CreateParser()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm -[DefaultValue_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm -[Parse()_7]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm +[CreateParser()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm +[DefaultValue_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm +[Parse()_7]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm diff --git a/docs/SourceGenerationDiagnostics.md b/docs/SourceGenerationDiagnostics.md index 5e5592d..2474ddb 100644 --- a/docs/SourceGenerationDiagnostics.md +++ b/docs/SourceGenerationDiagnostics.md @@ -908,9 +908,9 @@ partial class Arguments ``` This will not affect the actual value of the argument, since the property will not be set by the -[`CommandLineParser`][] if the [`CommandLineArgumentAttribute.DefaultValue`][] property is null. Therefore, -you can safely suppress this warning and include the relevant explanation of the default value in -the property's description manually, if desired. +[`CommandLineParser`][] if the [`CommandLineArgumentAttribute.DefaultValue`][] property is null. +Therefore, you can safely suppress this warning and include the relevant explanation of the default +value in the property's description manually, if desired. To avoid this warning, use one of the supported expression types, or use the [`CommandLineArgumentAttribute.DefaultValue`][] property. This warning will not be emitted if the @@ -954,11 +954,11 @@ If you cannot use source generation for some reason, you should disable this war ### OCL0041 -The `ValidateEnumValueAttribute` attribute was applied to an argument whose type is not an +The [`ValidateEnumValueAttribute`][] attribute was applied to an argument whose type is not an enumeration type, a nullable enumeration type, or an array or collection containing an enumeration type. -The `ValidateEnumValueAttribute` attribute only supports enumeration types, and will throw an +The [`ValidateEnumValueAttribute`][] attribute only supports enumeration types, and will throw an exception at runtime if used to validate an argument whose type is not an enumeration. For example, the following code triggers this warning: @@ -973,19 +973,19 @@ class Arguments } ``` -To fix this warning, either remove the `ValidateEnumValueAttribute` attribute or change the type of -the argument to an enumeration type. +To fix this warning, either remove the [`ValidateEnumValueAttribute`][] attribute or change the type +of the argument to an enumeration type. ### OCL0042 An argument has the [`ArgumentConverterAttribute`][] set, and uses properties of the -`ValidateEnumValueAttribute` that may not be supported by a custom converter. +[`ValidateEnumValueAttribute`][] that may not be supported by a custom converter. -The `CaseSensitive`, `AllowCommaSeparatedValues`, and `AllowNumericValues` properties of the -`ValidateEnumValueAttribute` attribute are not used by the `ValidateEnumValueAttribute` attribute -itself, but instead alter the behavior of the `EnumConverter` class. If an argument uses a custom -converter rather than the `EnumConverter`, it is not guaranteed that these properties will have any -effect. +The [`CaseSensitive`][CaseSensitive_1], [`AllowCommaSeparatedValues`][], and +[`AllowNumericValues`][] properties of the [`ValidateEnumValueAttribute`][] attribute are not used +by the [`ValidateEnumValueAttribute`][] attribute itself, but instead alter the behavior of the +[`EnumConverter`][] class. If an argument uses a custom converter rather than the +[`EnumConverter`][], it is not guaranteed that these properties will have any effect. For example, the following code triggers this warning: @@ -1000,55 +1000,60 @@ class Arguments } ``` -To fix this warning, either use the default `EnumConverter`, or remove the properties. If the +To fix this warning, either use the default [`EnumConverter`][], or remove the properties. If the custom converter does check the value of those properties, you can disable this warning. -[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AliasAttribute.htm -[`AllowDuplicateDictionaryKeysAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AllowDuplicateDictionaryKeysAttribute.htm -[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm -[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm -[`CommandAttribute.IsHidden`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandAttribute_IsHidden.htm -[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm -[`CommandLineArgumentAttribute.DefaultValue`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm -[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp.htm -[`CommandLineArgumentAttribute.IsHidden`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden.htm -[`CommandLineArgumentAttribute.IsLong`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong.htm -[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm -[`CommandLineArgumentAttribute.IsRequired`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsRequired.htm -[`CommandLineArgumentAttribute.IsShort`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsShort.htm -[`CommandLineArgumentAttribute.Position`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_Position.htm -[`CommandLineArgumentAttribute.ShortName`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm -[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser_1.htm -[`CommandManager`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandManager.htm +[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_AliasAttribute.htm +[`AllowCommaSeparatedValues`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowCommaSeparatedValues.htm +[`AllowDuplicateDictionaryKeysAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_AllowDuplicateDictionaryKeysAttribute.htm +[`AllowNumericValues`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowNumericValues.htm +[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`ArgumentConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverterAttribute.htm +[`CommandAttribute.IsHidden`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandAttribute_IsHidden.htm +[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm +[`CommandLineArgumentAttribute.DefaultValue`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm +[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp.htm +[`CommandLineArgumentAttribute.IsHidden`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden.htm +[`CommandLineArgumentAttribute.IsLong`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong.htm +[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm +[`CommandLineArgumentAttribute.IsRequired`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsRequired.htm +[`CommandLineArgumentAttribute.IsShort`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsShort.htm +[`CommandLineArgumentAttribute.Position`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_Position.htm +[`CommandLineArgumentAttribute.ShortName`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm +[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm +[`CommandManager`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandManager.htm [`DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute -[`GeneratedCommandManagerAttribute.AssemblyNames`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute_AssemblyNames.htm -[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm -[`GeneratedConverterNamespaceAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_GeneratedConverterNamespaceAttribute.htm -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm -[`ICommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommand.htm -[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm -[`IsShort`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsShort.htm -[`KeyConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyConverterAttribute.htm -[`KeyValuePairConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyValuePairConverter_2.htm -[`KeyValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_KeyValueSeparatorAttribute.htm -[`MultiValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_MultiValueSeparatorAttribute.htm -[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm -[`ParseOptions.ArgumentNameComparison`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameComparison.htm -[`ParseOptions.ArgumentNamePrefixes`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ArgumentNamePrefixes.htm -[`ParseOptions.ArgumentNameTransform`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameTransform.htm -[`ParseOptions.Mode`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_Mode.htm -[`ParseOptionsAttribute.ArgumentNamePrefixes`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_ArgumentNamePrefixes.htm -[`ParsingMode.LongShort`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParsingMode.htm -[`ShortAliasAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ShortAliasAttribute.htm +[`EnumConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_EnumConverter.htm +[`GeneratedCommandManagerAttribute.AssemblyNames`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute_AssemblyNames.htm +[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm +[`GeneratedConverterNamespaceAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_GeneratedConverterNamespaceAttribute.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`ICommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommand.htm +[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm +[`IsShort`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsShort.htm +[`KeyConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyConverterAttribute.htm +[`KeyValuePairConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyValuePairConverter_2.htm +[`KeyValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_KeyValueSeparatorAttribute.htm +[`MultiValueSeparatorAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_MultiValueSeparatorAttribute.htm +[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm +[`ParseOptions.ArgumentNameComparison`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameComparison.htm +[`ParseOptions.ArgumentNamePrefixes`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNamePrefixes.htm +[`ParseOptions.ArgumentNameTransform`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameTransform.htm +[`ParseOptions.Mode`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_Mode.htm +[`ParseOptionsAttribute.ArgumentNamePrefixes`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_ArgumentNamePrefixes.htm +[`ParsingMode.LongShort`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParsingMode.htm +[`ShortAliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ShortAliasAttribute.htm [`Type`]: https://learn.microsoft.com/dotnet/api/system.type [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter -[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm [`TypeConverterAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverterattribute -[`ValueConverterAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ValueConverterAttribute.htm -[IsHidden_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden.htm -[IsRequired_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsRequired.htm -[ShortName_1]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm +[`ValidateEnumValueAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateEnumValueAttribute.htm +[`ValueConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ValueConverterAttribute.htm +[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm +[CaseSensitive_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_CaseSensitive.htm +[IsHidden_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden.htm +[IsRequired_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsRequired.htm +[ShortName_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm diff --git a/docs/Subcommands.md b/docs/Subcommands.md index 9059731..37e7ae6 100644 --- a/docs/Subcommands.md +++ b/docs/Subcommands.md @@ -663,59 +663,59 @@ functionality. The next page will discuss Ookii.CommandLine's [source generation](SourceGeneration.md) in more detail. -[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AliasAttribute.htm -[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm -[`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm -[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm -[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm -[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm -[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandManager.GetCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_CommandManager_GetCommand.htm -[`CommandManager.ParseResult`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandManager_ParseResult.htm -[`CommandManager.RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm -[`CommandManager`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandManager.htm -[`CommandNameTransform`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandNameTransform.htm -[`CommandOptions.AutoCommandPrefixAliases`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_AutoCommandPrefixAliases.htm -[`CommandOptions.AutoVersionCommand`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_AutoVersionCommand.htm -[`CommandOptions.CommandFilter`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandFilter.htm -[`CommandOptions.CommandNameTransform`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandNameTransform.htm -[`CommandOptions.ParentCommand`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_ParentCommand.htm -[`CommandOptions.StripCommandNameSuffix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_StripCommandNameSuffix.htm -[`CommandOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandOptions.htm -[`CreateCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_CreateCommand.htm +[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_AliasAttribute.htm +[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm +[`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm +[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm +[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm +[`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm +[`CommandLineArgumentAttribute.IsPositional`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandManager.GetCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_CommandManager_GetCommand.htm +[`CommandManager.ParseResult`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandManager_ParseResult.htm +[`CommandManager.RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm +[`CommandManager`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandManager.htm +[`CommandNameTransform`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandNameTransform.htm +[`CommandOptions.AutoCommandPrefixAliases`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_AutoCommandPrefixAliases.htm +[`CommandOptions.AutoVersionCommand`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_AutoVersionCommand.htm +[`CommandOptions.CommandFilter`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandFilter.htm +[`CommandOptions.CommandNameTransform`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_CommandNameTransform.htm +[`CommandOptions.ParentCommand`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_ParentCommand.htm +[`CommandOptions.StripCommandNameSuffix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_StripCommandNameSuffix.htm +[`CommandOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandOptions.htm +[`CreateCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_CreateCommand.htm [`DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute [`Environment.GetCommandLineArgs()`]: https://learn.microsoft.com/dotnet/api/system.environment.getcommandlineargs -[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm -[`IAsyncCommand.RunAsync()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_IAsyncCommand_RunAsync.htm -[`IAsyncCommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_IAsyncCommand.htm -[`ICommand.Run()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm -[`ICommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommand.htm -[`ICommandWithCustomParsing.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_ICommandWithCustomParsing_Parse.htm -[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm -[`IncludeApplicationDescriptionBeforeCommandList`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescriptionBeforeCommandList.htm -[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LocalizedStringProvider.htm -[`NameTransform.DashCase`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_NameTransform.htm -[`NameTransform.None`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_NameTransform.htm -[`Ookii.CommandLine.Commands`]: https://www.ookii.org/docs/commandline-4.0/html/N_Ookii_CommandLine_Commands.htm -[`ParentCommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ParentCommand.htm -[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm -[`ParseOptions.AutoVersionArgument`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_AutoVersionArgument.htm -[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptions.htm -[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm -[`ParseResult.RemainingArguments`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseResult_RemainingArguments.htm -[`ParseResult.Status`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseResult_Status.htm -[`RunCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm -[`RunCommand`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm -[`RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm +[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`IAsyncCommand.RunAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_IAsyncCommand_RunAsync.htm +[`IAsyncCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_IAsyncCommand.htm +[`ICommand.Run()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm +[`ICommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommand.htm +[`ICommandWithCustomParsing.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_ICommandWithCustomParsing_Parse.htm +[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm +[`IncludeApplicationDescriptionBeforeCommandList`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescriptionBeforeCommandList.htm +[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm +[`NameTransform.DashCase`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_NameTransform.htm +[`NameTransform.None`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_NameTransform.htm +[`Ookii.CommandLine.Commands`]: https://www.ookii.org/docs/commandline-4.1/html/N_Ookii_CommandLine_Commands.htm +[`ParentCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ParentCommand.htm +[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm +[`ParseOptions.AutoVersionArgument`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_AutoVersionArgument.htm +[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm +[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm +[`ParseResult.RemainingArguments`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseResult_RemainingArguments.htm +[`ParseResult.Status`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseResult_Status.htm +[`RunCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm +[`RunCommand`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm +[`RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm [`StringComparison.OrdinalIgnoreCase`]: https://learn.microsoft.com/dotnet/api/system.stringcomparison -[`StripCommandNameSuffix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_StripCommandNameSuffix.htm -[`UsageWriter.IncludeCommandHelpInstruction`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_IncludeCommandHelpInstruction.htm -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm -[`WriteCommandDescription()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteCommandDescription.htm -[`WriteCommandHelpInstruction()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteCommandHelpInstruction.htm -[`WriteCommandListUsageCore()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteCommandListUsageCore.htm -[`WriteCommandListUsageSyntax()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteCommandListUsageSyntax.htm -[RunAsync()_0]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_AsyncCommandBase_RunAsync.htm +[`StripCommandNameSuffix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_StripCommandNameSuffix.htm +[`UsageWriter.IncludeCommandHelpInstruction`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeCommandHelpInstruction.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[`WriteCommandDescription()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteCommandDescription.htm +[`WriteCommandHelpInstruction()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteCommandHelpInstruction.htm +[`WriteCommandListUsageCore()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteCommandListUsageCore.htm +[`WriteCommandListUsageSyntax()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteCommandListUsageSyntax.htm +[RunAsync()_0]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_AsyncCommandBase_RunAsync.htm diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 8f1e26a..40986a3 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -974,52 +974,52 @@ following resources: - [Class library documentation](https://www.ookii.org/Link/CommandLineDoc) - [Sample applications](../src/Samples) -[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_AliasAttribute.htm -[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm -[`Arguments.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_IParser_1_Parse.htm +[`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_AliasAttribute.htm +[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm +[`Arguments.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_IParser_1_Parse.htm [`AssemblyTitleAttribute`]: https://learn.microsoft.com/dotnet/api/system.reflection.assemblytitleattribute -[`AsyncCommandBase.Run()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_AsyncCommandBase_Run.htm -[`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm -[`CaseSensitive`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive.htm -[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm -[`CommandLineArgumentAttribute.DefaultValue`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm -[`CommandLineArgumentAttribute.IsLong`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong.htm -[`CommandLineArgumentAttribute.ShortName`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm -[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandManager.RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm -[`CommandManager`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandManager.htm -[`CommandOptions.StripCommandNameSuffix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_StripCommandNameSuffix.htm -[`CommandOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandOptions.htm -[`CreateCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_CreateCommand.htm +[`AsyncCommandBase.Run()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_AsyncCommandBase_Run.htm +[`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm +[`CaseSensitive`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive.htm +[`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm +[`CommandLineArgumentAttribute.DefaultValue`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm +[`CommandLineArgumentAttribute.IsLong`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong.htm +[`CommandLineArgumentAttribute.ShortName`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_ShortName.htm +[`CommandLineArgumentAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentAttribute.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandManager.RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm +[`CommandManager`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandManager.htm +[`CommandOptions.StripCommandNameSuffix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_StripCommandNameSuffix.htm +[`CommandOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandOptions.htm +[`CreateCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_CreateCommand.htm [`DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute [`Environment.GetCommandLineArgs()`]: https://learn.microsoft.com/dotnet/api/system.environment.getcommandlineargs [`File.ReadLinesAsync()`]: https://learn.microsoft.com/dotnet/api/system.io.file.readlinesasync [`FileInfo`]: https://learn.microsoft.com/dotnet/api/system.io.fileinfo -[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm -[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm -[`GetCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_CommandManager_GetCommand.htm -[`IAsyncCommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_IAsyncCommand.htm +[`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`GetCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_CommandManager_GetCommand.htm +[`IAsyncCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_IAsyncCommand.htm [`IAsyncEnumerable`]: https://learn.microsoft.com/dotnet/api/system.collections.generic.iasyncenumerable-1 -[`ICommand.Run()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm -[`ICommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommand.htm -[`IncludeApplicationDescriptionBeforeCommandList`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescriptionBeforeCommandList.htm +[`ICommand.Run()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm +[`ICommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommand.htm +[`IncludeApplicationDescriptionBeforeCommandList`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescriptionBeforeCommandList.htm [`Nullable`]: https://learn.microsoft.com/dotnet/api/system.nullable-1 -[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptions.htm -[`ParseOptionsAttribute.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_AutoPrefixAliases.htm -[`ParseOptionsAttribute.IsPosix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm -[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm -[`ParsingMode.LongShort`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ParsingMode.htm -[`RunCommand()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm -[`RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm +[`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm +[`ParseOptionsAttribute.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_AutoPrefixAliases.htm +[`ParseOptionsAttribute.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm +[`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm +[`ParsingMode.LongShort`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParsingMode.htm +[`RunCommand()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommand.htm +[`RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm [`StringComparison`]: https://learn.microsoft.com/dotnet/api/system.stringcomparison [`Take()`]: https://learn.microsoft.com/dotnet/api/system.linq.enumerable.take [`Uri`]: https://learn.microsoft.com/dotnet/api/system.uri -[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm -[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm -[IsPosix_0]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_IsPosix.htm -[Mode_2]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_Mode.htm -[Parse()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[Run()_0]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_AsyncCommandBase_Run.htm -[Run()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm -[RunAsync()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Commands_IAsyncCommand_RunAsync.htm +[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm +[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm +[IsPosix_0]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_IsPosix.htm +[Mode_2]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_Mode.htm +[Parse()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[Run()_0]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_AsyncCommandBase_Run.htm +[Run()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm +[RunAsync()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_IAsyncCommand_RunAsync.htm diff --git a/docs/UsageHelp.md b/docs/UsageHelp.md index 4f6aa2f..370811f 100644 --- a/docs/UsageHelp.md +++ b/docs/UsageHelp.md @@ -344,39 +344,39 @@ Please see the [subcommand documentation](Subcommands.md) for information about Next, we'll take a look at [argument validation and dependencies](Validation.md). -[`CommandLineArgumentAttribute.IsHidden`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden.htm -[`CommandLineParser.GetUsage()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_GetUsage.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[`CommandLineParser.WriteUsage()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_WriteUsage.htm -[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm +[`CommandLineArgumentAttribute.IsHidden`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden.htm +[`CommandLineParser.GetUsage()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_GetUsage.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[`CommandLineParser.WriteUsage()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_WriteUsage.htm +[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm [`DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute -[`DescriptionListFilterMode.Information`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_DescriptionListFilterMode.htm -[`GetExtendedColor()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Terminal_TextFormat_GetExtendedColor.htm +[`DescriptionListFilterMode.Information`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_DescriptionListFilterMode.htm +[`GetExtendedColor()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Terminal_TextFormat_GetExtendedColor.htm [`Int32`]: https://learn.microsoft.com/dotnet/api/system.int32 -[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm -[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LocalizedStringProvider.htm +[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm +[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm [`Nullable`]: https://learn.microsoft.com/dotnet/api/system.nullable-1 -[`ParseOptions.DefaultValueDescriptions`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_DefaultValueDescriptions.htm -[`ParseOptions.ShowUsageOnError`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ShowUsageOnError.htm -[`ParseOptions.UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_UsageWriter.htm +[`ParseOptions.DefaultValueDescriptions`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_DefaultValueDescriptions.htm +[`ParseOptions.ShowUsageOnError`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ShowUsageOnError.htm +[`ParseOptions.UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_UsageWriter.htm [`SetConsoleMode`]: https://learn.microsoft.com/windows/console/setconsolemode [`String`]: https://learn.microsoft.com/dotnet/api/system.string [`System.ComponentModel.DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute -[`TextFormat`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Terminal_TextFormat.htm -[`UsageWriter.ArgumentDescriptionListFilter`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListFilter.htm -[`UsageWriter.ArgumentDescriptionListOrder`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListOrder.htm -[`UsageWriter.IncludeApplicationDescription`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescription.htm -[`UsageWriter.UseAbbreviatedSyntax`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_UseAbbreviatedSyntax.htm -[`UsageWriter.UseColor`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_UseColor.htm -[`UsageWriter.UseShortNamesForSyntax`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_UseShortNamesForSyntax.htm -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm -[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm -[`WriteArgumentDescriptions()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentDescriptions.htm -[`WriteArgumentName()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentName.htm -[`WriteArgumentSyntax()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentSyntax.htm -[`WriteParserUsageCore()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteParserUsageCore.htm -[`WriteParserUsageSyntax()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteParserUsageSyntax.htm -[`WriteValueDescription()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteValueDescription.htm -[`WriteValueDescriptionForDescription()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteValueDescriptionForDescription.htm -[Parse()_7]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm -[WriteArgumentDescription()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentDescription.htm +[`TextFormat`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_TextFormat.htm +[`UsageWriter.ArgumentDescriptionListFilter`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListFilter.htm +[`UsageWriter.ArgumentDescriptionListOrder`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListOrder.htm +[`UsageWriter.IncludeApplicationDescription`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescription.htm +[`UsageWriter.UseAbbreviatedSyntax`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseAbbreviatedSyntax.htm +[`UsageWriter.UseColor`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseColor.htm +[`UsageWriter.UseShortNamesForSyntax`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseShortNamesForSyntax.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm +[`WriteArgumentDescriptions()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentDescriptions.htm +[`WriteArgumentName()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentName.htm +[`WriteArgumentSyntax()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentSyntax.htm +[`WriteParserUsageCore()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteParserUsageCore.htm +[`WriteParserUsageSyntax()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteParserUsageSyntax.htm +[`WriteValueDescription()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteValueDescription.htm +[`WriteValueDescriptionForDescription()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteValueDescriptionForDescription.htm +[Parse()_7]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm +[WriteArgumentDescription()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_UsageWriter_WriteArgumentDescription.htm diff --git a/docs/Utilities.md b/docs/Utilities.md index 31f069f..8e06e53 100644 --- a/docs/Utilities.md +++ b/docs/Utilities.md @@ -163,20 +163,20 @@ public int Run() ``` [`Console.WindowWidth`]: https://learn.microsoft.com/dotnet/api/system.console.windowwidth -[`EnableColor()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Terminal_VirtualTerminal_EnableColor.htm -[`EnableVirtualTerminalSequences()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Terminal_VirtualTerminal_EnableVirtualTerminalSequences.htm -[`Indent`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_LineWrappingTextWriter_Indent.htm -[`LineWrappingTextWriter.ForConsoleError()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleError.htm -[`LineWrappingTextWriter.ForConsoleOut()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleOut.htm -[`LineWrappingTextWriter.Indent`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_LineWrappingTextWriter_Indent.htm -[`LineWrappingTextWriter.ResetIndent()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndent.htm -[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm -[`Ookii.CommandLine.Terminal`]: https://www.ookii.org/docs/commandline-4.0/html/N_Ookii_CommandLine_Terminal.htm -[`ResetIndent()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndent.htm -[`TextFormat`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Terminal_TextFormat.htm +[`EnableColor()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Terminal_VirtualTerminal_EnableColor.htm +[`EnableVirtualTerminalSequences()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Terminal_VirtualTerminal_EnableVirtualTerminalSequences.htm +[`Indent`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Indent.htm +[`LineWrappingTextWriter.ForConsoleError()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleError.htm +[`LineWrappingTextWriter.ForConsoleOut()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleOut.htm +[`LineWrappingTextWriter.Indent`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Indent.htm +[`LineWrappingTextWriter.ResetIndent()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndent.htm +[`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm +[`Ookii.CommandLine.Terminal`]: https://www.ookii.org/docs/commandline-4.1/html/N_Ookii_CommandLine_Terminal.htm +[`ResetIndent()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndent.htm +[`TextFormat`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_TextFormat.htm [`TextWriter`]: https://learn.microsoft.com/dotnet/api/system.io.textwriter -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm -[`VirtualTerminal`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Terminal_VirtualTerminal.htm -[`LineWrappingTextWriter.Wrapping`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping.htm -[`WrappingMode.Disabled`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_WrappingMode.htm -[`WrappingMode.EnabledNoForce`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_WrappingMode.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[`VirtualTerminal`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_VirtualTerminal.htm +[`LineWrappingTextWriter.Wrapping`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping.htm +[`WrappingMode.Disabled`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_WrappingMode.htm +[`WrappingMode.EnabledNoForce`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_WrappingMode.htm diff --git a/docs/Validation.md b/docs/Validation.md index 52af102..d812e7c 100644 --- a/docs/Validation.md +++ b/docs/Validation.md @@ -280,45 +280,45 @@ does not apply to validators that don't use [`ValidationMode.BeforeConversion`][ Now that you know (almost) everything there is to know about arguments, let's move on to [subcommands](Subcommands.md). -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm -[`ArgumentValidationAttribute.IsSpanValid`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_IsSpanValid.htm -[`ArgumentValidationAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ArgumentValidationAttribute.htm -[`ArgumentValidationWithHelpAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute.htm -[`Category`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_CommandLineArgumentException_Category.htm -[`ClassValidationAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ClassValidationAttribute.htm -[`CommandLineArgumentErrorCategory.ValidationFailed`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm -[`CommandLineArgumentException`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentException.htm -[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`ArgumentValidationAttribute.IsSpanValid`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_IsSpanValid.htm +[`ArgumentValidationAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ArgumentValidationAttribute.htm +[`ArgumentValidationWithHelpAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute.htm +[`Category`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentException_Category.htm +[`ClassValidationAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ClassValidationAttribute.htm +[`CommandLineArgumentErrorCategory.ValidationFailed`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm +[`CommandLineArgumentException`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentException.htm +[`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm [`DateOnly`]: https://learn.microsoft.com/dotnet/api/system.dateonly -[`EnumConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_EnumConverter.htm -[`ErrorCategory`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Validation_ArgumentValidationAttribute_ErrorCategory.htm -[`GetErrorMessage()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_GetErrorMessage.htm -[`GetUsageHelp()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_GetUsageHelp.htm -[`GetUsageHelpCore()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_GetUsageHelpCore.htm +[`EnumConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_EnumConverter.htm +[`ErrorCategory`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ArgumentValidationAttribute_ErrorCategory.htm +[`GetErrorMessage()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_GetErrorMessage.htm +[`GetUsageHelp()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_GetUsageHelp.htm +[`GetUsageHelpCore()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_GetUsageHelpCore.htm [`IComparable`]: https://learn.microsoft.com/dotnet/api/system.icomparable-1 -[`IsValid()`]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_IsValid.htm -[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LocalizedStringProvider.htm -[`NullableConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_NullableConverter.htm -[`Ookii.CommandLine.Validation`]: https://www.ookii.org/docs/commandline-4.0/html/N_Ookii_CommandLine_Validation.htm -[`ProhibitsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ProhibitsAttribute.htm +[`IsValid()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_IsValid.htm +[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm +[`NullableConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_NullableConverter.htm +[`Ookii.CommandLine.Validation`]: https://www.ookii.org/docs/commandline-4.1/html/N_Ookii_CommandLine_Validation.htm +[`ProhibitsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ProhibitsAttribute.htm [`ReadOnlySpan`]: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 -[`RequiresAnyAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_RequiresAnyAttribute.htm -[`RequiresAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_RequiresAttribute.htm -[`UsageWriter.IncludeValidatorsInDescription`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_IncludeValidatorsInDescription.htm -[`ValidateCountAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateCountAttribute.htm -[`ValidateEnumValueAttribute.IncludeValuesInErrorMessage`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_IncludeValuesInErrorMessage.htm -[`ValidateEnumValueAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateEnumValueAttribute.htm -[`ValidateNotEmptyAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateNotEmptyAttribute.htm -[`ValidateNotNullAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateNotNullAttribute.htm -[`ValidateNotWhiteSpaceAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateNotWhiteSpaceAttribute.htm -[`ValidatePatternAttribute.ErrorMessage`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Validation_ValidatePatternAttribute_ErrorMessage.htm -[`ValidatePatternAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidatePatternAttribute.htm -[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm -[`ValidateStringLengthAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateStringLengthAttribute.htm -[`ValidationMode.BeforeConversion`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidationMode.htm -[IncludeInUsageHelp_0]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_IncludeInUsageHelp.htm -[Mode_3]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Validation_ArgumentValidationAttribute_Mode.htm -[Parse()_7]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm -[ValidationFailed_1]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm +[`RequiresAnyAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_RequiresAnyAttribute.htm +[`RequiresAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_RequiresAttribute.htm +[`UsageWriter.IncludeValidatorsInDescription`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeValidatorsInDescription.htm +[`ValidateCountAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateCountAttribute.htm +[`ValidateEnumValueAttribute.IncludeValuesInErrorMessage`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_IncludeValuesInErrorMessage.htm +[`ValidateEnumValueAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateEnumValueAttribute.htm +[`ValidateNotEmptyAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateNotEmptyAttribute.htm +[`ValidateNotNullAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateNotNullAttribute.htm +[`ValidateNotWhiteSpaceAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateNotWhiteSpaceAttribute.htm +[`ValidatePatternAttribute.ErrorMessage`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidatePatternAttribute_ErrorMessage.htm +[`ValidatePatternAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidatePatternAttribute.htm +[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm +[`ValidateStringLengthAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateStringLengthAttribute.htm +[`ValidationMode.BeforeConversion`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidationMode.htm +[IncludeInUsageHelp_0]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_IncludeInUsageHelp.htm +[Mode_3]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ArgumentValidationAttribute_Mode.htm +[Parse()_7]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm +[ValidationFailed_1]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm diff --git a/docs/refs.json b/docs/refs.json index 4609598..cf35e2a 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -1,11 +1,13 @@ { "#apiPrefix": "https://learn.microsoft.com/dotnet/api/", - "#prefix": "https://www.ookii.org/docs/commandline-4.0/html/", + "#prefix": "https://www.ookii.org/docs/commandline-4.1/html/", "#suffix": ".htm", "AddCommand": null, "AliasAttribute": "T_Ookii_CommandLine_AliasAttribute", + "AllowCommaSeparatedValues": "P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowCommaSeparatedValues", "AllowDuplicateDictionaryKeys": "P_Ookii_CommandLine_CommandLineArgument_AllowDuplicateDictionaryKeys", "AllowDuplicateDictionaryKeysAttribute": "T_Ookii_CommandLine_AllowDuplicateDictionaryKeysAttribute", + "AllowNumericValues": "P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowNumericValues", "ApplicationFriendlyNameAttribute": "T_Ookii_CommandLine_ApplicationFriendlyNameAttribute", "Arg1": null, "Arg2": null, @@ -39,7 +41,10 @@ "P_Ookii_CommandLine_CommandLineArgument_CancelParsing", "P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing" ], - "CaseSensitive": "P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive", + "CaseSensitive": [ + "P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive", + "P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_CaseSensitive" + ], "Category": "P_Ookii_CommandLine_CommandLineArgumentException_Category", "ClassValidationAttribute": "T_Ookii_CommandLine_Validation_ClassValidationAttribute", "CommandAttribute": "T_Ookii_CommandLine_Commands_CommandAttribute", diff --git a/src/Samples/ArgumentDependencies/README.md b/src/Samples/ArgumentDependencies/README.md index ee7205e..0d5ef49 100644 --- a/src/Samples/ArgumentDependencies/README.md +++ b/src/Samples/ArgumentDependencies/README.md @@ -47,9 +47,9 @@ validators like [`ValidateRangeAttribute`][]), and all the included validators c case-by-case basis with the [`IncludeInUsageHelp`][IncludeInUsageHelp_0] property on each validator attribute. -[`ProhibitsAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ProhibitsAttribute.htm -[`RequiresAnyAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_RequiresAnyAttribute.htm -[`RequiresAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_RequiresAttribute.htm -[`UsageWriter.IncludeValidatorsInDescription`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_UsageWriter_IncludeValidatorsInDescription.htm -[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm -[IncludeInUsageHelp_0]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_IncludeInUsageHelp.htm +[`ProhibitsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ProhibitsAttribute.htm +[`RequiresAnyAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_RequiresAnyAttribute.htm +[`RequiresAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_RequiresAttribute.htm +[`UsageWriter.IncludeValidatorsInDescription`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeValidatorsInDescription.htm +[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm +[IncludeInUsageHelp_0]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_IncludeInUsageHelp.htm diff --git a/src/Samples/CustomUsage/README.md b/src/Samples/CustomUsage/README.md index c4d4bf9..2e57e27 100644 --- a/src/Samples/CustomUsage/README.md +++ b/src/Samples/CustomUsage/README.md @@ -50,5 +50,5 @@ If you compare this with the usage output of the [parser sample](../Parser), whi output format, you can see just how much you can change by simply overriding some methods on the [`UsageWriter`][] class. -[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_LocalizedStringProvider.htm -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm +[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm diff --git a/src/Samples/LongShort/README.md b/src/Samples/LongShort/README.md index 6b33797..b700bc0 100644 --- a/src/Samples/LongShort/README.md +++ b/src/Samples/LongShort/README.md @@ -75,4 +75,4 @@ argument names. Long/short mode allows you to combine switches with short names, so running `LongShort -vp` sets both `--verbose` and `--process` to true. -[`ParseOptionsAttribute.IsPosix`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm +[`ParseOptionsAttribute.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm diff --git a/src/Samples/NestedCommands/README.md b/src/Samples/NestedCommands/README.md index 877d138..8b9f823 100644 --- a/src/Samples/NestedCommands/README.md +++ b/src/Samples/NestedCommands/README.md @@ -106,9 +106,9 @@ Usage: NestedCommands student add [-FirstName] [-LastName] [[- The usage syntax shows both command names before the arguments. -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandManager`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandManager.htm -[`CommandOptions.ParentCommand`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_Commands_CommandOptions_ParentCommand.htm -[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm -[`ParentCommand`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ParentCommand.htm -[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandManager`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandManager.htm +[`CommandOptions.ParentCommand`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_ParentCommand.htm +[`ICommandWithCustomParsing`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ICommandWithCustomParsing.htm +[`ParentCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ParentCommand.htm +[`ParentCommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_ParentCommandAttribute.htm diff --git a/src/Samples/Parser/README.md b/src/Samples/Parser/README.md index 758efe2..d5da668 100644 --- a/src/Samples/Parser/README.md +++ b/src/Samples/Parser/README.md @@ -89,6 +89,6 @@ The `-Version` argument shows the value of the [`ApplicationFriendlyNameAttribut assembly title or name, if there isn't one), the assembly's informational version, and the assembly's copyright text. -[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm -[`ParseOptions.ShowUsageOnError`]: https://www.ookii.org/docs/commandline-4.0/html/P_Ookii_CommandLine_ParseOptions_ShowUsageOnError.htm -[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm +[`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm +[`ParseOptions.ShowUsageOnError`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ShowUsageOnError.htm +[`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm diff --git a/src/Samples/Subcommand/README.md b/src/Samples/Subcommand/README.md index a690d3e..aca44c3 100644 --- a/src/Samples/Subcommand/README.md +++ b/src/Samples/Subcommand/README.md @@ -83,8 +83,8 @@ Copyright (c) Sven Groot (Ookii.org) This is sample code, so you can use it freely. ``` -[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser.htm -[`CommandManager`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_Commands_CommandManager.htm -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm +[`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm +[`CommandManager`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandManager.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm [Encoding_1]: https://learn.microsoft.com/dotnet/api/system.text.encoding diff --git a/src/Samples/TopLevelArguments/README.md b/src/Samples/TopLevelArguments/README.md index d50c1bf..4327ceb 100644 --- a/src/Samples/TopLevelArguments/README.md +++ b/src/Samples/TopLevelArguments/README.md @@ -83,4 +83,4 @@ Usage: TopLevelArguments [global arguments] write [[--lines] ...] [--he When this option is specified, the file will be overwritten if it already exists. ``` -[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CancelMode.htm +[`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm diff --git a/src/Samples/Wpf/README.md b/src/Samples/Wpf/README.md index 057fbe6..80989db 100644 --- a/src/Samples/Wpf/README.md +++ b/src/Samples/Wpf/README.md @@ -33,7 +33,7 @@ A similar approach would work for Windows Forms, or any other GUI framework. This application is very basic; it's just a sample, and I don't do a lot of GUI work nowadays. It's just intended to show how the [`UsageWriter`][] can be adapted to work in the context of a GUI app. -[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_CommandLineParser_1.htm -[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.0/html/T_Ookii_CommandLine_UsageWriter.htm -[CreateParser()_1]: https://www.ookii.org/docs/commandline-4.0/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm -[Parse()_7]: https://www.ookii.org/docs/commandline-4.0/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm +[`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[CreateParser()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm +[Parse()_7]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm From a4b7fd27a73c824e1cba1d631f4810fd99971778 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 12 Dec 2023 17:56:29 -0800 Subject: [PATCH 31/54] Update change log. --- docs/ChangeLog.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++ docs/refs.json | 8 ++++++++ 2 files changed, 58 insertions(+) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 108b5c5..fa1f605 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -1,5 +1,36 @@ # What’s new in Ookii.CommandLine +## Ookii.CommandLine 4.1 (TBD) + +- Support for using a `--` argument to escape argument names for the remaining arguments, or to + cancel parsing. This can be enabled using [`ParseOptions.PrefixTermination`][] or + [`ParseOptionsAttribute.PrefixTermination`][]. +- The [`ValidateEnumValueAttribute`][] has additional properties to control how enumeration values + are parsed: [`CaseSensitive`][CaseSensitive_1], [`AllowNumericValues`][], and + [`AllowCommaSeparatedValues`][]. + - The [`EnumConverter`][] now also checks the + [`ValidateEnumValueAttribute.IncludeValuesInErrorMessage`][] property, if the attribute is + present on the argument, so that error message from the converter and validator are consistent. +- Support for passing a cancellation token to the [`CommandManager.RunCommandAsync()`][] method. + Tasks can access this token by implementing the [`IAsyncCancelableCommand`][] interface. The + [`AsyncCommandBase`][] class provides support as well. +- Usage help improvements: + - Support for custom default value formatting, using + [`CommandLineArgumentAttribute.DefaultValueFormat`][]. + - Add [`LineWrappingTextWriter.IndentAfterEmptyLine`][] and [`UsageWriter.IndentAfterEmptyLine`][] + properties, which allow for proper formatting of argument descriptions with blank lines using + the default usage help format. + - Add support for easily adding a footer to the usage help. + - Some localizable text that could previously only be customized by deriving from the + [`UsageWriter`][] class can now also be customized with the [`LocalizedStringProvider`][] class, + so you only need to derive from [`LocalizedStringProvider`][] to customize all user-facing + strings. +- Provide helper methods in the [`VirtualTerminal`][] class for writing text with VT formatting to + the standard output or error streams. +- Provide extension methods for [`StandardStream`][] in the [`StandardStreamExtensions`][] class. +- Emit a warning if a class isn't using the [`GeneratedParserAttribute`][] when it could, with an + automatic code fix to easily apply it. + ## Ookii.CommandLine 4.0.1 (2023-09-19) - Fix an issue where arguments defined by methods could not have aliases. @@ -218,34 +249,53 @@ and usage. Upgrading an existing project that is using Ookii.CommandLine 1.0 to Ookii.CommandLine 2.0 or newer may require substantial code changes and may change how command lines are parsed. +[`AllowCommaSeparatedValues`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowCommaSeparatedValues.htm +[`AllowNumericValues`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowNumericValues.htm [`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm [`ArgumentConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ArgumentConverter.htm [`AssemblyTitleAttribute`]: https://learn.microsoft.com/dotnet/api/system.reflection.assemblytitleattribute +[`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm +[`CommandLineArgumentAttribute.DefaultValueFormat`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValueFormat.htm [`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp.htm [`CommandLineParser.ParseResult`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_ParseResult.htm [`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm [`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm [`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm [`CommandManager.ParseResult`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandManager_ParseResult.htm +[`CommandManager.RunCommandAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/Overload_Ookii_CommandLine_Commands_CommandManager_RunCommandAsync.htm [`CommandOptions.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_CommandOptions_IsPosix.htm [`CultureInfo.InvariantCulture`]: https://learn.microsoft.com/dotnet/api/system.globalization.cultureinfo.invariantculture +[`EnumConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_EnumConverter.htm [`Environment.GetCommandLineArgs()`]: https://learn.microsoft.com/dotnet/api/system.environment.getcommandlineargs [`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm [`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`IAsyncCancelableCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_IAsyncCancelableCommand.htm [`IParsable`]: https://learn.microsoft.com/dotnet/api/system.iparsable-1 [`ISpanParsable`]: https://learn.microsoft.com/dotnet/api/system.ispanparsable-1 +[`LineWrappingTextWriter.IndentAfterEmptyLine`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_IndentAfterEmptyLine.htm [`LineWrappingTextWriter.ToString()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ToString.htm [`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm +[`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm [`ParseOptions.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_IsPosix.htm +[`ParseOptions.PrefixTermination`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_PrefixTermination.htm [`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm [`ParseOptionsAttribute.IsPosix`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm +[`ParseOptionsAttribute.PrefixTermination`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_PrefixTermination.htm [`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm [`ReadOnlySpan`]: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 [`ResetIndentAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndentAsync.htm +[`StandardStream`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_StandardStream.htm +[`StandardStreamExtensions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_StandardStreamExtensions.htm [`StringWriter`]: https://learn.microsoft.com/dotnet/api/system.io.stringwriter [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter +[`UsageWriter.IndentAfterEmptyLine`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IndentAfterEmptyLine.htm +[`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[`ValidateEnumValueAttribute.IncludeValuesInErrorMessage`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_IncludeValuesInErrorMessage.htm +[`ValidateEnumValueAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateEnumValueAttribute.htm [`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm +[`VirtualTerminal`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_VirtualTerminal.htm [`Wrapping`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping.htm +[CaseSensitive_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_CaseSensitive.htm [Flush()_0]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_Flush_1.htm [Parse()_6]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse.htm [Parse()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm diff --git a/docs/refs.json b/docs/refs.json index cf35e2a..80819aa 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -59,6 +59,7 @@ "CommandLineArgumentAttribute": "T_Ookii_CommandLine_CommandLineArgumentAttribute", "CommandLineArgumentAttribute.CancelParsing": "P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing", "CommandLineArgumentAttribute.DefaultValue": "P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue", + "CommandLineArgumentAttribute.DefaultValueFormat": "P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValueFormat", "CommandLineArgumentAttribute.IsHidden": "P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden", "CommandLineArgumentAttribute.IsLong": "P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong", "CommandLineArgumentAttribute.IsPositional": "P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional", @@ -163,6 +164,7 @@ "GetUsageHelp()": "M_Ookii_CommandLine_Validation_ArgumentValidationAttribute_GetUsageHelp", "GetUsageHelpCore()": "M_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_GetUsageHelpCore", "HelpRequested": "P_Ookii_CommandLine_CommandLineParser_HelpRequested", + "IAsyncCancelableCommand": "T_Ookii_CommandLine_Commands_IAsyncCancelableCommand", "IAsyncCommand": "T_Ookii_CommandLine_Commands_IAsyncCommand", "IAsyncCommand.RunAsync()": "M_Ookii_CommandLine_Commands_IAsyncCommand_RunAsync", "IAsyncEnumerable": "#system.collections.generic.iasyncenumerable-1", @@ -214,6 +216,7 @@ "LineWrappingTextWriter.ForConsoleError()": "M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleError", "LineWrappingTextWriter.ForConsoleOut()": "M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleOut", "LineWrappingTextWriter.Indent": "P_Ookii_CommandLine_LineWrappingTextWriter_Indent", + "LineWrappingTextWriter.IndentAfterEmptyLine": "P_Ookii_CommandLine_LineWrappingTextWriter_IndentAfterEmptyLine", "LineWrappingTextWriter.ResetIndent()": "M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndent", "LineWrappingTextWriter.ToString()": "M_Ookii_CommandLine_LineWrappingTextWriter_ToString", "LineWrappingTextWriter.Wrapping": "P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping", @@ -278,6 +281,7 @@ "ParseOptions.IsPosix": "P_Ookii_CommandLine_ParseOptions_IsPosix", "ParseOptions.Mode": "P_Ookii_CommandLine_ParseOptions_Mode", "ParseOptions.NameValueSeparators": "P_Ookii_CommandLine_ParseOptions_NameValueSeparators", + "ParseOptions.PrefixTermination": "P_Ookii_CommandLine_ParseOptions_PrefixTermination", "ParseOptions.ShowUsageOnError": "P_Ookii_CommandLine_ParseOptions_ShowUsageOnError", "ParseOptions.StringProvider": "P_Ookii_CommandLine_ParseOptions_StringProvider", "ParseOptions.UsageWriter": "P_Ookii_CommandLine_ParseOptions_UsageWriter", @@ -288,6 +292,7 @@ "ParseOptionsAttribute.CaseSensitive": "P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive", "ParseOptionsAttribute.IsPosix": "P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix", "ParseOptionsAttribute.NameValueSeparators": "P_Ookii_CommandLine_ParseOptionsAttribute_NameValueSeparators", + "ParseOptionsAttribute.PrefixTermination": "P_Ookii_CommandLine_ParseOptionsAttribute_PrefixTermination", "ParseResult.ArgumentName": "P_Ookii_CommandLine_ParseResult_ArgumentName", "ParseResult.LastException": "P_Ookii_CommandLine_ParseResult_LastException", "ParseResult.RemainingArguments": "P_Ookii_CommandLine_ParseResult_RemainingArguments", @@ -339,6 +344,8 @@ ], "SomeName": null, "SortedDictionary": "#system.collections.generic.sorteddictionary-2", + "StandardStream": "T_Ookii_CommandLine_Terminal_StandardStream", + "StandardStreamExtensions": "T_Ookii_CommandLine_Terminal_StandardStreamExtensions", "StreamReader": "#system.io.streamreader", "String": "#system.string", "StringComparison": "#system.stringcomparison", @@ -362,6 +369,7 @@ "UsageWriter.IncludeApplicationDescription": "P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescription", "UsageWriter.IncludeCommandHelpInstruction": "P_Ookii_CommandLine_UsageWriter_IncludeCommandHelpInstruction", "UsageWriter.IncludeValidatorsInDescription": "P_Ookii_CommandLine_UsageWriter_IncludeValidatorsInDescription", + "UsageWriter.IndentAfterEmptyLine": "P_Ookii_CommandLine_UsageWriter_IndentAfterEmptyLine", "UsageWriter.UseAbbreviatedSyntax": "P_Ookii_CommandLine_UsageWriter_UseAbbreviatedSyntax", "UsageWriter.UseColor": "P_Ookii_CommandLine_UsageWriter_UseColor", "UsageWriter.UseShortNamesForSyntax": "P_Ookii_CommandLine_UsageWriter_UseShortNamesForSyntax", From c1243b6b290339214135524e5edff760fa0ba003 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 12 Dec 2023 18:05:27 -0800 Subject: [PATCH 32/54] Update Indent property XML comment. --- src/Ookii.CommandLine/LineWrappingTextWriter.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Ookii.CommandLine/LineWrappingTextWriter.cs b/src/Ookii.CommandLine/LineWrappingTextWriter.cs index cc54e1d..620543c 100644 --- a/src/Ookii.CommandLine/LineWrappingTextWriter.cs +++ b/src/Ookii.CommandLine/LineWrappingTextWriter.cs @@ -399,9 +399,14 @@ public int MaximumLineLength /// /// /// - /// Whenever a line break is encountered (either because of wrapping or because a line break was written to the - /// ), the next line is indented by the number of characters specified - /// by this property, unless the previous line was blank. + /// Whenever a line break is encountered (either because of wrapping or because a line break + /// was written to the ), the next line is indented by the + /// number of characters specified by this property, unless the property is and the previous line + /// was blank. + /// + /// + /// Changes to this property will not /// /// /// The output position can be reset to the start of the line after a line break by calling From 5d1e72b85e0b48e09d7136ede98f02c29ae2363c Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Wed, 13 Dec 2023 15:50:48 -0800 Subject: [PATCH 33/54] Add an attribute to set a footer. --- .../ArgumentsClassAttributes.cs | 3 + .../ParserGenerator.cs | 12 ++++ src/Ookii.CommandLine.Generator/TypeHelper.cs | 2 + src/Ookii.CommandLine.Tests/ArgumentTypes.cs | 1 + .../CommandLineParserTest.Usage.cs | 4 ++ .../CommandLineParserTest.cs | 19 +++++- src/Ookii.CommandLine/CommandLineParser.cs | 23 +++++-- .../Support/ArgumentProvider.cs | 9 +++ .../Support/ReflectionArgumentProvider.cs | 2 + src/Ookii.CommandLine/UsageFooterAttribute.cs | 60 +++++++++++++++++++ src/Ookii.CommandLine/UsageWriter.cs | 11 +++- 11 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 src/Ookii.CommandLine/UsageFooterAttribute.cs diff --git a/src/Ookii.CommandLine.Generator/ArgumentsClassAttributes.cs b/src/Ookii.CommandLine.Generator/ArgumentsClassAttributes.cs index 7351964..2847dc8 100644 --- a/src/Ookii.CommandLine.Generator/ArgumentsClassAttributes.cs +++ b/src/Ookii.CommandLine.Generator/ArgumentsClassAttributes.cs @@ -6,6 +6,7 @@ internal readonly struct ArgumentsClassAttributes { private readonly AttributeData? _parseOptions; private readonly AttributeData? _description; + private readonly AttributeData? _usageFooter; private readonly AttributeData? _applicationFriendlyName; private readonly AttributeData? _command; private readonly AttributeData? _generatedParser; @@ -22,6 +23,7 @@ public ArgumentsClassAttributes(ITypeSymbol symbol, TypeHelper typeHelper) { var _ = attribute.CheckType(typeHelper.ParseOptionsAttribute, ref _parseOptions) || attribute.CheckType(typeHelper.DescriptionAttribute, ref _description) || + attribute.CheckType(typeHelper.UsageFooterAttribute, ref _usageFooter) || attribute.CheckType(typeHelper.ApplicationFriendlyNameAttribute, ref _applicationFriendlyName) || attribute.CheckType(typeHelper.CommandAttribute, ref _command) || attribute.CheckType(typeHelper.ClassValidationAttribute, ref _classValidators) || @@ -34,6 +36,7 @@ public ArgumentsClassAttributes(ITypeSymbol symbol, TypeHelper typeHelper) public AttributeData? ParseOptions => _parseOptions; public AttributeData? Description => _description; + public AttributeData? UsageFooter => _usageFooter; public AttributeData? ApplicationFriendlyName => _applicationFriendlyName; public AttributeData? Command => _command; public AttributeData? GeneratedParser => _generatedParser; diff --git a/src/Ookii.CommandLine.Generator/ParserGenerator.cs b/src/Ookii.CommandLine.Generator/ParserGenerator.cs index 4237575..72c300f 100644 --- a/src/Ookii.CommandLine.Generator/ParserGenerator.cs +++ b/src/Ookii.CommandLine.Generator/ParserGenerator.cs @@ -229,6 +229,12 @@ private bool GenerateProvider(ArgumentsClassAttributes attributes, bool isComman _builder.AppendGeneratedCodeAttribute(); _builder.AppendLine("private class OokiiCommandLineArgumentProvider : Ookii.CommandLine.Support.GeneratedArgumentProvider"); _builder.OpenBlock(); + if (attributes.UsageFooter != null) + { + _builder.AppendLine($"private readonly Ookii.CommandLine.UsageFooterAttribute _usageFooter = {attributes.UsageFooter.CreateInstantiation()};"); + _builder.AppendLine(); + } + _builder.AppendLine("public OokiiCommandLineArgumentProvider()"); _builder.IncreaseIndent(); _builder.AppendLine(": base("); @@ -244,6 +250,12 @@ private bool GenerateProvider(ArgumentsClassAttributes attributes, bool isComman _builder.AppendLine(); _builder.AppendLine($"public override bool IsCommand => {isCommand.ToCSharpString()};"); _builder.AppendLine(); + if (attributes.UsageFooter != null) + { + _builder.AppendLine("public override string UsageFooter => _usageFooter.Footer;"); + _builder.AppendLine(); + } + _builder.AppendLine("public override System.Collections.Generic.IEnumerable GetArguments(Ookii.CommandLine.CommandLineParser parser)"); _builder.OpenBlock(); diff --git a/src/Ookii.CommandLine.Generator/TypeHelper.cs b/src/Ookii.CommandLine.Generator/TypeHelper.cs index 7078c12..0859576 100644 --- a/src/Ookii.CommandLine.Generator/TypeHelper.cs +++ b/src/Ookii.CommandLine.Generator/TypeHelper.cs @@ -67,6 +67,8 @@ public TypeHelper(Compilation compilation) public INamedTypeSymbol? CancelMode => _compilation.GetTypeByMetadataName(NamespacePrefix + "CancelMode"); + public INamedTypeSymbol? UsageFooterAttribute => _compilation.GetTypeByMetadataName(NamespacePrefix + "UsageFooterAttribute"); + public INamedTypeSymbol? ArgumentValidationAttribute => _compilation.GetTypeByMetadataName(NamespacePrefix + "Validation.ArgumentValidationAttribute"); public INamedTypeSymbol? ClassValidationAttribute => _compilation.GetTypeByMetadataName(NamespacePrefix + "Validation.ClassValidationAttribute"); diff --git a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs index a69f747..d4f8ed1 100644 --- a/src/Ookii.CommandLine.Tests/ArgumentTypes.cs +++ b/src/Ookii.CommandLine.Tests/ArgumentTypes.cs @@ -696,6 +696,7 @@ partial class AutoPositionArguments : AutoPositionArgumentsBase } [GeneratedParser] +[UsageFooter("Some usage footer.")] partial class EmptyLineDescriptionArguments { [CommandLineArgument] diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs index a445093..c5fd3aa 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.Usage.cs @@ -682,6 +682,8 @@ Displays this help message. -Version [] Displays version information. +Some usage footer. + ".ReplaceLineEndings(); private static readonly string _expectedEmptyLineIndentAfterBlankLineUsage = @"Usage: test [-Argument ] [-Help] [-Version] @@ -697,6 +699,8 @@ Displays this help message. -Version [] Displays version information. +Some usage footer. + ".ReplaceLineEndings(); private static readonly string _expectedDefaultValueFormatUsage = @"Usage: test [-Argument ] [-Argument2 ] [-Help] [-Version] diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index 2711621..689e948 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -472,12 +472,27 @@ public void TestWriteUsageIndentAfterBlankLine(ProviderKind kind) }; var target = CreateParser(kind, options); - string actual = target.GetUsage(options.UsageWriter); + string actual = target.GetUsage(); Assert.AreEqual(_expectedEmptyLineDefaultUsage, actual); options.UsageWriter.IndentAfterEmptyLine = true; - actual = target.GetUsage(options.UsageWriter); + actual = target.GetUsage(); Assert.AreEqual(_expectedEmptyLineIndentAfterBlankLineUsage, actual); + + // Test again with a max length to make sure indents are properly reset where expected. + using var writer = LineWrappingTextWriter.ForStringWriter(80); + var usageWriter = new UsageWriter(writer) + { + ExecutableName = _executableName, + }; + + target.WriteUsage(usageWriter); + Assert.AreEqual(_expectedEmptyLineDefaultUsage, writer.ToString()); + + //((StringWriter)writer.BaseWriter).GetStringBuilder().Clear(); + //usageWriter.IndentAfterEmptyLine = true; + //target.WriteUsage(usageWriter); + //Assert.AreEqual(_expectedEmptyLineIndentAfterBlankLineUsage, writer.ToString()); } [TestMethod] diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index d511db8..c456337 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -488,17 +488,32 @@ public CommandLineParser(ArgumentProvider provider, ParseOptions? options = null /// Gets a description that is used when generating usage information. /// /// - /// The description of the command line application. The default value is an empty string (""). + /// The description of the command line application. /// /// /// - /// If not empty, this description will be added to the usage returned by the - /// method. This description can be set by applying the to - /// the command line arguments type. + /// If not empty, this description will be added at the top of the usage help returned by the + /// method. This description can be set by applying the + /// attribute to the command line arguments class. /// /// public string Description => _provider.Description; + /// + /// Gets footer text that is used when generating usage information. + /// + /// + /// The footer text. + /// + /// + /// + /// If not empty, this footer will be added at the bottom of the usage help returned by the + /// method. This footer can be set by applying the + /// attribute to the command line arguments class. + /// + /// + public string UsageFooter => _provider.UsageFooter; + /// /// Gets the options used by this instance. /// diff --git a/src/Ookii.CommandLine/Support/ArgumentProvider.cs b/src/Ookii.CommandLine/Support/ArgumentProvider.cs index 4324bfb..e96258c 100644 --- a/src/Ookii.CommandLine/Support/ArgumentProvider.cs +++ b/src/Ookii.CommandLine/Support/ArgumentProvider.cs @@ -73,6 +73,15 @@ protected ArgumentProvider( /// public abstract string Description { get; } + /// + /// Gets footer text that is used when generating usage information. + /// + /// + /// The footer text. + /// + // N.B. This is virtual, not abstract, for binary compatibility with v4.0. + public virtual string UsageFooter => string.Empty; + /// /// Gets the that was applied to the arguments type. /// diff --git a/src/Ookii.CommandLine/Support/ReflectionArgumentProvider.cs b/src/Ookii.CommandLine/Support/ReflectionArgumentProvider.cs index 8aac2a0..e17547d 100644 --- a/src/Ookii.CommandLine/Support/ReflectionArgumentProvider.cs +++ b/src/Ookii.CommandLine/Support/ReflectionArgumentProvider.cs @@ -35,6 +35,8 @@ public override string ApplicationFriendlyName public override string Description => ArgumentsType.GetCustomAttribute()?.Description ?? string.Empty; + public override string UsageFooter => ArgumentsType.GetCustomAttribute()?.Footer ?? string.Empty; + public override bool IsCommand => CommandInfo.IsCommand(ArgumentsType); public override object CreateInstance(CommandLineParser parser, object?[]? requiredPropertyValues) diff --git a/src/Ookii.CommandLine/UsageFooterAttribute.cs b/src/Ookii.CommandLine/UsageFooterAttribute.cs new file mode 100644 index 0000000..61b01d4 --- /dev/null +++ b/src/Ookii.CommandLine/UsageFooterAttribute.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel; + +namespace Ookii.CommandLine; + +/// +/// Gets or sets a footer that will be added to the usage help for an arguments class. +/// +/// +/// +/// The attribute provides text that's written at the top of +/// the usage help. The attribute does the same thing, but for +/// text that's written at the bottom of the usage help. +/// +/// +/// The footer will only be used when the full usage help is shown, using +/// . +/// +/// +/// You can derive from this attribute to use an alternative source for the footer, such as a +/// resource table that can be localized. +/// +/// +[AttributeUsage(AttributeTargets.Class)] +public class UsageFooterAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// The footer text. + /// + /// is . + /// + public UsageFooterAttribute(string footer) + { + FooterValue = footer ?? throw new ArgumentNullException(nameof(footer)); + } + + /// + /// Gets the footer text. + /// + /// + /// The footer text. + /// + public virtual string Footer => FooterValue; + + /// + /// Gets the footer text stored in this attribute. + /// + /// + /// The footer text. + /// + /// + /// + /// The base class implementation of the property returns the value of + /// this property. + /// + /// + protected string FooterValue { get; } +} diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index d5bb4e2..bc1e8c9 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -1746,13 +1746,18 @@ protected virtual void WriteMoreInfoMessage() /// only if the requested help is . /// /// - /// The base implementation does nothing; this function exists to allow derived classes to - /// easily add a footer to the help. + /// The base implementation writes the value of the + /// property, if it is not an empty string. This value can be set using the + /// attribute. /// /// protected virtual void WriteParserUsageFooter() { - // Nothing + if (!string.IsNullOrEmpty(Parser.UsageFooter)) + { + WriteLine(Parser.UsageFooter); + WriteLine(); + } } /// From 71ae2911ef087012a8d296e2f85effc112c77cb0 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Wed, 13 Dec 2023 16:22:14 -0800 Subject: [PATCH 34/54] Apply indent changes immediately if the current line is empty. --- .../CommandLineParserTest.cs | 9 ++- .../LineWrappingTextWriterTest.Constants.cs | 38 +++++++++- .../LineWrappingTextWriterTest.cs | 18 +++++ .../LineWrappingTextWriter.Async.cs | 18 +++-- .../LineWrappingTextWriter.cs | 73 ++++++++++--------- 5 files changed, 110 insertions(+), 46 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index 689e948..3d86bb8 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -489,10 +489,11 @@ public void TestWriteUsageIndentAfterBlankLine(ProviderKind kind) target.WriteUsage(usageWriter); Assert.AreEqual(_expectedEmptyLineDefaultUsage, writer.ToString()); - //((StringWriter)writer.BaseWriter).GetStringBuilder().Clear(); - //usageWriter.IndentAfterEmptyLine = true; - //target.WriteUsage(usageWriter); - //Assert.AreEqual(_expectedEmptyLineIndentAfterBlankLineUsage, writer.ToString()); + ((StringWriter)writer.BaseWriter).GetStringBuilder().Clear(); + writer.ResetIndent(); + usageWriter.IndentAfterEmptyLine = true; + target.WriteUsage(usageWriter); + Assert.AreEqual(_expectedEmptyLineIndentAfterBlankLineUsage, writer.ToString()); } [TestMethod] diff --git a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs index 49e580d..ea0f415 100644 --- a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs +++ b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.Constants.cs @@ -81,7 +81,7 @@ elementum curabitur. 0123456789012345678901234567890123456789012345678901234567890123456789012345 6789012345678901234567890123456789012345678901234567890123456789012345678901 234567890123456789012345678901234567890123456789 - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. @@ -123,6 +123,42 @@ elementum curabitur. 45678901234567890123456789012345678901234567890123456789 ".ReplaceLineEndings(); + private static readonly string _expectedIndentChangesNoMaximum = @" +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. + +Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae elementum curabitur. + +Lorem 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. + +Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae elementum curabitur. + +Lorem 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. + +Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Vitae tempus + quam pellentesque nec + nam aliquam. Porta non pulvinar neque laoreet suspendisse interdum consectetur. + Arcu risus quis varius quam. Cursus mattis molestie a iaculis at erat. Malesuada fames ac turpis egestas maecenas pharetra. Fringilla est + ullamcorper eget nulla facilisi etiam dignissim diam. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus. + Augue neque gravida in + fermentum et sollicitudin ac orci. Aliquam malesuada bibendum arcu vitae elementum curabitur. + +Lorem 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +".ReplaceLineEndings(); + private static readonly string _expectedIndentNoMaximum = @" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Donec adipiscing tristique risus nec feugiat in fermentum. diff --git a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs index 01a7e72..730c663 100644 --- a/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs +++ b/src/Ookii.CommandLine.Tests/LineWrappingTextWriterTest.cs @@ -137,6 +137,24 @@ public void TestIndentChanges() Assert.AreEqual(_expectedIndentChanges, writer.BaseWriter.ToString()); } + [TestMethod()] + public void TestIndentChangesNoMaximum() + { + using var writer = LineWrappingTextWriter.ForStringWriter(); + writer.Indent = 4; + writer.WriteLine(_input); + writer.Indent = 8; + writer.Write(_input.Trim()); + // Should add a new line. + writer.ResetIndent(); + writer.WriteLine(_input.Trim()); + // Should not add a new line. + writer.ResetIndent(); + writer.Flush(); + + Assert.AreEqual(_expectedIndentChangesNoMaximum, writer.BaseWriter.ToString()); + } + [TestMethod()] public async Task TestIndentChangesAsync() { diff --git a/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs b/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs index ddf3079..41afeba 100644 --- a/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs +++ b/src/Ookii.CommandLine/LineWrappingTextWriter.Async.cs @@ -14,7 +14,7 @@ public partial class LineWrappingTextWriter { private partial class LineBuffer { - public async Task FlushToAsync(TextWriter writer, int indent, bool insertNewLine, CancellationToken cancellationToken) + public async Task FlushToAsync(TextWriter writer, int? indent, bool insertNewLine, CancellationToken cancellationToken) { // Don't use IsContentEmpty because we also want to write if there's only VT sequences. if (_segments.Count != 0) @@ -28,7 +28,7 @@ public async Task WriteLineToAsync(TextWriter writer, int indent, CancellationTo await WriteToAsync(writer, indent, true, cancellationToken); } - private async Task WriteToAsync(TextWriter writer, int indent, bool insertNewLine, CancellationToken cancellationToken) + private async Task WriteToAsync(TextWriter writer, int? indent, bool insertNewLine, CancellationToken cancellationToken) { // Don't use IsContentEmpty because we also want to write if there's only VT sequences. if (_segments.Count != 0) @@ -46,7 +46,11 @@ private async Task WriteToAsync(TextWriter writer, int indent, bool insertNewLin private async Task WriteSegmentsAsync(TextWriter writer, IEnumerable segments, CancellationToken cancellationToken) { - await WriteIndentAsync(writer, Indentation); + if (Indentation is int indentation) + { + await WriteIndentAsync(writer, indentation); + } + foreach (var segment in segments) { switch (segment.Type) @@ -109,7 +113,7 @@ private async Task BreakLineAsync(TextWriter writer, ReadO } int offset = 0; - int contentOffset = Indentation; + int contentOffset = Indentation ?? 0; foreach (var segment in _segments) { offset += segment.Length; @@ -168,7 +172,7 @@ private async Task FlushCoreAsync(bool insertNewLine, CancellationToken cancella ThrowIfWriteInProgress(); if (_lineBuffer != null) { - await _lineBuffer.FlushToAsync(_baseWriter, insertNewLine ? _indent : 0, insertNewLine, cancellationToken); + await _lineBuffer.FlushToAsync(_baseWriter, insertNewLine ? _indent : null, insertNewLine, cancellationToken); } await _baseWriter.FlushAsync(); @@ -180,12 +184,12 @@ private async Task ResetIndentCoreAsync(CancellationToken cancellationToken) { if (!_lineBuffer.IsContentEmpty) { - await _lineBuffer.FlushToAsync(_baseWriter, 0, true, cancellationToken); + await _lineBuffer.FlushToAsync(_baseWriter, null, true, cancellationToken); } else { // Leave non-content segments in the buffer. - _lineBuffer.ClearCurrentLine(0, false); + _lineBuffer.ClearCurrentLine(null, false); } } else diff --git a/src/Ookii.CommandLine/LineWrappingTextWriter.cs b/src/Ookii.CommandLine/LineWrappingTextWriter.cs index 620543c..ec00678 100644 --- a/src/Ookii.CommandLine/LineWrappingTextWriter.cs +++ b/src/Ookii.CommandLine/LineWrappingTextWriter.cs @@ -123,9 +123,13 @@ public LineBuffer(LineWrappingTextWriter writer, int capacity) public bool IsEmpty => _segments.Count == 0; - public int Indentation { get; set; } + private int? Indentation { get; set; } - public int LineLength => ContentLength + Indentation; + public int LineLength => ContentLength + (Indentation ?? 0); + + public Segment? LastSegment => _segments.Count > 0 ? _segments[_segments.Count - 1] : null; + + public bool HasPartialFormatting => LastSegment is Segment last && last.Type >= StringSegmentType.PartialFormattingUnknown; public void Append(ReadOnlySpan span, StringSegmentType type) { @@ -168,17 +172,17 @@ public void Append(ReadOnlySpan span, StringSegmentType type) ContentLength += contentLength; } - public Segment? LastSegment => _segments.Count > 0 ? _segments[_segments.Count - 1] : null; - - public bool HasPartialFormatting => LastSegment is Segment last && last.Type >= StringSegmentType.PartialFormattingUnknown; - - public partial void FlushTo(TextWriter writer, int indent, bool insertNewLine); + public partial void FlushTo(TextWriter writer, int? indent, bool insertNewLine); public partial void WriteLineTo(TextWriter writer, int indent); public void Peek(TextWriter writer) { - WriteIndent(writer, Indentation); + if (Indentation is int indentation) + { + WriteIndent(writer, indentation); + } + int offset = 0; foreach (var segment in _segments) { @@ -218,7 +222,7 @@ public ReadOnlyMemory FindPartialFormattingEnd(ReadOnlyMemory newSeg return newSegment.Slice(FindPartialFormattingEndCore(newSegment.Span)); } - private partial void WriteTo(TextWriter writer, int indent, bool insertNewLine); + private partial void WriteTo(TextWriter writer, int? indent, bool insertNewLine); private int FindPartialFormattingEndCore(ReadOnlySpan newSegment) { @@ -255,7 +259,7 @@ private int FindPartialFormattingEndCore(ReadOnlySpan newSegment) private partial BreakLineResult BreakLine(TextWriter writer, ReadOnlySpan newSegment, int maxLength, int indent, BreakLineMode mode); - public void ClearCurrentLine(int indent, bool clearSegments = true) + public void ClearCurrentLine(int? indent, bool clearSegments = true) { if (clearSegments) { @@ -268,11 +272,21 @@ public void ClearCurrentLine(int indent, bool clearSegments = true) } else { - Indentation = 0; + Indentation = null; } ContentLength = 0; } + + public void UpdateIndent(int indent) + { + // We can apply the new indent immediately if the current line has indentation and is + // blank. + if (IsContentEmpty && Indentation != null) + { + Indentation = indent; + } + } } struct NoWrappingState @@ -350,10 +364,7 @@ public LineWrappingTextWriter(TextWriter baseWriter, int maximumLineLength, bool /// /// The that this is writing to. /// - public TextWriter BaseWriter - { - get { return _baseWriter; } - } + public TextWriter BaseWriter => _baseWriter; /// /// Gets the character encoding in which the output is written. @@ -386,10 +397,7 @@ public override string NewLine /// /// The maximum length of a line, or zero if the line length is not limited. /// - public int MaximumLineLength - { - get { return _maximumLineLength; } - } + public int MaximumLineLength => _maximumLineLength; /// /// Gets or sets the amount of characters to indent all but the first line. @@ -413,9 +421,12 @@ public int MaximumLineLength /// the method. /// /// + /// + /// The property was set to a value less than zero or greater than the maximum line length. + /// public int Indent { - get { return _indent; } + get => _indent; set { if (value < 0 || (_maximumLineLength > 0 && value >= _maximumLineLength)) @@ -424,6 +435,7 @@ public int Indent } _indent = value; + _lineBuffer?.UpdateIndent(value); } } @@ -481,8 +493,8 @@ public WrappingMode Wrapping { // Flush the buffer but not the base writer, and make sure indent is reset // even if the buffer was empty (for consistency). - _lineBuffer.FlushTo(_baseWriter, 0, false); - _lineBuffer.ClearCurrentLine(0); + _lineBuffer.FlushTo(_baseWriter, null, false); + _lineBuffer.ClearCurrentLine(null); // Ensure no state is carried over from the last time this was changed. _noWrappingState = default; @@ -501,10 +513,7 @@ public WrappingMode Wrapping /// A that writes to , /// the standard output stream. /// - public static LineWrappingTextWriter ForConsoleOut() - { - return new LineWrappingTextWriter(Console.Out, GetLineLengthForConsole(), false); - } + public static LineWrappingTextWriter ForConsoleOut() => new(Console.Out, GetLineLengthForConsole(), false); /// /// Gets a that writes to the standard error stream, @@ -514,10 +523,7 @@ public static LineWrappingTextWriter ForConsoleOut() /// A that writes to , /// the standard error stream. /// - public static LineWrappingTextWriter ForConsoleError() - { - return new LineWrappingTextWriter(Console.Error, GetLineLengthForConsole(), false); - } + public static LineWrappingTextWriter ForConsoleError() => new(Console.Error, GetLineLengthForConsole(), false); /// /// Gets a that writes to a . @@ -536,10 +542,9 @@ public static LineWrappingTextWriter ForConsoleError() /// To retrieve the resulting string, call the method. The result /// will include any unflushed text without flushing that text to the . /// - public static LineWrappingTextWriter ForStringWriter(int maximumLineLength = 0, IFormatProvider? formatProvider = null, bool countFormatting = false) - { - return new LineWrappingTextWriter(new StringWriter(formatProvider), maximumLineLength, true, countFormatting); - } + public static LineWrappingTextWriter ForStringWriter(int maximumLineLength = 0, IFormatProvider? formatProvider = null, + bool countFormatting = false) + => new(new StringWriter(formatProvider), maximumLineLength, true, countFormatting); /// public override void Write(char value) From d5914de732a546cd15875e653ee7fd30d2e43f93 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 18 Dec 2023 17:58:22 -0800 Subject: [PATCH 35/54] Add help for -- argument. --- docs/Arguments.md | 22 ++++++++++++++++++++++ docs/DefiningArguments.md | 18 +++++++++++++----- docs/refs.json | 1 + 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/docs/Arguments.md b/docs/Arguments.md index 4290714..c1f3abc 100644 --- a/docs/Arguments.md +++ b/docs/Arguments.md @@ -131,6 +131,26 @@ value1 -Positional1 value2 This is because `-Positional1` is assigned to twice; first by position, and then by name. Duplicate arguments cause an error by default, though this can be changed. +## The `--` argument + +Optionally, when an argument is encountered that consists only of `--` without a name following it, +this indicates that all following values must be treated as positional values, even if they begin +with an argument name prefix. + +For example, take the following command line: + +```text +value1 -- --value2 +``` + +In this example, the second positional argument would be set to the value "--value2". If there is +an argument named "value2", it would not be set. + +This behavior is disabled by default, but can be enabled using the +[`ParseOptionsAttribute.PrefixTermination`][] or [`ParseOptions.PrefixTermination`][] property. It can be +used with both the default parsing mode and long/short mode. Alternatively, you can also have the +`--` argument [cancel parsing](DefiningArguments.md#arguments-that-cancel-parsing). + ## Required arguments A command line argument that is required must be supplied on all invocations of the application. If @@ -431,10 +451,12 @@ Next, let's take a look at how to [define arguments](DefiningArguments.md). [`ParseOptions.ArgumentNameComparison`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameComparison.htm [`ParseOptions.Culture`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_Culture.htm [`ParseOptions.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_NameValueSeparators.htm +[`ParseOptions.PrefixTermination`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_PrefixTermination.htm [`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm [`ParseOptionsAttribute.AllowWhiteSpaceValueSeparator`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_AllowWhiteSpaceValueSeparator.htm [`ParseOptionsAttribute.CaseSensitive`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_CaseSensitive.htm [`ParseOptionsAttribute.NameValueSeparators`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_NameValueSeparators.htm +[`ParseOptionsAttribute.PrefixTermination`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_PrefixTermination.htm [`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm [`String`]: https://learn.microsoft.com/dotnet/api/system.string [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter diff --git a/docs/DefiningArguments.md b/docs/DefiningArguments.md index 851491e..44c85d1 100644 --- a/docs/DefiningArguments.md +++ b/docs/DefiningArguments.md @@ -379,10 +379,11 @@ public bool Help { get; set; } Note that this property will never be set to true by the [`CommandLineParser`][], since no instance will be created if the argument is supplied. -If you set the [`CancelParsing`][CancelParsing_1] property to [`CancelMode.Success`][], parsing is stopped, and the rest -of the command line is not process, but parsing will complete successfully. If all the required -arguments have been specified before that point, the [`CommandLineParser.Parse()`][] method and -various helper methods will return an instance of the arguments type. +If you set the [`CancelParsing`][CancelParsing_1] property to [`CancelMode.Success`][], parsing is +stopped, and the rest of the command line is not processed, but parsing will complete successfully. +If all the required arguments have been specified before that point, the +[`CommandLineParser.Parse()`][] method and various helper methods will return an instance of the +arguments type. The remaining arguments that were not parsed are available in the [`ParseResult.RemainingArguments`][] property. These are available for [`CancelMode.Abort`][], [`CancelMode.Success`][], and if parsing @@ -392,6 +393,10 @@ encountered an error. line processor, for example a child application, or a subcommand. See for example the [top-level arguments sample](../src/Samples/TopLevelArguments). +The `--` argument can also be used to cancel parsing and return success, by setting the +[`ParseOptionsAttribute.PrefixTermination`][] or [`ParseOptions.PrefixTermination`][] property to +[`PrefixTerminationMode.CancelWithSuccess`][]. + ## Using methods You can also apply the [`CommandLineArgumentAttribute`][] to a public static method. Method @@ -654,10 +659,13 @@ Next, we'll take a look at how to [parse the arguments we've defined](ParsingArg [`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LocalizedStringProvider.htm [`ParseOptions.ArgumentNameTransform`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_ArgumentNameTransform.htm [`ParseOptions.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_AutoPrefixAliases.htm +[`ParseOptions.PrefixTermination`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptions_PrefixTermination.htm [`ParseOptions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptions.htm [`ParseOptionsAttribute.AutoPrefixAliases`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_AutoPrefixAliases.htm +[`ParseOptionsAttribute.PrefixTermination`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_PrefixTermination.htm [`ParseOptionsAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseOptionsAttribute.htm [`ParseResult.RemainingArguments`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseResult_RemainingArguments.htm +[`PrefixTerminationMode.CancelWithSuccess`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_PrefixTerminationMode.htm [`ReadOnlySpan`]: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 [`ShortAliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ShortAliasAttribute.htm [`String`]: https://learn.microsoft.com/dotnet/api/system.string @@ -665,8 +673,8 @@ Next, we'll take a look at how to [parse the arguments we've defined](ParsingArg [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter [`TypeDescriptor.GetConverter()`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typedescriptor.getconverter [`ValueDescriptionAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ValueDescriptionAttribute.htm -[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm [`WrappedDefaultTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedDefaultTypeConverter_1.htm +[`WrappedTypeConverter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_WrappedTypeConverter_1.htm [CancelParsing_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm [DefaultValue_1]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm [IsPosix_2]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_ParseOptionsAttribute_IsPosix.htm diff --git a/docs/refs.json b/docs/refs.json index 80819aa..f18af93 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -313,6 +313,7 @@ "P_Ookii_CommandLine_CommandLineArgument_Position", "P_Ookii_CommandLine_CommandLineArgumentAttribute_Position" ], + "PrefixTerminationMode.CancelWithSuccess": "T_Ookii_CommandLine_PrefixTerminationMode", "ProhibitsAttribute": "T_Ookii_CommandLine_Validation_ProhibitsAttribute", "ReadCommand": null, "ReadDirectoryCommand": null, From aa43ae0e678078f78d3c610c33c8dfcbeb7eb45f Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 18 Dec 2023 18:09:11 -0800 Subject: [PATCH 36/54] Add new enum validation docs. --- docs/Arguments.md | 39 +++++++++++++++++++++------------------ docs/ChangeLog.md | 14 +++++++------- docs/Validation.md | 20 ++++++++++---------- docs/refs.json | 3 +++ 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/docs/Arguments.md b/docs/Arguments.md index c1f3abc..710109e 100644 --- a/docs/Arguments.md +++ b/docs/Arguments.md @@ -309,33 +309,32 @@ upgrade code that relied on a [`TypeConverter`][]. ### Enumeration conversion -The [`EnumConverter`][] used for enumeration types relies on the [`Enum.Parse()`][] method. It uses case -insensitive conversion, and allows both the names and underlying value of the enumeration to be -used. This means that e.g. for the [`DayOfWeek`][] enumeration, "Monday", "monday", and "1" can all -be used to indicate [`DayOfWeek.Monday`][]. +The [`EnumConverter`][] used for enumeration types relies on the [`Enum.Parse()`][] method. Its +default behavior is to use case insensitive conversion, and to allow both the names and underlying +value of the enumeration to be used. This means that e.g. for the [`DayOfWeek`][] enumeration, +"Monday", "monday", and "1" can all be used to indicate [`DayOfWeek.Monday`][]. In the case of a numeric value, the converter does not check if the resulting value is valid for the enumeration type, so again for [`DayOfWeek`][], a value of "9" would be converted to `(DayOfWeek)9` even though there is no such value in the enumeration. To ensure the result is constrained to only the defined values of the enumeration, use the -[`ValidateEnumValueAttribute` validator](Validation.md). +[`ValidateEnumValueAttribute` validator](Validation.md). This validator can also be used to alter +the conversion behavior. You can enable case sensitivity with the +[`ValidateEnumValueAttribute.CaseSensitive`][] property, and disallow numeric values with the +[`ValidateEnumValueAttribute.AllowNumericValues`][] property. -The converter allows the use of comma-separated values, which will be combined using a bitwise or -operation. This is allowed regardless of whether or not the [`FlagsAttribute`][] attribute is present on -the enumeration, which can have unexpected results. Using the [`DayOfWeek`][] example again, -"Monday,Tuesday" would result in the value `DayOfWeek.Monday | DayOfWeek.Tuesday`, which is actually -equivalent to [`DayOfWeek.Wednesday`][]. +By default, the converter allows the use of comma-separated values, which will be combined using a +bitwise or operation. This is allowed regardless of whether or not the [`FlagsAttribute`][] +attribute is present on the enumeration, which can have unexpected results. Using the +[`DayOfWeek`][] example again, "Monday,Tuesday" would result in the value +`DayOfWeek.Monday | DayOfWeek.Tuesday`, which is actually equivalent to [`DayOfWeek.Wednesday`][]. -One way to avoid this is to use the following pattern validator, which ensures that the -string value before conversion does not contain a comma: +Comma-separated values can be disabled by using the +[`ValidateEnumValueAttribute.AllowCommaSeparatedValues`][] property. -```csharp -[ValidatePattern("^[^,]*$")] -``` - -You can also use a pattern like `"^[a-zA-Z]"` to ensure the value starts with a letter, to disallow -the use of numeric values entirely. +These properties of the [`ValidateEnumValueAttribute`][] attribute only work if the default +[`EnumConverter`][] is used; a custom converter may or may not check them. ### Multi-value and dictionary value conversion @@ -461,5 +460,9 @@ Next, let's take a look at how to [define arguments](DefiningArguments.md). [`String`]: https://learn.microsoft.com/dotnet/api/system.string [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter [`Uri`]: https://learn.microsoft.com/dotnet/api/system.uri +[`ValidateEnumValueAttribute.AllowCommaSeparatedValues`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowCommaSeparatedValues.htm +[`ValidateEnumValueAttribute.AllowNumericValues`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowNumericValues.htm +[`ValidateEnumValueAttribute.CaseSensitive`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_CaseSensitive.htm +[`ValidateEnumValueAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Validation_ValidateEnumValueAttribute.htm [`ValueConverterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Conversion_ValueConverterAttribute.htm [NullArgumentValue_0]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineArgumentErrorCategory.htm diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index fa1f605..276ddcd 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -2,15 +2,15 @@ ## Ookii.CommandLine 4.1 (TBD) -- Support for using a `--` argument to escape argument names for the remaining arguments, or to - cancel parsing. This can be enabled using [`ParseOptions.PrefixTermination`][] or - [`ParseOptionsAttribute.PrefixTermination`][]. -- The [`ValidateEnumValueAttribute`][] has additional properties to control how enumeration values - are parsed: [`CaseSensitive`][CaseSensitive_1], [`AllowNumericValues`][], and - [`AllowCommaSeparatedValues`][]. +- Support for [using a `--` argument](Arguments.md#the----argument) to escape argument names for the + remaining arguments, or to cancel parsing. This can be enabled using + [`ParseOptions.PrefixTermination`][] or [`ParseOptionsAttribute.PrefixTermination`][]. +- The [`ValidateEnumValueAttribute`][] has additional properties to control how + [enumeration values are parsed](Arguments.md#enumeration-conversion): + [`CaseSensitive`][CaseSensitive_1], [`AllowNumericValues`][], and [`AllowCommaSeparatedValues`][]. - The [`EnumConverter`][] now also checks the [`ValidateEnumValueAttribute.IncludeValuesInErrorMessage`][] property, if the attribute is - present on the argument, so that error message from the converter and validator are consistent. + present on the argument, so that error messages from the converter and validator are consistent. - Support for passing a cancellation token to the [`CommandManager.RunCommandAsync()`][] method. Tasks can access this token by implementing the [`IAsyncCancelableCommand`][] interface. The [`AsyncCommandBase`][] class provides support as well. diff --git a/docs/Validation.md b/docs/Validation.md index d812e7c..bb06654 100644 --- a/docs/Validation.md +++ b/docs/Validation.md @@ -22,16 +22,16 @@ There are validators that check the value of an argument, and validators that ch inter-dependencies. The following are the built-in argument value validators (dependency validators are discussed [below](#argument-dependencies-and-restrictions)): -Validator | Description | Applied --------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------- -[`ValidateCountAttribute`][] | Validates that the number of items for a multi-value argument is in the specified range. | After parsing. -[`ValidateEnumValueAttribute`][] | Validates that the value is one of the defined values for an enumeration. The [`EnumConverter`][] class allows conversion from the underlying value, even if that value is not a defined value for the enumeration. This validator prevents that. See also [enumeration type conversion](Arguments.md#enumeration-conversion). | After conversion. -[`ValidateNotEmptyAttribute`][] | Validates that the value of an argument is not an empty string. | Before conversion. -[`ValidateNotNullAttribute`][] | Validates that the value of an argument is not null. This is only useful if the [`ArgumentConverter`][] for an argument can return null (for example, the [`NullableConverter`][] can). It's not necessary to use this validator on non-nullable value types, or if using .Net 6.0 or later, or [source generation](SourceGeneration.md), on non-nullable reference types. | After conversion. -[`ValidateNotWhiteSpaceAttribute`][] | Validates that the value of an argument is not an empty string or a string containing only white-space characters. | Before conversion. -[`ValidatePatternAttribute`][] | Validates that the value of an argument matches the specified regular expression. | Before conversion. -[`ValidateRangeAttribute`][] | Validates that the value of an argument is in the specified range. This can be used on any type that implements the [`IComparable`][] interface. | After conversion. -[`ValidateStringLengthAttribute`][] | Validates that the length of an argument's string value is in the specified range. | Before conversion. +Validator | Description | Applied +-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------- +[`ValidateCountAttribute`][] | Validates that the number of items for a multi-value argument is in the specified range. | After parsing. +[`ValidateEnumValueAttribute`][] | Validates that the value is one of the defined values for an enumeration. The [`EnumConverter`][] class allows conversion from the underlying value, even if that value is not a defined value for the enumeration. This validator prevents that. It can also be used to customize the behavior of the [`EnumConverter`][] class. See also [enumeration type conversion](Arguments.md#enumeration-conversion). | After conversion. +[`ValidateNotEmptyAttribute`][] | Validates that the value of an argument is not an empty string. | Before conversion. +[`ValidateNotNullAttribute`][] | Validates that the value of an argument is not null. This is only useful if the [`ArgumentConverter`][] for an argument can return null (for example, the [`NullableConverter`][] can). It's not necessary to use this validator on non-nullable value types, or if using .Net 6.0 or later, or [source generation](SourceGeneration.md), on non-nullable reference types. | After conversion. +[`ValidateNotWhiteSpaceAttribute`][] | Validates that the value of an argument is not an empty string or a string containing only white-space characters. | Before conversion. +[`ValidatePatternAttribute`][] | Validates that the value of an argument matches the specified regular expression. | Before conversion. +[`ValidateRangeAttribute`][] | Validates that the value of an argument is in the specified range. This can be used on any type that implements the [`IComparable`][] interface. | After conversion. +[`ValidateStringLengthAttribute`][] | Validates that the length of an argument's string value is in the specified range. | Before conversion. Note that there is no `ValidateSetAttribute`, or an equivalent way to make sure that an argument is one of a predefined set of values, because you're encouraged to use an enumeration type for this diff --git a/docs/refs.json b/docs/refs.json index f18af93..40322b7 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -376,6 +376,9 @@ "UsageWriter.UseShortNamesForSyntax": "P_Ookii_CommandLine_UsageWriter_UseShortNamesForSyntax", "ValidateCountAttribute": "T_Ookii_CommandLine_Validation_ValidateCountAttribute", "ValidateEnumValueAttribute": "T_Ookii_CommandLine_Validation_ValidateEnumValueAttribute", + "ValidateEnumValueAttribute.AllowCommaSeparatedValues": "P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowCommaSeparatedValues", + "ValidateEnumValueAttribute.AllowNumericValues": "P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_AllowNumericValues", + "ValidateEnumValueAttribute.CaseSensitive": "P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_CaseSensitive", "ValidateEnumValueAttribute.IncludeValuesInErrorMessage": "P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_IncludeValuesInErrorMessage", "ValidateNotEmptyAttribute": "T_Ookii_CommandLine_Validation_ValidateNotEmptyAttribute", "ValidateNotNullAttribute": "T_Ookii_CommandLine_Validation_ValidateNotNullAttribute", From e839ce8473d6c34661cd832edf493f1ecd910905 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Mon, 18 Dec 2023 18:14:24 -0800 Subject: [PATCH 37/54] Add async command cancellation token docs. --- docs/Subcommands.md | 8 ++++++++ docs/refs.json | 2 ++ 2 files changed, 10 insertions(+) diff --git a/docs/Subcommands.md b/docs/Subcommands.md index 37e7ae6..a965945 100644 --- a/docs/Subcommands.md +++ b/docs/Subcommands.md @@ -164,6 +164,11 @@ partial class AsyncSleepCommand : AsyncCommandBase } ``` +To support cancellation, you can pass a [`CancellationToken`][] to the +[`CommandManager.RunCommandAsync()`][] method. This token can be accessed by a command if it +implements the [`IAsyncCancelableCommand`][] interface. If you use the [`AsyncCommandBase`][] class, +the token is available using the [`AsyncCommandBase.CancellationToken`][] property. + ### Multiple commands with common arguments You may have multiple commands that have one or more arguments in common. For example, you may have @@ -665,7 +670,9 @@ detail. [`AliasAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_AliasAttribute.htm [`ApplicationFriendlyNameAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ApplicationFriendlyNameAttribute.htm +[`AsyncCommandBase.CancellationToken`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Commands_AsyncCommandBase_CancellationToken.htm [`AsyncCommandBase`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_AsyncCommandBase.htm +[`CancellationToken`]: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken [`CancelMode.Success`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CancelMode.htm [`CommandAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_CommandAttribute.htm [`CommandLineArgumentAttribute.CancelParsing`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing.htm @@ -689,6 +696,7 @@ detail. [`Environment.GetCommandLineArgs()`]: https://learn.microsoft.com/dotnet/api/system.environment.getcommandlineargs [`GeneratedCommandManagerAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_GeneratedCommandManagerAttribute.htm [`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm +[`IAsyncCancelableCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_IAsyncCancelableCommand.htm [`IAsyncCommand.RunAsync()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_IAsyncCommand_RunAsync.htm [`IAsyncCommand`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Commands_IAsyncCommand.htm [`ICommand.Run()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Commands_ICommand_Run.htm diff --git a/docs/refs.json b/docs/refs.json index 40322b7..bfecc57 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -31,8 +31,10 @@ "ArgumentValidationWithHelpAttribute": "T_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute", "AssemblyTitleAttribute": "#system.reflection.assemblytitleattribute", "AsyncCommandBase": "T_Ookii_CommandLine_Commands_AsyncCommandBase", + "AsyncCommandBase.CancellationToken": "P_Ookii_CommandLine_Commands_AsyncCommandBase_CancellationToken", "AsyncCommandBase.Run()": "M_Ookii_CommandLine_Commands_AsyncCommandBase_Run", "Bar": null, + "CancellationToken": "#system.threading.cancellationtoken", "CancelMode": "T_Ookii_CommandLine_CancelMode", "CancelMode.Abort": "T_Ookii_CommandLine_CancelMode", "CancelMode.None": "T_Ookii_CommandLine_CancelMode", From 99ed886c6f1f8d9a0463ede2513275f3d8e3d1b9 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 13:30:31 -0800 Subject: [PATCH 38/54] Refactor for unknown argument event. --- src/Ookii.CommandLine/CommandLineArgument.cs | 2 +- src/Ookii.CommandLine/CommandLineParser.cs | 370 +++++++++++------- .../UnknownArgumentEventArgs.cs | 43 ++ 3 files changed, 283 insertions(+), 132 deletions(-) create mode 100644 src/Ookii.CommandLine/UnknownArgumentEventArgs.cs diff --git a/src/Ookii.CommandLine/CommandLineArgument.cs b/src/Ookii.CommandLine/CommandLineArgument.cs index c5e9e75..903df1f 100644 --- a/src/Ookii.CommandLine/CommandLineArgument.cs +++ b/src/Ookii.CommandLine/CommandLineArgument.cs @@ -841,7 +841,7 @@ public string Description /// /// /// - /// For dictionary arguments, this property only returns the information that apples to both + /// For dictionary arguments, this property only returns the information that applies to both /// dictionary and multi-value arguments. For information that applies to dictionary /// arguments, but not other types of multi-value arguments, use the /// property. diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index c456337..e4ae9a9 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -184,6 +184,47 @@ private struct PrefixInfo public bool Short { get; set; } } + private struct ParseState + { + public CommandLineParser Parser; + + public ReadOnlyMemory Arguments; + + public int Index; + + public int PositionalArgumentIndex; + + public bool PositionalOnly; + + public CancelMode CancelParsing; + + public CommandLineArgument? Argument; + + public ReadOnlyMemory ArgumentName; + + public ReadOnlyMemory? ArgumentValue; + + public bool IsUnknown; + + public bool IsSpecifiedByPosition; + + public readonly CommandLineArgument? PositionalArgument + => PositionalArgumentIndex < Parser._positionalArgumentCount ? Parser.Arguments[PositionalArgumentIndex] : null; + + public readonly string RealArgumentName => Argument?.ArgumentName ?? ArgumentName.ToString(); + + public readonly ReadOnlyMemory RemainingArguments => Arguments.Slice(Index + 1); + + public void ResetForNextArgument() + { + Argument = null; + ArgumentName = default; + ArgumentValue = null; + IsUnknown = false; + IsSpecifiedByPosition = false; + } + } + #endregion private readonly ArgumentProvider _provider; @@ -259,6 +300,8 @@ private struct PrefixInfo /// public event EventHandler? DuplicateArgument; + public event EventHandler? UnknownArgument; + internal const string UnreferencedCodeHelpUrl = "https://www.ookii.org/Link/CommandLineSourceGeneration"; /// @@ -938,16 +981,21 @@ public string GetUsage(UsageWriter? usageWriter = null, int maximumLineLength = /// The command line arguments. public object? Parse(ReadOnlyMemory args) { - int index = -1; + var state = new ParseState() + { + Parser = this, + Arguments = args, + }; + try { HelpRequested = false; - return ParseCore(args, ref index); + return ParseCore(ref state); } catch (CommandLineArgumentException ex) { HelpRequested = true; - ParseResult = ParseResult.FromException(ex, args.Slice(index)); + ParseResult = ParseResult.FromException(ex, args.Slice(state.Index)); throw; } } @@ -1296,24 +1344,21 @@ public static ImmutableArray GetDefaultArgumentNamePrefixes() /// Raises the event. /// /// The data for the event. - protected virtual void OnArgumentParsed(ArgumentParsedEventArgs e) - { - ArgumentParsed?.Invoke(this, e); - } + protected virtual void OnArgumentParsed(ArgumentParsedEventArgs e) => ArgumentParsed?.Invoke(this, e); /// /// Raises the event. /// /// The data for the event. - protected virtual void OnDuplicateArgument(DuplicateArgumentEventArgs e) - { - DuplicateArgument?.Invoke(this, e); - } + protected virtual void OnDuplicateArgument(DuplicateArgumentEventArgs e) => DuplicateArgument?.Invoke(this, e); - internal static bool ShouldIndent(LineWrappingTextWriter writer) - { - return writer.MaximumLineLength is 0 or >= 30; - } + /// + /// Raises the event. + /// + /// The data for the event. + protected virtual void OnUnknownArgument(UnknownArgumentEventArgs e) => UnknownArgument?.Invoke(this, e); + + internal static bool ShouldIndent(LineWrappingTextWriter writer) => writer.MaximumLineLength is 0 or >= 30; internal static void WriteError(ParseOptions options, string message, TextFormat color, bool blankLine = false) { @@ -1491,80 +1536,56 @@ private void VerifyPositionalArgumentRules() } } - private object? ParseCore(ReadOnlyMemory args, ref int x) + private object? ParseCore(ref ParseState state) { - // Reset all arguments to their default value. - foreach (CommandLineArgument argument in _arguments) + Reset(); + for (; state.Index < state.Arguments.Length; ++state.Index) { - argument.Reset(); - } - - HelpRequested = false; - int positionalArgumentIndex = 0; - - bool optionalOnly = false; - var cancelParsing = CancelMode.None; - CommandLineArgument? lastArgument = null; - for (x = 0; x < args.Length; ++x) - { - string arg = args.Span[x]; - if (!optionalOnly && - _parseOptions.PrefixTerminationOrDefault != PrefixTerminationMode.None && - arg == _longArgumentNamePrefix) + var token = state.Arguments.Span[state.Index]; + state.ResetForNextArgument(); + if (!state.PositionalOnly && token == _longArgumentNamePrefix) { if (_parseOptions.PrefixTerminationOrDefault == PrefixTerminationMode.PositionalOnly) { - optionalOnly = true; + state.PositionalOnly = true; continue; } else if (_parseOptions.PrefixTerminationOrDefault == PrefixTerminationMode.CancelWithSuccess) { - cancelParsing = CancelMode.Success; - lastArgument = null; + state.CancelParsing = CancelMode.Success; + state.ArgumentName = default; break; } } - var argumentNamePrefix = optionalOnly ? null : CheckArgumentNamePrefix(arg); - if (argumentNamePrefix != null) + if (state.PositionalOnly || !FindNamedArgument(token, ref state)) { - // If white space was the value separator, this function returns the index of argument containing the value for the named argument. - // It returns -1 if parsing was canceled by the ArgumentParsed event handler or the CancelParsing property. - (cancelParsing, x, lastArgument) = ParseNamedArgument(args.Span, x, argumentNamePrefix.Value); - if (cancelParsing != CancelMode.None) - { - break; - } + state.IsSpecifiedByPosition = true; + state.ArgumentValue = token.AsMemory(); + FindPositionalArgument(ref state); } - else + + if (state.IsUnknown) { - // If this is a multi-value argument is must be the last argument. - if (positionalArgumentIndex < _positionalArgumentCount && _arguments[positionalArgumentIndex].MultiValueInfo == null) - { - // Skip named positional arguments that have already been specified by name. - while (positionalArgumentIndex < _positionalArgumentCount && _arguments[positionalArgumentIndex].MultiValueInfo == null && _arguments[positionalArgumentIndex].HasValue) - { - ++positionalArgumentIndex; - } - } + HandleUnknownArgument(ref state); + } - if (positionalArgumentIndex >= _positionalArgumentCount) - { - throw StringProvider.CreateException(CommandLineArgumentErrorCategory.TooManyArguments); - } + // Argument can be null without IsUnknown set if the token was a combined short switch + // argument. + if (state.Argument != null) + { + ParseArgumentValue(ref state); + } - lastArgument = _arguments[positionalArgumentIndex]; - cancelParsing = ParseArgumentValue(lastArgument, arg, arg.AsMemory()); - if (cancelParsing != CancelMode.None) - { - break; - } + if (state.CancelParsing != CancelMode.None) + { + break; } } - if (cancelParsing == CancelMode.Abort) + if (state.CancelParsing == CancelMode.Abort) { - ParseResult = ParseResult.FromCanceled(lastArgument!.ArgumentName, args.Slice(x + 1)); + ParseResult = ParseResult.FromCanceled(state.RealArgumentName, state.RemainingArguments); return null; } @@ -1577,6 +1598,19 @@ private void VerifyPositionalArgumentRules() // Run class validators. _provider.RunValidators(this); + var result = CreateResultInstance(); + + ParseResult = state.CancelParsing == CancelMode.None + ? ParseResult.FromSuccess() + : ParseResult.FromSuccess(state.Argument?.ArgumentName ?? LongArgumentNamePrefix, state.RemainingArguments); + + // Reset to false in case it was set by a method argument that didn't cancel parsing. + HelpRequested = false; + return result; + } + + private object CreateResultInstance() + { object commandLineArguments; try { @@ -1607,13 +1641,61 @@ private void VerifyPositionalArgumentRules() argument.ApplyPropertyValue(commandLineArguments); } - ParseResult = cancelParsing == CancelMode.None - ? ParseResult.FromSuccess() - : ParseResult.FromSuccess(lastArgument?.ArgumentName ?? LongArgumentNamePrefix, args.Slice(x + 1)); + return commandLineArguments; + } - // Reset to false in case it was set by a method argument that didn't cancel parsing. + private void Reset() + { HelpRequested = false; - return commandLineArguments; + + // Reset all arguments to their default value, and mark them as unassigned. + foreach (var argument in _arguments) + { + argument.Reset(); + } + } + + private void ParseArgumentValue(ref ParseState state) + { + Debug.Assert(state.Argument != null); + + var argument = state.Argument!; + bool parsedValue = false; + if (state.ArgumentValue == null && !argument.IsSwitch && AllowWhiteSpaceValueSeparator) + { + // No value separator was present in the token, but a value is required and white space is + // allowed. We take the next token as the value. For multi-value arguments that can consume + // multiple tokens, we keep going until we hit another argument name. + var allowMultiToken = argument.MultiValueInfo is MultiValueArgumentInfo info + && (info.AllowWhiteSpaceSeparator || state.IsSpecifiedByPosition); + + int index; + for (index = state.Index + 1; index < state.Arguments.Length; ++index) + { + var stringValue = state.Arguments.Span[index]; + if (CheckArgumentNamePrefix(stringValue) != null) + { + --index; + break; + } + + parsedValue = true; + state.CancelParsing = ParseArgumentValue(argument, stringValue, stringValue.AsMemory()); + if (state.CancelParsing != CancelMode.None || !allowMultiToken) + { + break; + } + } + + state.Index = index; + } + + // If the value was not parsed above, parse it now. In case there is no value and it's + // not a switch, CommandLineArgument.SetValue will throw an exception. + if (!parsedValue) + { + state.CancelParsing = ParseArgumentValue(argument, null, state.ArgumentValue); + } } private CancelMode ParseArgumentValue(CommandLineArgument argument, string? stringValue, ReadOnlyMemory? memoryValue) @@ -1662,74 +1744,67 @@ private CancelMode ParseArgumentValue(CommandLineArgument argument, string? stri return e.CancelParsing; } - private (CancelMode, int, CommandLineArgument?) ParseNamedArgument(ReadOnlySpan args, int index, PrefixInfo prefix) + private static void FindPositionalArgument(ref ParseState state) { - var (argumentName, argumentValue) = args[index].AsMemory(prefix.Prefix.Length).SplitFirstOfAny(_nameValueSeparators.AsSpan()); + // Skip named positional arguments that have already been specified by name, unless it's + // a multi-value argument which must be the last positional argument. + while (state.PositionalArgument is CommandLineArgument current && current.MultiValueInfo == null && current.HasValue) + { + ++state.PositionalArgumentIndex; + } - CancelMode cancelParsing; - CommandLineArgument? argument = null; - if (_argumentsByShortName != null && prefix.Short) + state.Argument = state.PositionalArgument; + if (state.Argument == null) { - if (argumentName.Length == 1) - { - argument = GetShortArgumentOrThrow(argumentName.Span[0]); - } - else - { - CommandLineArgument? lastArgument; - (cancelParsing, lastArgument) = ParseShortArgument(argumentName.Span, argumentValue); - return (cancelParsing, index, lastArgument); - } + state.IsUnknown = true; + return; } - if (argument == null && !_argumentsByName.TryGetValue(argumentName, out argument)) + state.ArgumentName = state.Argument.ArgumentName.AsMemory(); + } + + private bool FindNamedArgument(string token, ref ParseState state) + { + if (CheckArgumentNamePrefix(token) is not PrefixInfo prefix) { - if (Options.AutoPrefixAliasesOrDefault) + return false; + } + + (state.ArgumentName, state.ArgumentValue) = + token.AsMemory(prefix.Prefix.Length).SplitFirstOfAny(_nameValueSeparators.AsSpan()); + + if (_argumentsByShortName != null && prefix.Short) + { + if (state.ArgumentName.Length == 1) { - argument = GetArgumentByNamePrefix(argumentName.Span); + state.Argument = GetShortArgument(state.ArgumentName.Span[0]); + state.IsUnknown = state.Argument == null; + return true; } - - if (argument == null) + else { - throw StringProvider.CreateException(CommandLineArgumentErrorCategory.UnknownArgument, argumentName.ToString()); + ParseCombinedShortArgument(ref state); + return true; } } - argument.SetUsedArgumentName(argumentName); - if (!argumentValue.HasValue && !argument.IsSwitch && AllowWhiteSpaceValueSeparator) + if (state.Argument == null && !_argumentsByName.TryGetValue(state.ArgumentName, out state.Argument)) { - string? argumentValueString = null; - - // No separator was present but a value is required. We take the next argument as - // its value. For multi-value arguments that can consume multiple values, we keep - // going until we hit another argument name. - while (index + 1 < args.Length && CheckArgumentNamePrefix(args[index + 1]) == null) + if (Options.AutoPrefixAliasesOrDefault) { - ++index; - argumentValueString = args[index]; - - cancelParsing = ParseArgumentValue(argument, argumentValueString, argumentValueString.AsMemory()); - if (cancelParsing != CancelMode.None) - { - return (cancelParsing, index, argument); - } - - if (argument.MultiValueInfo is not MultiValueArgumentInfo { AllowWhiteSpaceSeparator: true }) - { - break; - } + state.Argument = GetArgumentByNamePrefix(state.ArgumentName.Span); } - if (argumentValueString != null) + if (state.Argument == null) { - return (CancelMode.None, index, argument); + state.IsUnknown = true; + return true; } } - // ParseArgumentValue returns true if parsing was canceled by the ArgumentParsed event handler - // or the CancelParsing property. - cancelParsing = ParseArgumentValue(argument, null, argumentValue); - return (cancelParsing, index, argument); + state.Argument.SetUsedArgumentName(state.ArgumentName); + state.ArgumentName = state.Argument.ArgumentName.AsMemory(); + return true; } private CommandLineArgument? GetArgumentByNamePrefix(ReadOnlySpan prefix) @@ -1771,25 +1846,57 @@ private CancelMode ParseArgumentValue(CommandLineArgument argument, string? stri return foundArgument; } - private (CancelMode, CommandLineArgument?) ParseShortArgument(ReadOnlySpan name, ReadOnlyMemory? value) + private void ParseCombinedShortArgument(ref ParseState state) { - CommandLineArgument? arg = null; - foreach (var ch in name) + var combinedName = state.ArgumentName.Span; + foreach (var ch in combinedName) { - arg = GetShortArgumentOrThrow(ch); - if (!arg.IsSwitch) + var argument = GetShortArgument(ch); + if (argument == null) + { + state.ArgumentName = ch.ToString().AsMemory(); + HandleUnknownArgument(ref state); + continue; + } + + if (!argument.IsSwitch) { - throw StringProvider.CreateException(CommandLineArgumentErrorCategory.CombinedShortNameNonSwitch, name.ToString()); + throw StringProvider.CreateException(CommandLineArgumentErrorCategory.CombinedShortNameNonSwitch, + combinedName.ToString()); } - var cancelParsing = ParseArgumentValue(arg, null, value); - if (cancelParsing != CancelMode.None) + state.ArgumentName = argument.ArgumentName.AsMemory(); + state.CancelParsing = ParseArgumentValue(argument, null, state.ArgumentValue); + if (state.CancelParsing != CancelMode.None) { - return (cancelParsing, arg); + break; } } + } + + private void HandleUnknownArgument(ref ParseState state) + { + var eventArgs = new UnknownArgumentEventArgs(state.Arguments.Span[state.Index], state.ArgumentName, + state.ArgumentValue ?? default); - return (CancelMode.None, arg); + OnUnknownArgument(eventArgs); + if (eventArgs.CancelParsing != CancelMode.None) + { + state.CancelParsing = eventArgs.CancelParsing; + return; + } + + if (!eventArgs.Ignore) + { + if (state.ArgumentName.Length > 0) + { + throw StringProvider.CreateException(CommandLineArgumentErrorCategory.UnknownArgument, + state.ArgumentName.ToString()); + } + + + throw StringProvider.CreateException(CommandLineArgumentErrorCategory.TooManyArguments); + } } private CommandLineArgument GetShortArgumentOrThrow(char shortName) @@ -1804,7 +1911,8 @@ private CommandLineArgument GetShortArgumentOrThrow(char shortName) private PrefixInfo? CheckArgumentNamePrefix(string argument) { - // Even if '-' is the argument name prefix, we consider an argument starting with dash followed by a digit as a value, because it could be a negative number. + // Even if '-' is the argument name prefix, we consider an argument starting with dash + // followed by a digit as a value, because it could be a negative number. if (argument.Length >= 2 && argument[0] == '-' && char.IsDigit(argument, 1)) { return null; diff --git a/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs new file mode 100644 index 0000000..3e6bc8e --- /dev/null +++ b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs @@ -0,0 +1,43 @@ +using System; + +namespace Ookii.CommandLine; + +public class UnknownArgumentEventArgs : EventArgs +{ + public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnlyMemory value) + { + Token = token ?? throw new ArgumentNullException(nameof(token)); + Name = name; + Value = value; + } + + public string Token { get; } + + public ReadOnlyMemory Name { get; } + + public ReadOnlyMemory Value { get; } + + public bool Ignore { get; set; } + + /// + /// Gets a value that indicates whether parsing should be canceled when the event handler + /// returns. + /// + /// + /// One of the values of the enumeration. The default value is + /// . + /// + /// + /// + /// If the event handler sets this property to a value other than , + /// command line processing will stop immediately, returning either or + /// an instance of the arguments class according to the value. + /// + /// + /// If you want usage help to be displayed after canceling, set the + /// property to . + /// + /// + public CancelMode CancelParsing { get; set; } + +} From dbde3583ba579f5f263151a70852764802957267 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 15:03:24 -0800 Subject: [PATCH 39/54] Testing and docs for UnknownArgument event. --- docs/ChangeLog.md | 2 + docs/ParsingArguments.md | 28 +++++- docs/refs.json | 1 + .../CommandLineParserTest.cs | 93 ++++++++++++++++++- src/Ookii.CommandLine/CommandLineParser.cs | 38 +++++--- .../UnknownArgumentEventArgs.cs | 72 +++++++++++++- 6 files changed, 217 insertions(+), 17 deletions(-) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 276ddcd..3360714 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -5,6 +5,7 @@ - Support for [using a `--` argument](Arguments.md#the----argument) to escape argument names for the remaining arguments, or to cancel parsing. This can be enabled using [`ParseOptions.PrefixTermination`][] or [`ParseOptionsAttribute.PrefixTermination`][]. +- Ignore unknown arguments by using the new [`CommandLineParser.UnknownArgument`][] event. - The [`ValidateEnumValueAttribute`][] has additional properties to control how [enumeration values are parsed](Arguments.md#enumeration-conversion): [`CaseSensitive`][CaseSensitive_1], [`AllowNumericValues`][], and [`AllowCommaSeparatedValues`][]. @@ -258,6 +259,7 @@ may require substantial code changes and may change how command lines are parsed [`CommandLineArgumentAttribute.DefaultValueFormat`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValueFormat.htm [`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp.htm [`CommandLineParser.ParseResult`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_ParseResult.htm +[`CommandLineParser.UnknownArgument`]: https://www.ookii.org/docs/commandline-4.1/html/E_Ookii_CommandLine_CommandLineParser_UnknownArgument.htm [`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser.htm [`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm [`CommandLineParser`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_CommandLineParser_1.htm diff --git a/docs/ParsingArguments.md b/docs/ParsingArguments.md index a486b42..04166f9 100644 --- a/docs/ParsingArguments.md +++ b/docs/ParsingArguments.md @@ -106,9 +106,10 @@ and create your own error message. The generated [`Parse()`][Parse()_7] methods and the static [`Parse()`][Parse()_1] method and their overloads will likely be sufficient for most use cases. However, sometimes you may want even -more fine-grained control. This includes the ability to handle the [`ArgumentParsed`][] and -[`DuplicateArgument`][DuplicateArgument_0] events, and to get additional information about the -arguments using the [`Arguments`][Arguments_0] property or the [`GetArgument`][] function. +more fine-grained control. This includes the ability to handle the [`ArgumentParsed`][], +`UnknownArgument` and [`DuplicateArgument`][DuplicateArgument_0] events, and to get additional +information about the arguments using the [`Arguments`][Arguments_0] property or the +[`GetArgument`][] function. In this case, you can manually create an instance of the [`CommandLineParser`][] class. Then, call the instance [`ParseWithErrorHandling()`][ParseWithErrorHandling()_1] or [`Parse()`][Parse()_5] method. @@ -138,6 +139,27 @@ if (arguments == null) } ``` +Or, you could use this to handle the `UnknownArgument` event to collect a list of unrecognized +arguments: + +```csharp +var unknownArguments = new List(); +var parser = MyArguments.CreateParser(); +parser.UnknownArgument += (_, e) => +{ + // Note: in long/short mode, this may not have the desired effect for a combined switch argument + // where one of the switches is unknown. + unknownArguments.Add(e.Token); + e.Ignore = true; +}; + +var arguments = parser.ParseWithErrorHandling(); +if (arguments == null) +{ + return 1; +} +``` + The status will be set to [`ParseStatus.Canceled`][] if parsing was canceled with [`CancelMode.Abort`][]. You can also use the [`ParseResult.ArgumentName`][] property to determine which argument canceled diff --git a/docs/refs.json b/docs/refs.json index bfecc57..5bfc335 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -84,6 +84,7 @@ "CommandLineParser.Parse()": "M_Ookii_CommandLine_CommandLineParser_Parse__1", "CommandLineParser.ParseResult": "P_Ookii_CommandLine_CommandLineParser_ParseResult", "CommandLineParser.ParseWithErrorHandling()": "Overload_Ookii_CommandLine_CommandLineParser_ParseWithErrorHandling", + "CommandLineParser.UnknownArgument": "E_Ookii_CommandLine_CommandLineParser_UnknownArgument", "CommandLineParser.WriteUsage()": "M_Ookii_CommandLine_CommandLineParser_WriteUsage", "CommandLineParser": "T_Ookii_CommandLine_CommandLineParser_1", "CommandLineParser.Parse()": "Overload_Ookii_CommandLine_CommandLineParser_1_Parse", diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index 3d86bb8..5c4a28c 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -621,7 +621,7 @@ public void TestCancelParsing(ProviderKind kind) Assert.IsTrue(parser.HelpRequested); Assert.AreEqual(ParseStatus.Canceled, parser.ParseResult.Status); Assert.IsNull(parser.ParseResult.LastException); - AssertSpanEqual(new[] { "-Argument2", "bar" }.AsSpan(), parser.ParseResult.RemainingArguments.Span); + AssertSpanEqual(["-Argument2", "bar"], parser.ParseResult.RemainingArguments.Span); Assert.AreEqual("DoesCancel", parser.ParseResult.ArgumentName); Assert.IsTrue(parser.GetArgument("Argument1")!.HasValue); Assert.AreEqual("foo", (string?)parser.GetArgument("Argument1")!.Value); @@ -1466,6 +1466,97 @@ public void TestAutoPosition() } } + [TestMethod] + [DynamicData(nameof(ProviderKinds), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))] + public void TestUnknownArgument(ProviderKind kind) + { + var parser = CreateParser(kind); + ReadOnlyMemory expectedName = default; + ReadOnlyMemory expectedValue = default; + var expectedToken = ""; + var ignore = false; + var cancel = CancelMode.None; + var eventRaised = false; + parser.UnknownArgument += (_, e) => + { + AssertMemoryEqual(expectedName, e.Name); + AssertMemoryEqual(expectedValue, e.Value); + Assert.AreEqual(expectedToken, e.Token); + e.CancelParsing = cancel; + e.Ignore = ignore; + eventRaised = true; + }; + + expectedName = "Unknown".AsMemory(); + expectedToken = "--Unknown"; + CheckThrows(parser, ["--arg1", "5", "--Unknown", "foo"], CommandLineArgumentErrorCategory.UnknownArgument, "Unknown", remainingArgumentCount: 2); + Assert.IsTrue(eventRaised); + + eventRaised = false; + ignore = true; + var result = CheckSuccess(parser, ["--arg1", "5", "--Unknown", "1"]); + Assert.AreEqual(1, result.Foo); + Assert.AreEqual(5, result.Arg1); + Assert.IsTrue(eventRaised); + + eventRaised = false; + cancel = CancelMode.Success; + result = CheckSuccess(parser, ["--arg1", "5", "--Unknown", "1"], "Unknown", 1); + Assert.AreEqual(0, result.Foo); + Assert.AreEqual(5, result.Arg1); + Assert.IsTrue(eventRaised); + + eventRaised = false; + cancel = CancelMode.Abort; + CheckCanceled(parser, ["--arg1", "5", "--Unknown", "1"], "Unknown", false, 1); + Assert.IsTrue(eventRaised); + + // With a value. + expectedValue = "foo".AsMemory(); + expectedToken = "--Unknown:foo"; + CheckCanceled(parser, ["--arg1", "5", "--Unknown:foo", "1"], "Unknown", false, 1); + Assert.IsTrue(eventRaised); + + // Now with a short name. + eventRaised = false; + expectedName = "z".AsMemory(); + expectedValue = default; + expectedToken = "-z"; + cancel = CancelMode.None; + result = CheckSuccess(parser, ["--arg1", "5", "-z", "1"]); + Assert.AreEqual(1, result.Foo); + Assert.AreEqual(5, result.Arg1); + Assert.IsTrue(eventRaised); + + // One in a combined short name. + eventRaised = false; + expectedToken = "-szu"; + cancel = CancelMode.None; + result = CheckSuccess(parser, ["--arg1", "5", "-szu", "1"]); + Assert.AreEqual(1, result.Foo); + Assert.AreEqual(5, result.Arg1); + Assert.IsTrue(result.Switch1); + Assert.IsTrue(result.Switch3); + Assert.IsTrue(eventRaised); + + // Positional + eventRaised = false; + expectedName = default; + expectedValue = "4".AsMemory(); + expectedToken = "4"; + result = CheckSuccess(parser, ["1", "2", "3", "4", "--arg1", "5"]); + Assert.AreEqual(1, result.Foo); + Assert.AreEqual(2, result.Bar); + Assert.AreEqual(3, result.Arg2); + Assert.AreEqual(5, result.Arg1); + Assert.IsTrue(eventRaised); + + eventRaised = false; + ignore = false; + CheckThrows(parser, ["1", "2", "3", "4", "--arg1", "5"], CommandLineArgumentErrorCategory.TooManyArguments, remainingArgumentCount: 3); + Assert.IsTrue(eventRaised); + } + private class ExpectedArgument { public ExpectedArgument(string name, Type type, ArgumentKind kind = ArgumentKind.SingleValue) diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index e4ae9a9..0d8f8e6 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -294,12 +294,34 @@ public void ResetForNextArgument() /// arguments. /// /// - /// This even is only raised when the property is + /// This event is only raised when the property is /// . /// /// public event EventHandler? DuplicateArgument; + /// + /// Event raised when an unknown argument name or a positional value with no matching argument + /// is used. + /// + /// + /// + /// Specifying an argument with an unknown name, or too many positional arguments, is normally + /// an error. By handling this event and setting the + /// property to + /// , you can instead continue parsing the remainder of the command + /// line, ignoring the unknown argument. + /// + /// + /// You can also cancel parsing instead using the + /// property. + /// + /// + /// If an unknown argument name is encountered and is followed by a value separated by + /// whitespace, that value will be treated as the next positional argument value. It is not + /// considered to be a value for the unknown argument. + /// + /// public event EventHandler? UnknownArgument; internal const string UnreferencedCodeHelpUrl = "https://www.ookii.org/Link/CommandLineSourceGeneration"; @@ -1602,7 +1624,9 @@ private void VerifyPositionalArgumentRules() ParseResult = state.CancelParsing == CancelMode.None ? ParseResult.FromSuccess() - : ParseResult.FromSuccess(state.Argument?.ArgumentName ?? LongArgumentNamePrefix, state.RemainingArguments); + : ParseResult.FromSuccess(state.Argument?.ArgumentName ?? + (state.ArgumentName.Length == 0 ? LongArgumentNamePrefix : state.ArgumentName.ToString()), + state.RemainingArguments); // Reset to false in case it was set by a method argument that didn't cancel parsing. HelpRequested = false; @@ -1899,16 +1923,6 @@ private void HandleUnknownArgument(ref ParseState state) } } - private CommandLineArgument GetShortArgumentOrThrow(char shortName) - { - if (_argumentsByShortName!.TryGetValue(shortName, out CommandLineArgument? argument)) - { - return argument; - } - - throw StringProvider.CreateException(CommandLineArgumentErrorCategory.UnknownArgument, shortName.ToString()); - } - private PrefixInfo? CheckArgumentNamePrefix(string argument) { // Even if '-' is the argument name prefix, we consider an argument starting with dash diff --git a/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs index 3e6bc8e..14768c4 100644 --- a/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs +++ b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs @@ -2,8 +2,20 @@ namespace Ookii.CommandLine; +/// +/// Provides data for the event. +/// public class UnknownArgumentEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The argument token that contains the unknown argument. + /// The argument name. + /// The argument value. + /// + /// is . + /// public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnlyMemory value) { Token = token ?? throw new ArgumentNullException(nameof(token)); @@ -11,12 +23,71 @@ public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnl Value = value; } + /// + /// Gets the token for the unknown argument. + /// + /// + /// The raw token value. + /// + /// + /// + /// For an unknown named argument, the token includes the prefix, and the value if one was + /// present using a non-whitespace separator. For example, "-Name:Value" or "--name". + /// + /// + /// If the unknown argument was part of a combined short switch argument when using + /// , the property + /// will contain all the switch names, while the property only contains the + /// name of the unknown switch. For example, the token could be "-xyz" while the name is + /// "y". + /// + /// + /// For an unknown positional argument value, the property is equal to + /// the property. + /// + /// public string Token { get; } + /// + /// Gets the name of the unknown argument. + /// + /// + /// The argument name, or an empty span if this was an unknown positional argument value. + /// + /// + /// + /// If the unknown argument was part of a combined short switch argument when using + /// , the property + /// will contain all the switch names, while the property only contains the + /// name of the unknown switch. For example, the token could be "-xyz" while the name is + /// "y". + /// + /// public ReadOnlyMemory Name { get; } + /// + /// Gets the value of the unknown argument. + /// + /// + /// The argument value, or an empty span if this was a named argument that did not contain a + /// value using a non-whitespace separator. + /// public ReadOnlyMemory Value { get; } + /// + /// Gets or sets a value that indicates whether the unknown argument will be ignored. + /// + /// + /// to ignore the unknown argument and continue parsing with the + /// remaining arguments; for the default behavior where parsing fails. + /// The default value is + /// + /// + /// + /// This property is not used if the property is set to a value + /// other than . + /// + /// public bool Ignore { get; set; } /// @@ -39,5 +110,4 @@ public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnl /// /// public CancelMode CancelParsing { get; set; } - } From 9680835ddba361604d77da426df423042f4af769 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 15:10:26 -0800 Subject: [PATCH 40/54] Add property to identify combined switch arguments. --- .../CommandLineParserTest.cs | 4 +++ src/Ookii.CommandLine/CommandLineParser.cs | 6 ++-- .../UnknownArgumentEventArgs.cs | 30 +++++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs index 5c4a28c..3019c37 100644 --- a/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs +++ b/src/Ookii.CommandLine.Tests/CommandLineParserTest.cs @@ -1474,6 +1474,7 @@ public void TestUnknownArgument(ProviderKind kind) ReadOnlyMemory expectedName = default; ReadOnlyMemory expectedValue = default; var expectedToken = ""; + var expectedCombined = false; var ignore = false; var cancel = CancelMode.None; var eventRaised = false; @@ -1482,6 +1483,7 @@ public void TestUnknownArgument(ProviderKind kind) AssertMemoryEqual(expectedName, e.Name); AssertMemoryEqual(expectedValue, e.Value); Assert.AreEqual(expectedToken, e.Token); + Assert.AreEqual(expectedCombined, e.IsCombinedSwitchToken); e.CancelParsing = cancel; e.Ignore = ignore; eventRaised = true; @@ -1531,6 +1533,7 @@ public void TestUnknownArgument(ProviderKind kind) // One in a combined short name. eventRaised = false; expectedToken = "-szu"; + expectedCombined = true; cancel = CancelMode.None; result = CheckSuccess(parser, ["--arg1", "5", "-szu", "1"]); Assert.AreEqual(1, result.Foo); @@ -1544,6 +1547,7 @@ public void TestUnknownArgument(ProviderKind kind) expectedName = default; expectedValue = "4".AsMemory(); expectedToken = "4"; + expectedCombined = false; result = CheckSuccess(parser, ["1", "2", "3", "4", "--arg1", "5"]); Assert.AreEqual(1, result.Foo); Assert.AreEqual(2, result.Bar); diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index 0d8f8e6..373eb42 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -1879,7 +1879,7 @@ private void ParseCombinedShortArgument(ref ParseState state) if (argument == null) { state.ArgumentName = ch.ToString().AsMemory(); - HandleUnknownArgument(ref state); + HandleUnknownArgument(ref state, true); continue; } @@ -1898,10 +1898,10 @@ private void ParseCombinedShortArgument(ref ParseState state) } } - private void HandleUnknownArgument(ref ParseState state) + private void HandleUnknownArgument(ref ParseState state, bool isCombined = false) { var eventArgs = new UnknownArgumentEventArgs(state.Arguments.Span[state.Index], state.ArgumentName, - state.ArgumentValue ?? default); + state.ArgumentValue ?? default, isCombined); OnUnknownArgument(eventArgs); if (eventArgs.CancelParsing != CancelMode.None) diff --git a/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs index 14768c4..349bf7d 100644 --- a/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs +++ b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs @@ -13,14 +13,18 @@ public class UnknownArgumentEventArgs : EventArgs /// The argument token that contains the unknown argument. /// The argument name. /// The argument value. + /// + /// Indicates whether the argument is part of a combined short switch argument. + /// /// /// is . /// - public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnlyMemory value) + public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnlyMemory value, bool isCombinedSwithToken) { Token = token ?? throw new ArgumentNullException(nameof(token)); Name = name; Value = value; + IsCombinedSwitchToken = isCombinedSwithToken; } /// @@ -38,7 +42,7 @@ public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnl /// If the unknown argument was part of a combined short switch argument when using /// , the property /// will contain all the switch names, while the property only contains the - /// name of the unknown switch. For example, the token could be "-xyz" while the name is + /// name of the unknown switch. For example, the token could be "-xyz" while the name is /// "y". /// /// @@ -59,7 +63,7 @@ public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnl /// If the unknown argument was part of a combined short switch argument when using /// , the property /// will contain all the switch names, while the property only contains the - /// name of the unknown switch. For example, the token could be "-xyz" while the name is + /// name of the unknown switch. For example, the token could be "-xyz" while the name is /// "y". /// /// @@ -74,6 +78,26 @@ public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnl /// public ReadOnlyMemory Value { get; } + /// + /// Gets a value that indicates whether this argument is one among a token containing several + /// combined short name switches. + /// + /// + /// if the unknown argument is part of a combined switch argument when + /// using ; otherwise, + /// . + /// + /// + /// + /// If the unknown argument was part of a combined short switch argument when using + /// , the property + /// will contain all the switch names, while the property only contains the + /// name of the unknown switch. For example, the token could be "-xyz" while the name is + /// "y". + /// + /// + public bool IsCombinedSwitchToken { get; } + /// /// Gets or sets a value that indicates whether the unknown argument will be ignored. /// From 3500012b3aa78be89d5db26455d5da24d3df6e3c Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 15:34:12 -0800 Subject: [PATCH 41/54] Add API links. --- docs/ParsingArguments.md | 5 +++-- docs/refs.json | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ParsingArguments.md b/docs/ParsingArguments.md index 04166f9..bcb827e 100644 --- a/docs/ParsingArguments.md +++ b/docs/ParsingArguments.md @@ -107,7 +107,7 @@ and create your own error message. The generated [`Parse()`][Parse()_7] methods and the static [`Parse()`][Parse()_1] method and their overloads will likely be sufficient for most use cases. However, sometimes you may want even more fine-grained control. This includes the ability to handle the [`ArgumentParsed`][], -`UnknownArgument` and [`DuplicateArgument`][DuplicateArgument_0] events, and to get additional +[`UnknownArgument`][] and [`DuplicateArgument`][DuplicateArgument_0] events, and to get additional information about the arguments using the [`Arguments`][Arguments_0] property or the [`GetArgument`][] function. @@ -139,7 +139,7 @@ if (arguments == null) } ``` -Or, you could use this to handle the `UnknownArgument` event to collect a list of unrecognized +Or, you could use this to handle the [`UnknownArgument`][] event to collect a list of unrecognized arguments: ```csharp @@ -257,6 +257,7 @@ Next, we'll take a look at [generating usage help](UsageHelp.md). [`ParseStatus.Canceled`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseStatus.htm [`ParseStatus.Error`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseStatus.htm [`ParseStatus.Success`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_ParseStatus.htm +[`UnknownArgument`]: https://www.ookii.org/docs/commandline-4.1/html/E_Ookii_CommandLine_CommandLineParser_UnknownArgument.htm [`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm [Arguments_0]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineParser_Arguments.htm [CreateParser()_1]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm diff --git a/docs/refs.json b/docs/refs.json index 5bfc335..baac3ad 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -365,6 +365,7 @@ "TypeConverter": "#system.componentmodel.typeconverter", "TypeConverterAttribute": "#system.componentmodel.typeconverterattribute", "TypeDescriptor.GetConverter()": "#system.componentmodel.typedescriptor.getconverter", + "UnknownArgument": "E_Ookii_CommandLine_CommandLineParser_UnknownArgument", "Uri": "#system.uri", "UsageHelpRequest.SyntaxOnly": "T_Ookii_CommandLine_UsageHelpRequest", "UsageWriter": "T_Ookii_CommandLine_UsageWriter", From 119779a4ee888bc25d59da0fa0e0019ca2791d56 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 15:49:11 -0800 Subject: [PATCH 42/54] Add help for custom default value format. --- docs/ChangeLog.md | 2 +- docs/UsageHelp.md | 33 +++++++++++++++++++++++++++++++++ docs/refs.json | 2 ++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 3360714..e43d806 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -16,7 +16,7 @@ Tasks can access this token by implementing the [`IAsyncCancelableCommand`][] interface. The [`AsyncCommandBase`][] class provides support as well. - Usage help improvements: - - Support for custom default value formatting, using + - Support for [custom default value formatting](UsageHelp.md#default-values), using [`CommandLineArgumentAttribute.DefaultValueFormat`][]. - Add [`LineWrappingTextWriter.IndentAfterEmptyLine`][] and [`UsageWriter.IndentAfterEmptyLine`][] properties, which allow for proper formatting of argument descriptions with blank lines using diff --git a/docs/UsageHelp.md b/docs/UsageHelp.md index 370811f..96f0036 100644 --- a/docs/UsageHelp.md +++ b/docs/UsageHelp.md @@ -236,6 +236,34 @@ arguments. You can use the [`ParseOptions.ShowUsageOnError`][] property to customize this behavior. +### Default values + +An argument's default value will be included in the usage help if the +[`UsageWriter.IncludeDefaultValueInDescription`][] property is true and the +[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`][] property is true. Both of these are true +by default, but they can be set to false to disable including the default value either globally or +for a specific argument. + +If you use [source generation](SourceGeneration.md) with the [`GeneratedParserAttribute`][] attribute, +you can use a property initializer to set a default value which will be shown in the description. +Without source generation, only default values set using the [`CommandLineArgumentAttribute.DefaultValue`][] +property can be shown. + +You can customize how an argument's default value is formatted in the usagehelp by using the [`CommandLineArgumentAttribute.DefaultValueFormat`][] property. This property +takes a compound formatting string with a single placeholder. For example, the following argument +displays its default value in hexadecimal, as `0xff`: + +```csharp +[GeneratedParser] +partial class MyArguments +{ + [CommandLineArgument(DefaultValueFormat = "0x{0:x}")] + public int Argument { get; set; } = 0xff; +} +``` + +Without the custom format, the value would've been formatted as `255`. + ## Hidden arguments Sometimes, you may want an argument to be available, but not easily discoverable. For example, if @@ -344,6 +372,9 @@ Please see the [subcommand documentation](Subcommands.md) for information about Next, we'll take a look at [argument validation and dependencies](Validation.md). +[`CommandLineArgumentAttribute.DefaultValue`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue.htm +[`CommandLineArgumentAttribute.DefaultValueFormat`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValueFormat.htm +[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp.htm [`CommandLineArgumentAttribute.IsHidden`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden.htm [`CommandLineParser.GetUsage()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_GetUsage.htm [`CommandLineParser.Parse()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_Parse__1.htm @@ -351,6 +382,7 @@ Next, we'll take a look at [argument validation and dependencies](Validation.md) [`CommandLineParser.ParseWithErrorHandling()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_CommandLineParser_1_ParseWithErrorHandling.htm [`DescriptionAttribute`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.descriptionattribute [`DescriptionListFilterMode.Information`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_DescriptionListFilterMode.htm +[`GeneratedParserAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_GeneratedParserAttribute.htm [`GetExtendedColor()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Terminal_TextFormat_GetExtendedColor.htm [`Int32`]: https://learn.microsoft.com/dotnet/api/system.int32 [`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm @@ -366,6 +398,7 @@ Next, we'll take a look at [argument validation and dependencies](Validation.md) [`UsageWriter.ArgumentDescriptionListFilter`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListFilter.htm [`UsageWriter.ArgumentDescriptionListOrder`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListOrder.htm [`UsageWriter.IncludeApplicationDescription`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescription.htm +[`UsageWriter.IncludeDefaultValueInDescription`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeDefaultValueInDescription.htm [`UsageWriter.UseAbbreviatedSyntax`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseAbbreviatedSyntax.htm [`UsageWriter.UseColor`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseColor.htm [`UsageWriter.UseShortNamesForSyntax`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseShortNamesForSyntax.htm diff --git a/docs/refs.json b/docs/refs.json index baac3ad..57f3bad 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -62,6 +62,7 @@ "CommandLineArgumentAttribute.CancelParsing": "P_Ookii_CommandLine_CommandLineArgumentAttribute_CancelParsing", "CommandLineArgumentAttribute.DefaultValue": "P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValue", "CommandLineArgumentAttribute.DefaultValueFormat": "P_Ookii_CommandLine_CommandLineArgumentAttribute_DefaultValueFormat", + "CommandLineArgumentAttribute.IncludeDefaultInUsageHelp": "P_Ookii_CommandLine_CommandLineArgumentAttribute_IncludeDefaultInUsageHelp", "CommandLineArgumentAttribute.IsHidden": "P_Ookii_CommandLine_CommandLineArgumentAttribute_IsHidden", "CommandLineArgumentAttribute.IsLong": "P_Ookii_CommandLine_CommandLineArgumentAttribute_IsLong", "CommandLineArgumentAttribute.IsPositional": "P_Ookii_CommandLine_CommandLineArgumentAttribute_IsPositional", @@ -373,6 +374,7 @@ "UsageWriter.ArgumentDescriptionListOrder": "P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListOrder", "UsageWriter.IncludeApplicationDescription": "P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescription", "UsageWriter.IncludeCommandHelpInstruction": "P_Ookii_CommandLine_UsageWriter_IncludeCommandHelpInstruction", + "UsageWriter.IncludeDefaultValueInDescription": "P_Ookii_CommandLine_UsageWriter_IncludeDefaultValueInDescription", "UsageWriter.IncludeValidatorsInDescription": "P_Ookii_CommandLine_UsageWriter_IncludeValidatorsInDescription", "UsageWriter.IndentAfterEmptyLine": "P_Ookii_CommandLine_UsageWriter_IndentAfterEmptyLine", "UsageWriter.UseAbbreviatedSyntax": "P_Ookii_CommandLine_UsageWriter_UseAbbreviatedSyntax", From b17c0da96eacf5bf8144a19b779a492d6a0347d1 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 15:57:47 -0800 Subject: [PATCH 43/54] Add help for IndentAfterEmptyLine. --- docs/ChangeLog.md | 4 ++-- docs/UsageHelp.md | 52 ++++++++++++++++++++++++++++++++++++++--------- docs/Utilities.md | 10 +++++---- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index e43d806..fd234af 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -19,8 +19,8 @@ - Support for [custom default value formatting](UsageHelp.md#default-values), using [`CommandLineArgumentAttribute.DefaultValueFormat`][]. - Add [`LineWrappingTextWriter.IndentAfterEmptyLine`][] and [`UsageWriter.IndentAfterEmptyLine`][] - properties, which allow for proper formatting of argument descriptions with blank lines using - the default usage help format. + properties, which allow for proper formatting of [argument descriptions with blank lines](UsageHelp.md#descriptions-with-blank-lines) + using the default usage help format. - Add support for easily adding a footer to the usage help. - Some localizable text that could previously only be customized by deriving from the [`UsageWriter`][] class can now also be customized with the [`LocalizedStringProvider`][] class, diff --git a/docs/UsageHelp.md b/docs/UsageHelp.md index 96f0036..715a15d 100644 --- a/docs/UsageHelp.md +++ b/docs/UsageHelp.md @@ -240,18 +240,19 @@ You can use the [`ParseOptions.ShowUsageOnError`][] property to customize this b An argument's default value will be included in the usage help if the [`UsageWriter.IncludeDefaultValueInDescription`][] property is true and the -[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`][] property is true. Both of these are true -by default, but they can be set to false to disable including the default value either globally or -for a specific argument. +[`CommandLineArgumentAttribute.IncludeDefaultInUsageHelp`][] property is true. Both of these are +true by default, but they can be set to false to disable including the default value either globally +or for a specific argument. -If you use [source generation](SourceGeneration.md) with the [`GeneratedParserAttribute`][] attribute, -you can use a property initializer to set a default value which will be shown in the description. -Without source generation, only default values set using the [`CommandLineArgumentAttribute.DefaultValue`][] -property can be shown. +If you use [source generation](SourceGeneration.md) with the [`GeneratedParserAttribute`][] +attribute, you can use a property initializer to set a default value which will be shown in the +description. Without source generation, only default values set using the +[`CommandLineArgumentAttribute.DefaultValue`][] property can be shown. -You can customize how an argument's default value is formatted in the usagehelp by using the [`CommandLineArgumentAttribute.DefaultValueFormat`][] property. This property -takes a compound formatting string with a single placeholder. For example, the following argument -displays its default value in hexadecimal, as `0xff`: +You can customize how an argument's default value is formatted in the usage help by using the +[`CommandLineArgumentAttribute.DefaultValueFormat`][] property. This property takes a compound +formatting string with a single placeholder. For example, the following argument displays its +default value in hexadecimal, as `0xff`: ```csharp [GeneratedParser] @@ -264,6 +265,36 @@ partial class MyArguments Without the custom format, the value would've been formatted as `255`. +### Descriptions with blank lines + +Sometimes, you may want to make your argument description more legible by including blank lines. +Unfortunately, this does not work well with the default usage help format, because the descriptions +are indented, but indentation is reset after a blank line. This means your usage help can end up +looking like this: + +```text + -SomeArgument + First line of the description. + +And another line, after a blank line. + + -OtherArgument + Other description. +``` + +To avoid this, set the [`UsageWriter.IndentAfterEmptyLine`][] property to true. Now, the same usage +help will look like this: + +```text + -SomeArgument + First line of the description. + + And another line, after a blank line. + + -OtherArgument + Other description. +``` + ## Hidden arguments Sometimes, you may want an argument to be available, but not easily discoverable. For example, if @@ -399,6 +430,7 @@ Next, we'll take a look at [argument validation and dependencies](Validation.md) [`UsageWriter.ArgumentDescriptionListOrder`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_ArgumentDescriptionListOrder.htm [`UsageWriter.IncludeApplicationDescription`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeApplicationDescription.htm [`UsageWriter.IncludeDefaultValueInDescription`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IncludeDefaultValueInDescription.htm +[`UsageWriter.IndentAfterEmptyLine`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IndentAfterEmptyLine.htm [`UsageWriter.UseAbbreviatedSyntax`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseAbbreviatedSyntax.htm [`UsageWriter.UseColor`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseColor.htm [`UsageWriter.UseShortNamesForSyntax`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_UseShortNamesForSyntax.htm diff --git a/docs/Utilities.md b/docs/Utilities.md index 8e06e53..3ddf546 100644 --- a/docs/Utilities.md +++ b/docs/Utilities.md @@ -42,9 +42,10 @@ The [`LineWrappingTextWriter`][] class uses hanging indents, also called negativ lines except the first one are indented. The indentation level can be set using the [`LineWrappingTextWriter.Indent`][] property, which indicates the number of spaces to indent by. -When this property is set, it will apply to the next line that needs to be indented. The first line -of text, and any line after a blank line, is not indented. Indentation is applied both to lines that -were wrapped, and lines created by explicit new lines in the text. +When this property is set, it will apply to the next line that needs to be indented. Indentation is +applied both to lines that were wrapped, and lines created by explicit new lines in the text. The +first line of text is not indented. Lines after a blank line are not indented either, unless you set +the [`LineWrappingTextWriter.IndentAfterEmptyLine`][] property to true. You can change the [`Indent`][] property at any time to change the size of the indentation to use. @@ -169,7 +170,9 @@ public int Run() [`LineWrappingTextWriter.ForConsoleError()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleError.htm [`LineWrappingTextWriter.ForConsoleOut()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ForConsoleOut.htm [`LineWrappingTextWriter.Indent`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Indent.htm +[`LineWrappingTextWriter.IndentAfterEmptyLine`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_IndentAfterEmptyLine.htm [`LineWrappingTextWriter.ResetIndent()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndent.htm +[`LineWrappingTextWriter.Wrapping`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping.htm [`LineWrappingTextWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_LineWrappingTextWriter.htm [`Ookii.CommandLine.Terminal`]: https://www.ookii.org/docs/commandline-4.1/html/N_Ookii_CommandLine_Terminal.htm [`ResetIndent()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_LineWrappingTextWriter_ResetIndent.htm @@ -177,6 +180,5 @@ public int Run() [`TextWriter`]: https://learn.microsoft.com/dotnet/api/system.io.textwriter [`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm [`VirtualTerminal`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_VirtualTerminal.htm -[`LineWrappingTextWriter.Wrapping`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping.htm [`WrappingMode.Disabled`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_WrappingMode.htm [`WrappingMode.EnabledNoForce`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_WrappingMode.htm From fa0ef5971872b67c58d190abc7fb1cff841b60ae Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 16:03:47 -0800 Subject: [PATCH 44/54] Add help for usage footers. --- docs/ChangeLog.md | 4 +++- docs/UsageHelp.md | 23 +++++++++++++++++++++-- docs/refs.json | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index fd234af..4e77244 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -21,7 +21,8 @@ - Add [`LineWrappingTextWriter.IndentAfterEmptyLine`][] and [`UsageWriter.IndentAfterEmptyLine`][] properties, which allow for proper formatting of [argument descriptions with blank lines](UsageHelp.md#descriptions-with-blank-lines) using the default usage help format. - - Add support for easily adding a footer to the usage help. + - [Add a footer](UsageHelp.md#usage-help-footer) to the usage help with the + [`UsageFooterAttribute`][] attribute. - Some localizable text that could previously only be customized by deriving from the [`UsageWriter`][] class can now also be customized with the [`LocalizedStringProvider`][] class, so you only need to derive from [`LocalizedStringProvider`][] to customize all user-facing @@ -290,6 +291,7 @@ may require substantial code changes and may change how command lines are parsed [`StandardStreamExtensions`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_StandardStreamExtensions.htm [`StringWriter`]: https://learn.microsoft.com/dotnet/api/system.io.stringwriter [`TypeConverter`]: https://learn.microsoft.com/dotnet/api/system.componentmodel.typeconverter +[`UsageFooterAttribute`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageFooterAttribute.htm [`UsageWriter.IndentAfterEmptyLine`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_UsageWriter_IndentAfterEmptyLine.htm [`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm [`ValidateEnumValueAttribute.IncludeValuesInErrorMessage`]: https://www.ookii.org/docs/commandline-4.1/html/P_Ookii_CommandLine_Validation_ValidateEnumValueAttribute_IncludeValuesInErrorMessage.htm diff --git a/docs/UsageHelp.md b/docs/UsageHelp.md index 715a15d..bdac04e 100644 --- a/docs/UsageHelp.md +++ b/docs/UsageHelp.md @@ -64,8 +64,8 @@ Usage: Parser [-Source] [-Destination] [[-OperationIndex] Date: Tue, 19 Dec 2023 16:11:28 -0800 Subject: [PATCH 45/54] Add help for VT helper methods. --- docs/ChangeLog.md | 4 ++-- docs/Utilities.md | 14 ++++++++++++++ docs/refs.json | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 4e77244..6909850 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -27,8 +27,8 @@ [`UsageWriter`][] class can now also be customized with the [`LocalizedStringProvider`][] class, so you only need to derive from [`LocalizedStringProvider`][] to customize all user-facing strings. -- Provide helper methods in the [`VirtualTerminal`][] class for writing text with VT formatting to - the standard output or error streams. +- Provide [helper methods](Utilities.md#virtual-terminal-support) in the [`VirtualTerminal`][] class + for writing text with VT formatting to the standard output or error streams. - Provide extension methods for [`StandardStream`][] in the [`StandardStreamExtensions`][] class. - Emit a warning if a class isn't using the [`GeneratedParserAttribute`][] when it could, with an automatic code fix to easily apply it. diff --git a/docs/Utilities.md b/docs/Utilities.md index 3ddf546..0bcbd38 100644 --- a/docs/Utilities.md +++ b/docs/Utilities.md @@ -129,6 +129,18 @@ and they return a disposable type that will revert the console mode when dispose collected. On other platforms, it only checks for support and disposing the returned instance does nothing. +To simplify writing messages to the console that use a single format for the whole message, two +helper methods are provided: [`VirtualTerminal.WriteLineFormatted()`][] and +[`VirtualTerminal.WriteLineErrorFormatted()`][]. These methods call [`EnableColor()`][], write the message +to either the standard output or standard error stream respectively, using the specified formatting, +and then reset the format to the default. + +The below example is identical to the one above: + +```csharp +VirtualTerminal.WriteLineFormatted("This text is green and underlined.", TextFormat.ForegroundGreen + TextFormat.Underline); +``` + In the [tutorial](Tutorial.md), we created an application with an `--inverted` argument, that actually just set the console to use a white background and a black foreground, instead of truly inverting the console colors. With virtual terminal support, we can update the `read` command to use @@ -179,6 +191,8 @@ public int Run() [`TextFormat`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_TextFormat.htm [`TextWriter`]: https://learn.microsoft.com/dotnet/api/system.io.textwriter [`UsageWriter`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_UsageWriter.htm +[`VirtualTerminal.WriteLineErrorFormatted()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Terminal_VirtualTerminal_WriteLineErrorFormatted.htm +[`VirtualTerminal.WriteLineFormatted()`]: https://www.ookii.org/docs/commandline-4.1/html/M_Ookii_CommandLine_Terminal_VirtualTerminal_WriteLineFormatted.htm [`VirtualTerminal`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_Terminal_VirtualTerminal.htm [`WrappingMode.Disabled`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_WrappingMode.htm [`WrappingMode.EnabledNoForce`]: https://www.ookii.org/docs/commandline-4.1/html/T_Ookii_CommandLine_WrappingMode.htm diff --git a/docs/refs.json b/docs/refs.json index 816432b..a267ef1 100644 --- a/docs/refs.json +++ b/docs/refs.json @@ -402,6 +402,8 @@ "ValueConverterAttribute": "T_Ookii_CommandLine_Conversion_ValueConverterAttribute", "ValueDescriptionAttribute": "T_Ookii_CommandLine_ValueDescriptionAttribute", "VirtualTerminal": "T_Ookii_CommandLine_Terminal_VirtualTerminal", + "VirtualTerminal.WriteLineErrorFormatted()": "M_Ookii_CommandLine_Terminal_VirtualTerminal_WriteLineErrorFormatted", + "VirtualTerminal.WriteLineFormatted()": "M_Ookii_CommandLine_Terminal_VirtualTerminal_WriteLineFormatted", "Wrapping": "P_Ookii_CommandLine_LineWrappingTextWriter_Wrapping", "WrappingMode.Disabled": "T_Ookii_CommandLine_WrappingMode", "WrappingMode.EnabledNoForce": "T_Ookii_CommandLine_WrappingMode", From ce2c5f95ee15c6db24954eb4c48cd9f817e2bb9c Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 16:19:11 -0800 Subject: [PATCH 46/54] Fix some documentation issues. --- docs/DefiningArguments.md | 23 +++++++++++------------ docs/UsageHelp.md | 2 +- src/Samples/TopLevelArguments/README.md | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/DefiningArguments.md b/docs/DefiningArguments.md index 44c85d1..4079de4 100644 --- a/docs/DefiningArguments.md +++ b/docs/DefiningArguments.md @@ -475,13 +475,21 @@ partial class Arguments ### Long/short mode -To enable [long/short mode](Arguments.md#longshort-mode), you typically want to set three options +To enable [long/short mode](Arguments.md#longshort-mode), you typically want to set several options if you want to mimic typical POSIX conventions: the mode itself, case sensitive argument names, and dash-case [name transformation](#name-transformation). This can be done with either the [`ParseOptionsAttribute`][] attribute or the [`ParseOptions`][] class. -A convenient [`IsPosix`][IsPosix_2] property is provided on either class, that sets all relevant options when -set to true. +A convenient [`IsPosix`][IsPosix_2] property is provided on either class, that sets all relevant +options when set to true. Using `[ParseOptions(IsPosix = true)]` is equivalent to manually setting +the following properties. + +```csharp +[ParseOptions(Mode = ParsingMode.LongShort, + CaseSensitive = true, + ArgumentNameTransform = NameTransform.DashCase, + ValueDescriptionNameTransform = NameTransform.DashCase)] +``` When using long/short mode, the name derived from the member name, or the explicit name set by the [`CommandLineArgumentAttribute`][] attribute is the long name. @@ -509,15 +517,6 @@ partial class MyArguments } ``` -Using `[ParseOptions(IsPosix = true)]` is equivalent to manually setting the following properties. - -```csharp -[ParseOptions(Mode = ParsingMode.LongShort, - CaseSensitive = true, - ArgumentNameTransform = NameTransform.DashCase, - ValueDescriptionNameTransform = NameTransform.DashCase)] -``` - In this example, the `FileName` property defines a required positional argument with the long name `--file-name` and the short name `-f`. The `Foo` property defines an argument with the long name `--foo` and the explicit short name `-F`, which is distinct from `-f` because case sensitivity is diff --git a/docs/UsageHelp.md b/docs/UsageHelp.md index bdac04e..4bcd6b7 100644 --- a/docs/UsageHelp.md +++ b/docs/UsageHelp.md @@ -102,7 +102,7 @@ multiple values. The order of the arguments in the usage syntax is as follows: 1. The positional arguments, in their defined order. -2. Required positional arguments, in alphabetical order. +2. Required non-positional arguments, in alphabetical order. 3. The remaining arguments, in alphabetical order. The syntax for a single argument has the following default format: diff --git a/src/Samples/TopLevelArguments/README.md b/src/Samples/TopLevelArguments/README.md index 4327ceb..26cf2ca 100644 --- a/src/Samples/TopLevelArguments/README.md +++ b/src/Samples/TopLevelArguments/README.md @@ -5,7 +5,7 @@ than using a base class with the common arguments, which makes the common argume command (as shown in the [nested commands sample](../NestedCommands)), this sample defines several top-level arguments that are not part of any command. -The commands themselves are based on the regular [subcommand sample](../SubCommand), so see that for +The commands themselves are based on the regular [subcommand sample](../Subcommand), so see that for more detailed descriptions. This sample uses POSIX conventions, for variation, but this isn't required. From 2b375d590882860a0b83605718faa62554e4d8f3 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 16:21:38 -0800 Subject: [PATCH 47/54] Update readme. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5228ab4..9a71c3d 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,9 @@ when [source generation](docs/SourceGeneration.md) is used. The .Net 7.0 version has additional support for `required` properties, and can utilize `ISpanParsable` and `IParsable` for argument value conversions. +An assembly built for .Net 8.0 is also provided; this has no additional functionality over the +.Net 7.0 version, but is provided to ensure optimal compatibility and performance. + ## Building and testing To build Ookii.CommandLine, make sure you have the following installed: From 1a194addeef6214e9f9c7aac3131d1b386cfc13a Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 19 Dec 2023 16:40:53 -0800 Subject: [PATCH 48/54] Set preview2 suffix. --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index f3d8146..5935568 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,6 @@ Ookii.org Copyright (c) Sven Groot (Ookii.org) 4.1.0 - preview + preview2 \ No newline at end of file From a1c293c9836154dae5ad8ff1986a0bc0f7956d1c Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 9 Jan 2024 17:26:51 -0800 Subject: [PATCH 49/54] Fix XML comments for OpenStream. --- .../Terminal/StandardStreamExtensions.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs index 0cb7fc4..7a9b8c5 100644 --- a/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs +++ b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs @@ -24,7 +24,8 @@ public static class StandardStreamExtensions /// /// /// - /// The returned should not be disposed by the caller. + /// The returned instance should not be disposed by the + /// caller. /// /// public static TextWriter GetWriter(this StandardStream stream) @@ -38,18 +39,22 @@ public static TextWriter GetWriter(this StandardStream stream) } /// - /// Gets the for either - /// or . + /// Creates a for a . /// /// A value. /// - /// The value of either or - /// . + /// The return value of either , + /// , or + /// . /// /// - /// was a value other than - /// or . + /// was not a valid value. /// + /// + /// + /// The returned instance should be disposed by the caller. + /// + /// public static Stream OpenStream(this StandardStream stream) { return stream switch From bfd539b0eafdc681a8e62aac50ea91c8376aa1d0 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Tue, 9 Jan 2024 17:51:35 -0800 Subject: [PATCH 50/54] Restrict dependabot versions for Microsoft.CodeAnalysis.Workspaces. --- .github/dependabot.yml | 4 +++- src/Directory.Build.props | 2 +- .../Ookii.CommandLine.Generator.csproj | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 32c9f61..ff7eb12 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,9 +11,11 @@ updates: patterns: - "*" ignore: - # 4.3 is the latest version that can work with the .Net 6.0 SDK. + # 4.3.x is the latest version that can work with the .Net 6.0 SDK for both of these. - dependency-name: "Microsoft.CodeAnalysis.CSharp" update-types: ["version-update:semver-major", "version-update:semver-minor"] + - dependency-name: "Microsoft.CodeAnalysis.Workspaces" + update-types: ["version-update:semver-major", "version-update:semver-minor"] - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5935568..3f9482a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,6 @@ Ookii.org Copyright (c) Sven Groot (Ookii.org) 4.1.0 - preview2 + preview3 \ No newline at end of file diff --git a/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj b/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj index 22e639d..e096e17 100644 --- a/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj +++ b/src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj @@ -17,7 +17,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 58f2ded715e25e5003fbc1e568f4a41b4ac5cca7 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Thu, 18 Jan 2024 17:59:34 -0800 Subject: [PATCH 51/54] Proofread markdown documentation. --- docs/Arguments.md | 6 +++--- docs/DefiningArguments.md | 8 ++++---- docs/SourceGeneration.md | 9 +++++++++ docs/Tutorial.md | 6 +++--- docs/Utilities.md | 6 +++--- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/Arguments.md b/docs/Arguments.md index 710109e..2a30369 100644 --- a/docs/Arguments.md +++ b/docs/Arguments.md @@ -147,9 +147,9 @@ In this example, the second positional argument would be set to the value "--val an argument named "value2", it would not be set. This behavior is disabled by default, but can be enabled using the -[`ParseOptionsAttribute.PrefixTermination`][] or [`ParseOptions.PrefixTermination`][] property. It can be -used with both the default parsing mode and long/short mode. Alternatively, you can also have the -`--` argument [cancel parsing](DefiningArguments.md#arguments-that-cancel-parsing). +[`ParseOptionsAttribute.PrefixTermination`][] or [`ParseOptions.PrefixTermination`][] property. It +can be used with both the default parsing mode and long/short mode. Alternatively, you can also set +it so that the `--` argument will [cancel parsing](DefiningArguments.md#arguments-that-cancel-parsing). ## Required arguments diff --git a/docs/DefiningArguments.md b/docs/DefiningArguments.md index 4079de4..f1d8aa5 100644 --- a/docs/DefiningArguments.md +++ b/docs/DefiningArguments.md @@ -475,10 +475,10 @@ partial class Arguments ### Long/short mode -To enable [long/short mode](Arguments.md#longshort-mode), you typically want to set several options -if you want to mimic typical POSIX conventions: the mode itself, case sensitive argument names, -and dash-case [name transformation](#name-transformation). This can be done with either the -[`ParseOptionsAttribute`][] attribute or the [`ParseOptions`][] class. +When enabling [long/short mode](Arguments.md#longshort-mode), you may want to also set several +related options if you want to mimic typical POSIX conventions: long/short mode itself, case +sensitive argument names, and dash-case [name transformation](#name-transformation). This can be +done with either the [`ParseOptionsAttribute`][] attribute or the [`ParseOptions`][] class. A convenient [`IsPosix`][IsPosix_2] property is provided on either class, that sets all relevant options when set to true. Using `[ParseOptions(IsPosix = true)]` is equivalent to manually setting diff --git a/docs/SourceGeneration.md b/docs/SourceGeneration.md index 2aa2cd0..55a8c66 100644 --- a/docs/SourceGeneration.md +++ b/docs/SourceGeneration.md @@ -41,6 +41,15 @@ A few restrictions apply to projects that use Ookii.CommandLine's source generat Generally, it's recommended to use source generation unless you cannot meet these requirements. +To encourage the use of source generation, Ookii.CommandLine also includes an analyzer that will +emit [a warning](SourceGenerationDiagnostics.md#ocl0040) if a class is found that contains any +public property or method with the [`CommandLineArgumentAttribute`][] attribute and meets the +requirements listed above, but does not have the [`GeneratedParserAttribute`][]. A code fix, +accessible with lightbulb UI in Visual Studio, that applies the attribute and makes the class +partial if necessary, is available in this case. + +If you don't want to or cannot use source generation, you can simply disable this warning. + ## Generating a parser To use source generation to determine the command line arguments defined by a class, apply the diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 40986a3..d6effd5 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -12,7 +12,7 @@ Create a directory called "tutorial" for the project, and run the following comm directory: ```bash -dotnet new console --framework net7.0 +dotnet new console --framework net8.0 ``` Next, we will add a reference to Ookii.CommandLine's NuGet package: @@ -96,7 +96,7 @@ But wait, we didn't pass any arguments to this method? Actually, the method will explicit `string[]` array with the arguments, if you want to pass them manually. So, let's run our application. Build the application using `dotnet build`, and then, from the -`bin/Debug/net7.0` directory, run the following: +`bin/Debug/net8.0` directory, run the following: ```bash ./tutorial ../../../tutorial.csproj @@ -109,7 +109,7 @@ Which will give print the contents of the tutorial.csproj file: Exe - net7.0 + net8.0 enable enable diff --git a/docs/Utilities.md b/docs/Utilities.md index 0bcbd38..665c2ca 100644 --- a/docs/Utilities.md +++ b/docs/Utilities.md @@ -131,9 +131,9 @@ nothing. To simplify writing messages to the console that use a single format for the whole message, two helper methods are provided: [`VirtualTerminal.WriteLineFormatted()`][] and -[`VirtualTerminal.WriteLineErrorFormatted()`][]. These methods call [`EnableColor()`][], write the message -to either the standard output or standard error stream respectively, using the specified formatting, -and then reset the format to the default. +[`VirtualTerminal.WriteLineErrorFormatted()`][]. These methods call [`EnableColor()`][], write the +message to either the standard output or standard error stream respectively, using the specified +formatting, and then reset the format to the default. The below example is identical to the one above: From 823ae81a6c347ca164f22ac81163c4e0eb8a0251 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 19 Jan 2024 18:09:35 -0800 Subject: [PATCH 52/54] Proofread XML comments. --- src/Ookii.CommandLine/CommandLineParser.cs | 4 +- .../Commands/AsyncCommandBase.cs | 2 - .../Commands/CommandManager.cs | 10 ++-- .../Commands/IAsyncCommand.cs | 2 +- .../Conversion/EnumConverter.cs | 60 +++++++++---------- .../LineWrappingTextWriter.cs | 4 +- .../LocalizedStringProvider.Usage.cs | 8 +-- .../ParseOptionsAttribute.cs | 4 +- .../PrefixTerminationMode.cs | 7 ++- .../Terminal/StandardStreamExtensions.cs | 18 +++--- .../Terminal/VirtualTerminal.cs | 6 +- .../UnknownArgumentEventArgs.cs | 5 +- src/Ookii.CommandLine/UsageFooterAttribute.cs | 1 + src/Ookii.CommandLine/UsageWriter.cs | 6 +- .../Validation/ValidateEnumValueAttribute.cs | 8 +-- 15 files changed, 74 insertions(+), 71 deletions(-) diff --git a/src/Ookii.CommandLine/CommandLineParser.cs b/src/Ookii.CommandLine/CommandLineParser.cs index 373eb42..40fea57 100644 --- a/src/Ookii.CommandLine/CommandLineParser.cs +++ b/src/Ookii.CommandLine/CommandLineParser.cs @@ -557,7 +557,7 @@ public CommandLineParser(ArgumentProvider provider, ParseOptions? options = null /// /// /// - /// If not empty, this description will be added at the top of the usage help returned by the + /// If not empty, this description will be added at the top of the usage help created by the /// method. This description can be set by applying the /// attribute to the command line arguments class. /// @@ -572,7 +572,7 @@ public CommandLineParser(ArgumentProvider provider, ParseOptions? options = null /// /// /// - /// If not empty, this footer will be added at the bottom of the usage help returned by the + /// If not empty, this footer will be added at the bottom of the usage help created by the /// method. This footer can be set by applying the /// attribute to the command line arguments class. /// diff --git a/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs b/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs index b51ff5e..d40e39e 100644 --- a/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs +++ b/src/Ookii.CommandLine/Commands/AsyncCommandBase.cs @@ -27,6 +27,4 @@ public abstract class AsyncCommandBase : IAsyncCancelableCommand /// public abstract Task RunAsync(); - - } diff --git a/src/Ookii.CommandLine/Commands/CommandManager.cs b/src/Ookii.CommandLine/Commands/CommandManager.cs index 006ce32..c745995 100644 --- a/src/Ookii.CommandLine/Commands/CommandManager.cs +++ b/src/Ookii.CommandLine/Commands/CommandManager.cs @@ -37,8 +37,8 @@ namespace Ookii.CommandLine.Commands; /// interface. /// /// -/// Subcommands can be asynchronous by implementing the or -/// interface. +/// Subcommands can support asynchronous execution by implementing the +/// or interface. /// /// /// Commands can be defined in a single assembly, or in multiple assemblies. @@ -1083,10 +1083,10 @@ private IEnumerable GetCommandsUnsortedAndFiltered() private static async Task RunCommandAsync(ICommand? command, CancellationToken cancellationToken) { - if (command is IAsyncCancelableCommand asyncCancelableCommand) - { + if (command is IAsyncCancelableCommand asyncCancelableCommand) + { asyncCancelableCommand.CancellationToken = cancellationToken; - return await asyncCancelableCommand.RunAsync(); + return await asyncCancelableCommand.RunAsync(); } else if (command is IAsyncCommand asyncCommand) { diff --git a/src/Ookii.CommandLine/Commands/IAsyncCommand.cs b/src/Ookii.CommandLine/Commands/IAsyncCommand.cs index d5a39e2..c9d991f 100644 --- a/src/Ookii.CommandLine/Commands/IAsyncCommand.cs +++ b/src/Ookii.CommandLine/Commands/IAsyncCommand.cs @@ -18,7 +18,7 @@ namespace Ookii.CommandLine.Commands; /// /// If you want to use the cancellation token passed to the /// -/// method, you should instead implement the interface or +/// method, you should instead implement the interface, or /// derive from the class. /// /// diff --git a/src/Ookii.CommandLine/Conversion/EnumConverter.cs b/src/Ookii.CommandLine/Conversion/EnumConverter.cs index bf30be4..f1700de 100644 --- a/src/Ookii.CommandLine/Conversion/EnumConverter.cs +++ b/src/Ookii.CommandLine/Conversion/EnumConverter.cs @@ -12,13 +12,13 @@ namespace Ookii.CommandLine.Conversion; /// /// This converter performs a case insensitive conversion, and accepts the name of an enumeration /// value or a number representing the underlying type of the enumeration. Comma-separated values -/// that will be combined using bitwise-or are also excepted, regardless of whether the +/// that will be combined using bitwise-or are also accepted, regardless of whether the /// enumeration uses the attribute. When using a numeric value, the /// value does not need to be one of the defined values of the enumeration. /// /// /// Use the attribute to alter these behaviors. Applying -/// that attribute will ensure that only defined values are allowed. The +/// that attribute will ensure that only values defined by the enumeration are allowed. The /// /// property can be used to control the use of multiple values, and the /// property @@ -27,11 +27,11 @@ namespace Ookii.CommandLine.Conversion; /// to enable case sensitive conversion. /// /// -/// If conversion fails, the error message will check the +/// If conversion fails, the converter will check the /// /// property to see whether or not the enumeration's defined values should be listed in the /// error message. If the argument does not have the -/// attribute applied, the values will be listed. +/// attribute applied, the enumeration's values will be listed in the message. /// /// /// @@ -85,32 +85,32 @@ public EnumConverter(Type enumType) /// /// The value was not valid for the enumeration type. /// - public override object? Convert(string value, CultureInfo culture, CommandLineArgument argument) -#if NET6_0_OR_GREATER - => Convert(value.AsSpan(), culture, argument); - - /// - /// Converts a string span to the enumeration type. - /// - /// The containing the string to convert. - /// The culture to use for the conversion. - /// - /// The that will use the converted value. - /// - /// An object representing the converted value. - /// - /// - /// This method performs the conversion using the - /// method. - /// - /// - /// - /// The value was not valid for the enumeration type. - /// - public override object? Convert(ReadOnlySpan value, CultureInfo culture, CommandLineArgument argument) -#endif - { - var attribute = argument.Validators.OfType().FirstOrDefault(); + public override object? Convert(string value, CultureInfo culture, CommandLineArgument argument) +#if NET6_0_OR_GREATER + => Convert(value.AsSpan(), culture, argument); + + /// + /// Converts a string span to the enumeration type. + /// + /// The containing the string to convert. + /// The culture to use for the conversion. + /// + /// The that will use the converted value. + /// + /// An object representing the converted value. + /// + /// + /// This method performs the conversion using the + /// method. + /// + /// + /// + /// The value was not valid for the enumeration type. + /// + public override object? Convert(ReadOnlySpan value, CultureInfo culture, CommandLineArgument argument) +#endif + { + var attribute = argument.Validators.OfType().FirstOrDefault(); #if NET6_0_OR_GREATER if (attribute != null && !attribute.ValidateBeforeConversion(argument, value)) #else diff --git a/src/Ookii.CommandLine/LineWrappingTextWriter.cs b/src/Ookii.CommandLine/LineWrappingTextWriter.cs index ec00678..7ea6fce 100644 --- a/src/Ookii.CommandLine/LineWrappingTextWriter.cs +++ b/src/Ookii.CommandLine/LineWrappingTextWriter.cs @@ -178,7 +178,7 @@ public void Append(ReadOnlySpan span, StringSegmentType type) public void Peek(TextWriter writer) { - if (Indentation is int indentation) + if (Indentation is int indentation) { WriteIndent(writer, indentation); } @@ -444,7 +444,7 @@ public int Indent /// indentation. /// /// - /// if a line after am empty line should be indented; otherwise, + /// if a line after an empty line should be indented; otherwise, /// . The default value is . /// /// diff --git a/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs b/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs index 82ea9da..d4e5b32 100644 --- a/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs +++ b/src/Ookii.CommandLine/LocalizedStringProvider.Usage.cs @@ -10,20 +10,20 @@ namespace Ookii.CommandLine; partial class LocalizedStringProvider { /// - /// Gets the default prefix for usage syntax, used by the class. + /// Gets the default prefix for the usage syntax, used by the class. /// /// The string. public virtual string UsageSyntaxPrefix() => Resources.DefaultUsagePrefix; /// - /// Gets the default suffix for usage syntax when creating command list usage help, used by the - /// class. + /// Gets the default suffix for the usage syntax when creating command list usage help, used by + /// the class. /// /// The string. public virtual string CommandUsageSuffix() => Resources.DefaultCommandUsageSuffix; /// - /// Gets the default suffix for usage syntax to indicate more arguments are available if the + /// Gets the default suffix for the usage syntax to indicate more arguments are available if the /// syntax is abbreviated, used by the class. /// /// The string. diff --git a/src/Ookii.CommandLine/ParseOptionsAttribute.cs b/src/Ookii.CommandLine/ParseOptionsAttribute.cs index 3346265..bcf7ab2 100644 --- a/src/Ookii.CommandLine/ParseOptionsAttribute.cs +++ b/src/Ookii.CommandLine/ParseOptionsAttribute.cs @@ -155,8 +155,8 @@ public virtual bool IsPosix /// /// /// - /// This property is only used if the property or the - /// is + /// This property is only used if the or + /// property is /// , or if the /// or property is not /// . diff --git a/src/Ookii.CommandLine/PrefixTerminationMode.cs b/src/Ookii.CommandLine/PrefixTerminationMode.cs index 596299e..b4029cf 100644 --- a/src/Ookii.CommandLine/PrefixTerminationMode.cs +++ b/src/Ookii.CommandLine/PrefixTerminationMode.cs @@ -9,12 +9,13 @@ public enum PrefixTerminationMode { /// - /// This argument has no special meaning. + /// There is no special behavior for the argument. /// None, /// - /// The argument terminates the used of named arguments. Any following arguments are interpreted - /// as values for positional arguments, even if they begin with an argument name separator. + /// The argument terminates the use of named arguments. Any following arguments are interpreted + /// as values for positional arguments, even if they begin with a long or short argument name + /// prefix. /// PositionalOnly, /// diff --git a/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs index 7a9b8c5..9d76ae2 100644 --- a/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs +++ b/src/Ookii.CommandLine/Terminal/StandardStreamExtensions.cs @@ -39,7 +39,7 @@ public static TextWriter GetWriter(this StandardStream stream) } /// - /// Creates a for a . + /// Creates a instance for a . /// /// A value. /// @@ -67,8 +67,8 @@ public static Stream OpenStream(this StandardStream stream) } /// - /// Gets the associated with a if that - /// writer is for either the standard output or error stream. + /// Gets the associated with a instance, + /// if that instance is for either the standard output or error stream. /// /// The . /// @@ -82,8 +82,8 @@ public static Stream OpenStream(this StandardStream stream) /// /// /// If is an instance of the - /// class, the will be - /// checked. + /// class, the value of the + /// property will be checked. /// /// public static StandardStream? GetStandardStream(this TextWriter writer) @@ -130,13 +130,13 @@ public static bool IsRedirected(this StandardStream stream) } /// - /// Gets the associated with a if that - /// reader is for the standard input stream. + /// Gets the associated with a instance, + /// if that instance is for the standard input stream. /// /// The . /// - /// The that is reader from, or - /// if it's not reader from the standard input stream. + /// The that is reading from, or + /// if it's not reading from the standard input stream. /// /// /// is . diff --git a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs index e12c505..0adb071 100644 --- a/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs +++ b/src/Ookii.CommandLine/Terminal/VirtualTerminal.cs @@ -128,7 +128,8 @@ public static VirtualTerminalSupport EnableColor(StandardStream stream) /// /// /// The and parameters will be ignored - /// if the + /// if the standard output stream does not support VT sequences. In that case, the value of + /// will be written without formatting. /// /// public static void WriteLineFormatted(string text, TextFormat textFormat, TextFormat? reset = null) @@ -155,7 +156,8 @@ public static void WriteLineFormatted(string text, TextFormat textFormat, TextFo /// /// /// The and parameters will be ignored - /// if the + /// if the standard error stream does not support VT sequences. In that case, the value of + /// will be written without formatting. /// /// public static void WriteLineErrorFormatted(string text, TextFormat? textFormat = null, TextFormat? reset = null) diff --git a/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs index 349bf7d..7e48171 100644 --- a/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs +++ b/src/Ookii.CommandLine/UnknownArgumentEventArgs.cs @@ -5,6 +5,7 @@ namespace Ookii.CommandLine; /// /// Provides data for the event. /// +/// public class UnknownArgumentEventArgs : EventArgs { /// @@ -115,8 +116,8 @@ public UnknownArgumentEventArgs(string token, ReadOnlyMemory name, ReadOnl public bool Ignore { get; set; } /// - /// Gets a value that indicates whether parsing should be canceled when the event handler - /// returns. + /// Gets or sets a value that indicates whether parsing should be canceled when the event + /// handler returns. /// /// /// One of the values of the enumeration. The default value is diff --git a/src/Ookii.CommandLine/UsageFooterAttribute.cs b/src/Ookii.CommandLine/UsageFooterAttribute.cs index 61b01d4..3d29418 100644 --- a/src/Ookii.CommandLine/UsageFooterAttribute.cs +++ b/src/Ookii.CommandLine/UsageFooterAttribute.cs @@ -21,6 +21,7 @@ namespace Ookii.CommandLine; /// resource table that can be localized. /// /// +/// [AttributeUsage(AttributeTargets.Class)] public class UsageFooterAttribute : Attribute { diff --git a/src/Ookii.CommandLine/UsageWriter.cs b/src/Ookii.CommandLine/UsageWriter.cs index bc1e8c9..10002b0 100644 --- a/src/Ookii.CommandLine/UsageWriter.cs +++ b/src/Ookii.CommandLine/UsageWriter.cs @@ -488,7 +488,7 @@ public bool IncludeExecutableExtension /// indentation. /// /// - /// if a line after am empty line should be indented; otherwise, + /// if a line after an empty line should be indented; otherwise, /// . The default value is . /// /// @@ -2035,8 +2035,8 @@ protected virtual void WriteCommandDescription(string description) /// /// /// - /// The base implementation calls if the help - /// instruction is explicitly or automatically enabled. + /// The base implementation calls the method if the + /// help instruction is explicitly or automatically enabled. /// /// /// This method is called by the base implementation of the diff --git a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs index c39742e..056c3ce 100644 --- a/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs +++ b/src/Ookii.CommandLine/Validation/ValidateEnumValueAttribute.cs @@ -6,7 +6,8 @@ namespace Ookii.CommandLine.Validation; /// /// Validates whether the value of an enumeration type is one of the defined values for that -/// type, and provides additional conversion options for enumeration types. +/// type, and provides additional conversion options for enumeration types converted using the +/// class. /// /// /// @@ -64,7 +65,7 @@ public class ValidateEnumValueAttribute : ArgumentValidationWithHelpAttribute /// Setting this to essentially makes this validator do nothing. It /// is useful if you want to use it solely to list defined values in the usage help, or if /// you want to use one of the other properties that affect the - /// without also checking for defined values. + /// class without also checking for defined values. /// /// public bool AllowNonDefinedValues { get; set; } @@ -80,8 +81,7 @@ public class ValidateEnumValueAttribute : ArgumentValidationWithHelpAttribute /// /// /// This property is used when validation fails, and is also checked by the - /// class, which is the default converter for enumeration types, - /// when conversion fails due to an invalid string value. + /// class when conversion fails due to an invalid string value. /// /// public bool IncludeValuesInErrorMessage { get; set; } = true; From f72f8754738c7bda41fa4fd7effa8645f37276b3 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 26 Jan 2024 12:02:06 -0800 Subject: [PATCH 53/54] Update nuget packages. --- src/Ookii.CommandLine.Tests/Ookii.CommandLine.Tests.csproj | 4 ++-- src/Samples/Wpf/Wpf.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ookii.CommandLine.Tests/Ookii.CommandLine.Tests.csproj b/src/Ookii.CommandLine.Tests/Ookii.CommandLine.Tests.csproj index f1b41bd..30d5a68 100644 --- a/src/Ookii.CommandLine.Tests/Ookii.CommandLine.Tests.csproj +++ b/src/Ookii.CommandLine.Tests/Ookii.CommandLine.Tests.csproj @@ -15,8 +15,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Samples/Wpf/Wpf.csproj b/src/Samples/Wpf/Wpf.csproj index 2159102..98c5229 100644 --- a/src/Samples/Wpf/Wpf.csproj +++ b/src/Samples/Wpf/Wpf.csproj @@ -14,7 +14,7 @@ This is sample code, so you can use it freely. - + From af6b8cbece454b41e914e3175de8e15136b5b997 Mon Sep 17 00:00:00 2001 From: Sven Groot Date: Fri, 26 Jan 2024 12:06:45 -0800 Subject: [PATCH 54/54] Release preparation. --- docs/ChangeLog.md | 2 +- docs/Ookii.CommandLine.shfbproj | 2 +- src/Directory.Build.props | 1 - .../AnalyzerReleases.Shipped.md | 7 +++++++ .../AnalyzerReleases.Unshipped.md | 6 ------ 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 6909850..14ca01b 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -1,6 +1,6 @@ # What’s new in Ookii.CommandLine -## Ookii.CommandLine 4.1 (TBD) +## Ookii.CommandLine 4.1 (2024-01-26) - Support for [using a `--` argument](Arguments.md#the----argument) to escape argument names for the remaining arguments, or to cancel parsing. This can be enabled using diff --git a/docs/Ookii.CommandLine.shfbproj b/docs/Ookii.CommandLine.shfbproj index b691949..6a37fa3 100644 --- a/docs/Ookii.CommandLine.shfbproj +++ b/docs/Ookii.CommandLine.shfbproj @@ -51,7 +51,7 @@ C#, Visual Basic, Visual Basic Usage, Managed C++ Blank True - True + False AboveNamespaces True True diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3f9482a..3e5bff0 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,5 @@ Ookii.org Copyright (c) Sven Groot (Ookii.org) 4.1.0 - preview3 \ No newline at end of file diff --git a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md index f673512..f38cffc 100644 --- a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md +++ b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Shipped.md @@ -1,3 +1,10 @@ ; Shipped analyzer releases ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md +## Release 4.1 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +OCL0040 | Ookii.CommandLine | Warning | ParserShouldBeGenerated diff --git a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md index 466167a..6ed4fa0 100644 --- a/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md +++ b/src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md @@ -2,9 +2,3 @@ ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md ; This is only for rules used by the analyzer; rules for the source generator should not be listed ; here. - -### New Rules - -Rule ID | Category | Severity | Notes ---------|----------|----------|------- -OCL0040 | Ookii.CommandLine | Warning | ParserShouldBeGenerated