├── .github ├── dependabot.yml └── workflows │ └── dotnet.yml ├── .gitignore ├── README.md ├── docs ├── Arguments.md ├── ChangeLog.md ├── DefiningArguments.md ├── Migrating.md ├── Ookii.CommandLine.shfbproj ├── ParsingArguments.md ├── README.md ├── SourceGeneration.md ├── SourceGenerationDiagnostics.md ├── Subcommands.md ├── Tutorial.md ├── UsageHelp.md ├── Utilities.md ├── Validation.md ├── images │ ├── color.png │ ├── custom_usage.png │ └── wpf.png └── refs.json ├── license.md └── src ├── .editorconfig ├── Create-Release.ps1 ├── Directory.Build.props ├── Ookii.CommandLine.CodeFix ├── Ookii.CommandLine.CodeFix.csproj ├── ParserCodeFixProvider.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx └── ookii.snk ├── Ookii.CommandLine.Generator ├── AnalyzerReleases.Shipped.md ├── AnalyzerReleases.Unshipped.md ├── ArgumentAttributes.cs ├── ArgumentsClassAttributes.cs ├── CommandAttributeInfo.cs ├── CommandGenerator.cs ├── CommandLineArgumentAttributeInfo.cs ├── ConverterGenerator.cs ├── Diagnostics.cs ├── Extensions.cs ├── Ookii.CommandLine.Generator.csproj ├── ParserAnalyzer.cs ├── ParserGenerator.cs ├── ParserIncrementalGenerator.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── SourceBuilder.cs ├── TypeHelper.cs └── ookii.snk ├── Ookii.CommandLine.Tests.Commands ├── Commands.cs ├── Ookii.CommandLine.Tests.Commands.csproj └── ookii.snk ├── Ookii.CommandLine.Tests ├── .editorconfig ├── ArgumentCategory.cs ├── ArgumentTypes.cs ├── ArgumentValidatorTest.cs ├── CommandLineParserNullableTest.cs ├── CommandLineParserTest.Usage.cs ├── CommandLineParserTest.cs ├── CommandOptionsTest.cs ├── CommandTypes.cs ├── CustomUsageWriter.cs ├── KeyValuePairConverterTest.cs ├── LineWrappingTextWriterTest.Constants.cs ├── LineWrappingTextWriterTest.cs ├── NameTransformTest.cs ├── NetStandardHelpers.cs ├── NullableArgumentTypes.cs ├── Ookii.CommandLine.Tests.csproj ├── ParseOptionsAttributeTest.cs ├── ParseOptionsTest.cs ├── StandardStreamTest.cs ├── SubCommandTest.Usage.cs ├── SubCommandTest.cs ├── TextFormatTest.cs └── ookii.snk ├── Ookii.CommandLine.sln ├── Ookii.CommandLine ├── AliasAttribute.cs ├── AllowDuplicateDictionaryKeysAttribute.cs ├── AmbiguousPrefixAliasException.cs ├── ApplicationFriendlyNameAttribute.cs ├── ArgumentKind.cs ├── ArgumentParsedEventArgs.cs ├── BreakLineMode.cs ├── CancelMode.cs ├── CategoryInfo.cs ├── CommandLineArgument.cs ├── CommandLineArgumentAttribute.cs ├── CommandLineArgumentErrorCategory.cs ├── CommandLineArgumentException.cs ├── CommandLineParser.cs ├── CommandLineParserGeneric.cs ├── Commands │ ├── AsyncCommandBase.cs │ ├── AutomaticVersionCommand.cs │ ├── AutomaticVersionCommandInfo.cs │ ├── CommandAttribute.cs │ ├── CommandInfo.cs │ ├── CommandManager.cs │ ├── CommandOptions.cs │ ├── GeneratedCommandManagerAttribute.cs │ ├── IAsyncCommand.cs │ ├── ICommand.cs │ ├── ICommandWithCustomParsing.cs │ ├── ParentCommand.cs │ └── ParentCommandAttribute.cs ├── Conversion │ ├── ArgumentConverter.cs │ ├── ArgumentConverterAttribute.cs │ ├── BooleanConverter.cs │ ├── ConstructorConverter.cs │ ├── EnumConverter.cs │ ├── GeneratedConverterNamespaceAttribute.cs │ ├── KeyConverterAttribute.cs │ ├── KeyValuePairConverter.cs │ ├── KeyValueSeparatorAttribute.cs │ ├── NullableConverter.cs │ ├── ParsableConverter.cs │ ├── ParseConverter.cs │ ├── SpanParsableConverter.cs │ ├── StringConverter.cs │ ├── ValueConverterAttribute.cs │ ├── WrappedDefaultTypeConverter.cs │ ├── WrappedTypeConverter.cs │ └── WrappedTypeConverterGeneric.cs ├── Convert-SyncMethod.ps1 ├── DescriptionListFilterMode.cs ├── DescriptionListSortMode.cs ├── DictionaryArgumentInfo.cs ├── DisposableWrapper.cs ├── DuplicateArgumentEventArgs.cs ├── ErrorMode.cs ├── GeneratedParserAttribute.cs ├── IParser.cs ├── IParserProvider.cs ├── LineWrappingTextWriter.Async.cs ├── LineWrappingTextWriter.cs ├── LocalizedStringProvider.Error.cs ├── LocalizedStringProvider.Usage.cs ├── LocalizedStringProvider.Validators.cs ├── LocalizedStringProvider.cs ├── MultiValueArgumentInfo.cs ├── MultiValueSeparatorAttribute.cs ├── NameTransform.cs ├── NameTransformExtensions.cs ├── NativeMethods.txt ├── NonSwitchBoolean.cs ├── Ookii.CommandLine.csproj ├── PackageReadme.md ├── ParseOptions.cs ├── ParseOptionsAttribute.cs ├── ParseResult.cs ├── ParseStatus.cs ├── ParsingMode.cs ├── PrefixTerminationMode.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── RingBuffer.Async.cs ├── RingBuffer.cs ├── ShortAliasAttribute.cs ├── StringBuilderExtensions.cs ├── StringComparisonExtensions.cs ├── StringSegmentType.cs ├── StringSpan.Async.cs ├── StringSpanExtensions.cs ├── Support │ ├── ArgumentCreationInfo.cs │ ├── ArgumentProvider.cs │ ├── CommandProvider.cs │ ├── GeneratedArgument.cs │ ├── GeneratedArgumentBase.cs │ ├── GeneratedArgumentProvider.cs │ ├── GeneratedCommandInfo.cs │ ├── GeneratedCommandInfoWithCustomParsing.cs │ ├── GeneratedDictionaryArgument.cs │ ├── ProviderKind.cs │ ├── ReflectionArgument.cs │ ├── ReflectionArgumentProvider.cs │ ├── ReflectionCommandInfo.cs │ └── ReflectionCommandProvider.cs ├── Terminal │ ├── StandardStream.cs │ ├── StandardStreamExtensions.cs │ ├── TextFormat.cs │ ├── VirtualTerminal.cs │ └── VirtualTerminalSupport.cs ├── TriState.cs ├── TypeHelper.cs ├── UnknownArgumentEventArgs.cs ├── UsageFooterAttribute.cs ├── UsageHelpRequest.cs ├── UsageWriter.cs ├── Validation │ ├── ArgumentValidationAttribute.cs │ ├── ArgumentValidationWithHelpAttribute.cs │ ├── ClassValidationAttribute.cs │ ├── DependencyValidationAttribute.cs │ ├── ProhibitsAttribute.cs │ ├── RequiresAnyAttribute.cs │ ├── RequiresAttribute.cs │ ├── ValidateCountAttribute.cs │ ├── ValidateEnumValueAttribute.cs │ ├── ValidateNotEmptyAttribute.cs │ ├── ValidateNotNullAttribute.cs │ ├── ValidateNotWhiteSpaceAttribute.cs │ ├── ValidatePatternAttribute.cs │ ├── ValidateRangeAttribute.cs │ └── ValidateStringLengthAttribute.cs ├── ValueDescriptionAttribute.cs ├── WrappingMode.cs ├── icon.png └── ookii.snk ├── Samples ├── ArgumentDependencies │ ├── ArgumentDependencies.csproj │ ├── Program.cs │ ├── ProgramArguments.cs │ └── README.md ├── Categories │ ├── ArgumentCategory.cs │ ├── Arguments.cs │ ├── Categories.csproj │ ├── DomainUser.cs │ ├── InstallMethod.cs │ ├── Program.cs │ └── README.md ├── CustomUsage │ ├── CustomStringProvider.cs │ ├── CustomUsage.csproj │ ├── CustomUsageWriter.cs │ ├── Program.cs │ ├── ProgramArguments.cs │ └── README.md ├── LongShort │ ├── LongShort.csproj │ ├── Program.cs │ ├── ProgramArguments.cs │ └── README.md ├── NestedCommands │ ├── BaseCommand.cs │ ├── CourseCommands.cs │ ├── ExitCode.cs │ ├── GeneratedManager.cs │ ├── ListCommand.cs │ ├── Models.cs │ ├── NestedCommands.csproj │ ├── Program.cs │ ├── README.md │ └── StudentCommands.cs ├── Parser │ ├── Parser.csproj │ ├── Program.cs │ ├── ProgramArguments.cs │ └── README.md ├── README.md ├── Subcommand │ ├── EncodingConverter.cs │ ├── ExitCode.cs │ ├── GeneratedManager.cs │ ├── Program.cs │ ├── README.md │ ├── ReadCommand.cs │ ├── Subcommand.csproj │ └── WriteCommand.cs ├── TopLevelArguments │ ├── CommandUsageWriter.cs │ ├── EncodingConverter.cs │ ├── ExitCode.cs │ ├── GeneratedManager.cs │ ├── Program.cs │ ├── README.md │ ├── ReadCommand.cs │ ├── TopLevelArguments.cs │ ├── TopLevelArguments.csproj │ ├── TopLevelUsageWriter.cs │ └── WriteCommand.cs └── Wpf │ ├── App.xaml │ ├── App.xaml.cs │ ├── Arguments.cs │ ├── AssemblyInfo.cs │ ├── HtmlUsageWriter.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── README.md │ ├── UsageWindow.xaml │ ├── UsageWindow.xaml.cs │ ├── Wpf.csproj │ └── app.manifest ├── history.txt └── test.runsettings /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/src" 5 | schedule: 6 | interval: "daily" 7 | target-branch: "main" 8 | groups: 9 | # Group all dependencies in one PR. 10 | nuget-dependencies: 11 | patterns: 12 | - "*" 13 | ignore: 14 | # 4.11.x is the latest version that can work with the .Net 8.0 SDK for both of these. 15 | - dependency-name: "Microsoft.CodeAnalysis.CSharp" 16 | update-types: ["version-update:semver-major", "version-update:semver-minor"] 17 | - dependency-name: "Microsoft.CodeAnalysis.CSharp.Workspaces" 18 | update-types: ["version-update:semver-major", "version-update:semver-minor"] 19 | - package-ecosystem: "github-actions" 20 | directory: "/" 21 | schedule: 22 | interval: "daily" 23 | target-branch: "main" 24 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: .NET 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest] 17 | 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v4 23 | with: 24 | dotnet-version: 8.0.x 25 | - name: Restore dependencies 26 | run: dotnet restore src 27 | - name: Build 28 | run: dotnet build src --no-restore --configuration Release 29 | - name: Test 30 | run: dotnet test src --no-build --verbosity normal --configuration Release 31 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Ookii.CommandLine is a library for parsing the command line arguments for your applications. In this 4 | guide, we will introduce the basic functionality using a tutorial, describe in detail the command 5 | line parsing rules used and how to create various types of arguments, how to use and customize usage 6 | help, and explain other functionality such as subcommands. 7 | 8 | In addition to this documentation, several [samples](../src/Samples) are provided, all with 9 | explanations of what they do and examples of their output. 10 | 11 | ## Contents 12 | 13 | - [What's new in Ookii.CommandLine](ChangeLog.md) 14 | - [Migrating from Ookii.CommandLine 2.x / 3.x](Migrating.md) 15 | - [Tutorial: getting started with Ookii.CommandLine](Tutorial.md) 16 | - [Command line arguments in Ookii.CommandLine](Arguments.md) 17 | - [Defining command line arguments](DefiningArguments.md) 18 | - [Parsing command line arguments](ParsingArguments.md) 19 | - [Generating usage help](UsageHelp.md) 20 | - [Argument validation and dependencies](Validation.md) 21 | - [Subcommands](Subcommands.md) 22 | - [Source generation](SourceGeneration.md) 23 | - [Diagnostics](SourceGenerationDiagnostics.md) 24 | - [LineWrappingTextWriter and other utilities](Utilities.md) 25 | - [Class library documentation](https://www.ookii.org/Link/CommandLineDoc) 26 | -------------------------------------------------------------------------------- /docs/images/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/docs/images/color.png -------------------------------------------------------------------------------- /docs/images/custom_usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/docs/images/custom_usage.png -------------------------------------------------------------------------------- /docs/images/wpf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/docs/images/wpf.png -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # MIT license 2 | 3 | Copyright (c) Sven Groot (Ookii.org) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Create-Release.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/src/Create-Release.ps1 -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sven Groot 5 | Ookii.org 6 | Copyright (c) Sven Groot (Ookii.org) 7 | 5.0.0 8 | 9 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.CodeFix/Ookii.CommandLine.CodeFix.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | false 6 | 12.0 7 | enable 8 | enable 9 | true 10 | true 11 | ookii.snk 12 | false 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | True 28 | True 29 | Resources.resx 30 | 31 | 32 | 33 | 34 | 35 | ResXFileCodeGenerator 36 | Resources.Designer.cs 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.CodeFix/ookii.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/src/Ookii.CommandLine.CodeFix/ookii.snk -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Generator/AnalyzerReleases.Unshipped.md: -------------------------------------------------------------------------------- 1 | ; Unshipped analyzer release 2 | ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md 3 | ; This is only for rules used by the analyzer; rules for the source generator should not be listed 4 | ; here. 5 | 6 | ### New Rules 7 | 8 | Rule ID | Category | Severity | Notes 9 | --------|-------------------|----------|----------------------- 10 | OCL0043 | Ookii.CommandLine | Error | CategoryNotEnum 11 | OCL0044 | Ookii.CommandLine | Error | MismatchedCategoryType 12 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Generator/CommandAttributeInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Ookii.CommandLine.Generator; 4 | 5 | internal class CommandAttributeInfo 6 | { 7 | public CommandAttributeInfo(AttributeData data) 8 | { 9 | foreach (var named in data.NamedArguments) 10 | { 11 | switch (named.Key) 12 | { 13 | case nameof(IsHidden): 14 | IsHidden = (bool)named.Value.Value!; 15 | break; 16 | } 17 | } 18 | } 19 | 20 | public bool IsHidden { get; } 21 | } 22 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Generator/CommandLineArgumentAttributeInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Ookii.CommandLine.Generator; 4 | 5 | internal class CommandLineArgumentAttributeInfo 6 | { 7 | private readonly bool _isShort; 8 | private readonly bool _isPositional; 9 | 10 | public CommandLineArgumentAttributeInfo(AttributeData data) 11 | { 12 | if (data.ConstructorArguments.Length > 0) 13 | { 14 | ArgumentName = data.ConstructorArguments[0].Value as string; 15 | } 16 | 17 | foreach (var named in data.NamedArguments) 18 | { 19 | switch (named.Key) 20 | { 21 | case nameof(IsRequired): 22 | IsRequired = (bool)named.Value.Value!; 23 | HasIsRequired = true; 24 | break; 25 | 26 | case nameof(DefaultValue): 27 | DefaultValue = named.Value.Value; 28 | break; 29 | 30 | case nameof(Position): 31 | var position = (int)named.Value.Value!; 32 | if (position >= 0) 33 | { 34 | Position = position; 35 | } 36 | 37 | break; 38 | 39 | case nameof(IsPositional): 40 | _isPositional = (bool)named.Value.Value!; 41 | break; 42 | 43 | case nameof(IsShort): 44 | _isShort = (bool)named.Value.Value!; 45 | ExplicitIsShort = _isShort; 46 | break; 47 | 48 | case nameof(ShortName): 49 | ShortName = (char)named.Value.Value!; 50 | break; 51 | 52 | case nameof(IsLong): 53 | IsLong = (bool)named.Value.Value!; 54 | break; 55 | 56 | case nameof(IsHidden): 57 | IsHidden = (bool)named.Value.Value!; 58 | break; 59 | 60 | case nameof(IncludeDefaultInUsageHelp): 61 | IncludeDefaultInUsageHelp = (bool)named.Value.Value!; 62 | break; 63 | 64 | case nameof(Category): 65 | Category = named.Value; 66 | break; 67 | } 68 | } 69 | } 70 | 71 | public string? ArgumentName { get; } 72 | 73 | public bool IsRequired { get; } 74 | 75 | public bool HasIsRequired { get; } 76 | 77 | public int? Position { get; } 78 | 79 | public bool IsPositional => _isPositional || Position != null; 80 | 81 | public object? DefaultValue { get; } 82 | 83 | public bool IsShort => _isShort || ShortName != '\0'; 84 | 85 | public bool? ExplicitIsShort { get; } 86 | 87 | public char ShortName { get; } 88 | 89 | public bool IsLong { get; } = true; 90 | 91 | public bool IsHidden { get; } 92 | 93 | public bool IncludeDefaultInUsageHelp { get; set; } = true; 94 | 95 | public TypedConstant Category { get; set; } 96 | } 97 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Generator/Ookii.CommandLine.Generator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | false 6 | 12.0 7 | enable 8 | enable 9 | true 10 | true 11 | ookii.snk 12 | false 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | True 27 | True 28 | Resources.resx 29 | 30 | 31 | 32 | 33 | 34 | ResXFileCodeGenerator 35 | Resources.Designer.cs 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Generator/ookii.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/src/Ookii.CommandLine.Generator/ookii.snk -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests.Commands/Commands.cs: -------------------------------------------------------------------------------- 1 | // Commands to test loading commands from an external assembly. 2 | using Ookii.CommandLine.Commands; 3 | 4 | namespace Ookii.CommandLine.Tests.Commands; 5 | 6 | #pragma warning disable OCL0034 // Subcommands should have a description. 7 | 8 | [Command("external")] 9 | [GeneratedParser] 10 | public partial class ExternalCommand : ICommand 11 | { 12 | public int Run() => throw new NotImplementedException(); 13 | } 14 | 15 | [Command] 16 | public class OtherExternalCommand : ICommand 17 | { 18 | public int Run() => throw new NotImplementedException(); 19 | } 20 | 21 | [Command] 22 | internal class InternalCommand : ICommand 23 | { 24 | public int Run() => throw new NotImplementedException(); 25 | } 26 | 27 | public class NotACommand : ICommand 28 | { 29 | public int Run() => throw new NotImplementedException(); 30 | } 31 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests.Commands/Ookii.CommandLine.Tests.Commands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;net48 5 | enable 6 | enable 7 | 12.0 8 | true 9 | ookii.snk 10 | false 11 | 12 | 1.0.0 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests.Commands/ookii.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/src/Ookii.CommandLine.Tests.Commands/ookii.snk -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | # Unused parameters or privates are not a problem in the test project, and is often deliberate. 2 | [*.cs] 3 | dotnet_diagnostic.IDE0051.severity = silent 4 | dotnet_code_quality_unused_parameters = silent 5 | dotnet_diagnostic.IDE0060.severity = silent -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/ArgumentCategory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Ookii.CommandLine.Tests; 9 | 10 | class DerivedDescriptionAttribute : DescriptionAttribute 11 | { 12 | public override string Description => "The second category."; 13 | } 14 | 15 | enum ArgumentCategory 16 | { 17 | [Description("The first category.")] 18 | Category1, 19 | [DerivedDescription()] 20 | Category2, 21 | // No description 22 | Category3, 23 | // Unused 24 | [Description("The fourth category.")] 25 | Category4, 26 | } 27 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/CustomUsageWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine.Tests; 4 | 5 | internal class CustomUsageWriter : UsageWriter 6 | { 7 | public CustomUsageWriter() { } 8 | 9 | public CustomUsageWriter(LineWrappingTextWriter writer) : base(writer) { } 10 | 11 | protected override void WriteParserUsageFooter() 12 | { 13 | WriteLine("This is a custom footer."); 14 | } 15 | 16 | protected override void WriteCommandListUsageFooter() 17 | { 18 | base.WriteCommandListUsageFooter(); 19 | WriteLine("This is the command list footer."); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/KeyValuePairConverterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Ookii.CommandLine.Conversion; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | 7 | namespace Ookii.CommandLine.Tests; 8 | 9 | [TestClass] 10 | public class KeyValuePairConverterTest 11 | { 12 | // Needed because SpanParsableConverter only exists on .Net 7. 13 | private class IntConverter : ArgumentConverter 14 | { 15 | public override object Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 16 | => int.Parse(value.ToString(), culture); 17 | } 18 | 19 | [TestMethod] 20 | public void TestConvertFrom() 21 | { 22 | var parser = new CommandLineParser(); 23 | var converter = new KeyValuePairConverter(); 24 | var converted = converter.Convert("foo=5".AsMemory(), CultureInfo.InvariantCulture, parser.GetArgument("Argument1")!); 25 | Assert.AreEqual(KeyValuePair.Create("foo", 5), converted); 26 | } 27 | 28 | [TestMethod] 29 | public void TestCustomSeparator() 30 | { 31 | var parser = new CommandLineParser(); 32 | var converter = new KeyValuePairConverter(new StringConverter(), new IntConverter(), ":", false); 33 | var pair = converter.Convert("foo:5".AsMemory(), CultureInfo.InvariantCulture, parser.GetArgument("Argument1")!); 34 | Assert.AreEqual(KeyValuePair.Create("foo", 5), pair); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/NameTransformTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Ookii.CommandLine.Tests; 4 | 5 | [TestClass] 6 | public class NameTransformTest 7 | { 8 | [TestMethod] 9 | public void TestNone() 10 | { 11 | var transform = NameTransform.None; 12 | Assert.AreEqual("TestName", transform.Apply("TestName")); 13 | Assert.AreEqual("testName", transform.Apply("testName")); 14 | Assert.AreEqual("__test__name__", transform.Apply("__test__name__")); 15 | Assert.AreEqual("TestName", transform.Apply("TestName")); 16 | } 17 | 18 | [TestMethod] 19 | public void TestPascalCase() 20 | { 21 | var transform = NameTransform.PascalCase; 22 | Assert.AreEqual("TestName", transform.Apply("TestName")); 23 | Assert.AreEqual("TestName", transform.Apply("testName")); 24 | Assert.AreEqual("TestName", transform.Apply("__test__name__")); 25 | Assert.AreEqual("TestName", transform.Apply("TestName")); 26 | } 27 | 28 | [TestMethod] 29 | public void TestCamelCase() 30 | { 31 | var transform = NameTransform.CamelCase; 32 | Assert.AreEqual("testName", transform.Apply("TestName")); 33 | Assert.AreEqual("testName", transform.Apply("testName")); 34 | Assert.AreEqual("testName", transform.Apply("__test__name__")); 35 | Assert.AreEqual("testName", transform.Apply("TestName")); 36 | } 37 | 38 | [TestMethod] 39 | public void TestSnakeCase() 40 | { 41 | var transform = NameTransform.SnakeCase; 42 | Assert.AreEqual("test_name", transform.Apply("TestName")); 43 | Assert.AreEqual("test_name", transform.Apply("testName")); 44 | Assert.AreEqual("test_name", transform.Apply("__test__name__")); 45 | Assert.AreEqual("test_name", transform.Apply("TestName")); 46 | } 47 | 48 | [TestMethod] 49 | public void TestDashCase() 50 | { 51 | var transform = NameTransform.DashCase; 52 | Assert.AreEqual("test-name", transform.Apply("TestName")); 53 | Assert.AreEqual("test-name", transform.Apply("testName")); 54 | Assert.AreEqual("test-name", transform.Apply("__test__name__")); 55 | Assert.AreEqual("test-name", transform.Apply("TestName")); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/NetStandardHelpers.cs: -------------------------------------------------------------------------------- 1 | #if !NET6_0_OR_GREATER 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Ookii.CommandLine.Tests; 8 | 9 | internal static class KeyValuePair 10 | { 11 | public static KeyValuePair Create(TKey key, TValue value) 12 | { 13 | return new KeyValuePair(key, value); 14 | } 15 | } 16 | 17 | internal static class StringExtensions 18 | { 19 | private static readonly char[] _newLineChars = { '\r', '\n' }; 20 | 21 | public static string ReplaceLineEndings(this string value, string? ending = null) 22 | { 23 | ending ??= Environment.NewLine; 24 | var result = new StringBuilder(); 25 | int pos = 0; 26 | while (pos < value.Length) 27 | { 28 | int index = value.IndexOfAny(_newLineChars, pos); 29 | if (index < 0) 30 | { 31 | result.Append(value.Substring(pos)); 32 | break; 33 | } 34 | 35 | if (index > pos) 36 | { 37 | result.Append(value.Substring(pos, index - pos)); 38 | } 39 | 40 | result.Append(ending); 41 | if (value[index] == '\r' && index + 1 < value.Length && value[index + 1] == '\n') 42 | { 43 | pos = index + 2; 44 | } 45 | else 46 | { 47 | pos = index + 1; 48 | } 49 | } 50 | 51 | return result.ToString(); 52 | } 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/Ookii.CommandLine.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net48 5 | net8.0 6 | enable 7 | Ookii.CommandLine Unit Tests 8 | Tests for Ookii.CommandLine. 9 | false 10 | 12.0 11 | true 12 | true 13 | ookii.snk 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/ParseOptionsAttributeTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Ookii.CommandLine.Tests; 4 | 5 | [TestClass] 6 | public class ParseOptionsAttributeTest 7 | { 8 | [TestMethod] 9 | public void TestConstructor() 10 | { 11 | var options = new ParseOptionsAttribute(); 12 | Assert.IsTrue(options.AllowWhiteSpaceValueSeparator); 13 | Assert.IsNull(options.ArgumentNamePrefixes); 14 | Assert.AreEqual(NameTransform.None, options.ArgumentNameTransform); 15 | Assert.IsTrue(options.AutoHelpArgument); 16 | Assert.IsTrue(options.AutoPrefixAliases); 17 | Assert.IsTrue(options.AutoVersionArgument); 18 | Assert.IsFalse(options.CaseSensitive); 19 | Assert.AreEqual(ErrorMode.Error, options.DuplicateArguments); 20 | Assert.IsFalse(options.IsPosix); 21 | Assert.IsNull(options.LongArgumentNamePrefix); 22 | Assert.AreEqual(ParsingMode.Default, options.Mode); 23 | Assert.IsNull(options.NameValueSeparators); 24 | Assert.AreEqual(NameTransform.None, options.ValueDescriptionTransform); 25 | } 26 | 27 | [TestMethod] 28 | public void TestIsPosix() 29 | { 30 | var options = new ParseOptionsAttribute() 31 | { 32 | IsPosix = true 33 | }; 34 | 35 | Assert.IsTrue(options.IsPosix); 36 | Assert.AreEqual(ParsingMode.LongShort, options.Mode); 37 | Assert.IsTrue(options.CaseSensitive); 38 | Assert.AreEqual(NameTransform.DashCase, options.ArgumentNameTransform); 39 | Assert.AreEqual(NameTransform.DashCase, options.ValueDescriptionTransform); 40 | options.CaseSensitive = false; 41 | Assert.IsFalse(options.IsPosix); 42 | options.CaseSensitive = true; 43 | Assert.IsTrue(options.IsPosix); 44 | 45 | options.IsPosix = false; 46 | Assert.AreEqual(ParsingMode.Default, options.Mode); 47 | Assert.IsFalse(options.CaseSensitive); 48 | Assert.AreEqual(NameTransform.None, options.ArgumentNameTransform); 49 | Assert.AreEqual(NameTransform.None, options.ValueDescriptionTransform); 50 | 51 | options = new ParseOptionsAttribute() 52 | { 53 | Mode = ParsingMode.LongShort, 54 | CaseSensitive = true, 55 | ArgumentNameTransform = NameTransform.DashCase, 56 | ValueDescriptionTransform = NameTransform.DashCase 57 | }; 58 | 59 | Assert.IsTrue(options.IsPosix); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/StandardStreamTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Ookii.CommandLine.Terminal; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Ookii.CommandLine.Tests; 7 | 8 | [TestClass] 9 | public class StandardStreamTest 10 | { 11 | [TestMethod] 12 | public void TestGetWriter() 13 | { 14 | Assert.AreSame(Console.Out, StandardStream.Output.GetWriter()); 15 | Assert.AreSame(Console.Error, StandardStream.Error.GetWriter()); 16 | Assert.ThrowsException(() => StandardStream.Input.GetWriter()); 17 | } 18 | 19 | [TestMethod] 20 | public void TestOpenStream() 21 | { 22 | using var output = StandardStream.Output.OpenStream(); 23 | using var error = StandardStream.Error.OpenStream(); 24 | using var input = StandardStream.Input.OpenStream(); 25 | Assert.AreNotSame(output, input); 26 | Assert.AreNotSame(output, error); 27 | Assert.AreNotSame(error, input); 28 | } 29 | 30 | [TestMethod] 31 | public void TestGetStandardStream() 32 | { 33 | Assert.AreEqual(StandardStream.Output, Console.Out.GetStandardStream()); 34 | Assert.AreEqual(StandardStream.Error, Console.Error.GetStandardStream()); 35 | Assert.AreEqual(StandardStream.Input, Console.In.GetStandardStream()); 36 | using (var writer = new StringWriter()) 37 | { 38 | Assert.IsNull(writer.GetStandardStream()); 39 | } 40 | 41 | using (var writer = LineWrappingTextWriter.ForConsoleOut()) 42 | { 43 | Assert.AreEqual(StandardStream.Output, writer.GetStandardStream()); 44 | } 45 | 46 | using (var writer = LineWrappingTextWriter.ForStringWriter()) 47 | { 48 | Assert.IsNull(writer.GetStandardStream()); 49 | } 50 | 51 | using (var reader = new StringReader("foo")) 52 | { 53 | Assert.IsNull(reader.GetStandardStream()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/TextFormatTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Ookii.CommandLine.Terminal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Ookii.CommandLine.Tests; 10 | 11 | [TestClass] 12 | public class TextFormatTest 13 | { 14 | [TestMethod] 15 | public void TestDefault() 16 | { 17 | var value = new TextFormat(); 18 | Assert.AreEqual("", value.Value); 19 | 20 | var value2 = default(TextFormat); 21 | Assert.AreEqual("", value2.Value); 22 | } 23 | 24 | [TestMethod] 25 | public void TestAddition() 26 | { 27 | var value = TextFormat.ForegroundRed + TextFormat.BackgroundGreen; 28 | Assert.AreEqual("\x1b[31m\x1b[42m", value.Value); 29 | } 30 | 31 | [TestMethod] 32 | public void TestEquality() 33 | { 34 | Assert.AreEqual(TextFormat.ForegroundRed, TextFormat.ForegroundRed); 35 | Assert.AreNotEqual(TextFormat.ForegroundGreen, TextFormat.ForegroundRed); 36 | var value1 = TextFormat.ForegroundRed; 37 | var value2 = TextFormat.ForegroundRed; 38 | Assert.IsTrue(value1 == value2); 39 | Assert.IsFalse(value1 != value2); 40 | value2 = TextFormat.ForegroundGreen; 41 | Assert.IsFalse(value1 == value2); 42 | Assert.IsTrue(value1 != value2); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine.Tests/ookii.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/src/Ookii.CommandLine.Tests/ookii.snk -------------------------------------------------------------------------------- /src/Ookii.CommandLine/AllowDuplicateDictionaryKeysAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ookii.CommandLine; 5 | 6 | /// 7 | /// Indicates that a dictionary argument accepts the same key more than once. 8 | /// 9 | /// 10 | /// 11 | /// If this attribute is applied to an argument whose type is 12 | /// or another type that implements the interface, a 13 | /// duplicate key will simply overwrite the previous value. 14 | /// 15 | /// 16 | /// If this attribute is not applied, a with a 17 | /// the property set to 18 | /// will 19 | /// be thrown when a duplicate key is specified. 20 | /// 21 | /// 22 | /// The is ignored if it is applied to any 23 | /// other type of argument. 24 | /// 25 | /// 26 | /// 27 | /// 28 | [AttributeUsage(AttributeTargets.Property)] 29 | public sealed class AllowDuplicateDictionaryKeysAttribute : Attribute 30 | { 31 | } 32 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ApplicationFriendlyNameAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Ookii.CommandLine; 5 | 6 | /// 7 | /// Sets the friendly name of the application to be used in the output of the "-Version" 8 | /// argument or "version" subcommand. 9 | /// 10 | /// 11 | /// 12 | /// This attribute is used when a "-Version" argument is automatically added to the arguments 13 | /// of your application, and by the automatically added "version" subcommand. It can be applied to 14 | /// the type defining command line arguments, or to the assembly that contains it. 15 | /// 16 | /// 17 | /// If not present, the automatic "-Version" argument and "version" command will use the 18 | /// value of the attribute, or the assembly name of the 19 | /// assembly containing the arguments type. 20 | /// 21 | /// 22 | /// For the "version" subcommand, this attribute must be applied to the entry point assembly of 23 | /// your application. 24 | /// 25 | /// 26 | /// 27 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)] 28 | public class ApplicationFriendlyNameAttribute : Attribute 29 | { 30 | private readonly string _name; 31 | 32 | /// 33 | /// Initializes a new instance of the 34 | /// attribute. 35 | /// 36 | /// The friendly name of the application. 37 | /// 38 | /// is . 39 | /// 40 | public ApplicationFriendlyNameAttribute(string name) 41 | { 42 | _name = name ?? throw new ArgumentNullException(nameof(name)); 43 | } 44 | 45 | /// 46 | /// Gets the friendly name of the application. 47 | /// 48 | /// 49 | /// The friendly name of the application. 50 | /// 51 | public string Name => _name; 52 | } 53 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ArgumentKind.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Specifies what kind of argument an instance of the class 5 | /// represents. 6 | /// 7 | public enum ArgumentKind 8 | { 9 | /// 10 | /// A regular argument that can have only a single value. 11 | /// 12 | SingleValue, 13 | /// 14 | /// A multi-value argument. 15 | /// 16 | MultiValue, 17 | /// 18 | /// A dictionary argument, which is a multi-value argument where the values are key/value 19 | /// pairs with unique keys. 20 | /// 21 | Dictionary, 22 | /// 23 | /// An argument that invokes a method when specified. 24 | /// 25 | Method 26 | } 27 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ArgumentParsedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine; 4 | 5 | /// 6 | /// Provides data for the event. 7 | /// 8 | /// 9 | public class ArgumentParsedEventArgs : EventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The argument that has been parsed. 15 | /// is . 16 | public ArgumentParsedEventArgs(CommandLineArgument argument) 17 | { 18 | Argument = argument ?? throw new ArgumentNullException(nameof(argument)); 19 | } 20 | 21 | /// 22 | /// Gets the argument that was parsed. 23 | /// 24 | /// 25 | /// The instance for the argument. 26 | /// 27 | public CommandLineArgument Argument { get; } 28 | 29 | /// 30 | /// Gets a value that indicates whether parsing should be canceled when the event handler 31 | /// returns. 32 | /// 33 | /// 34 | /// One of the values of the enumeration. The default value is the 35 | /// value of the 36 | /// property, or the return value of a method argument. 37 | /// 38 | /// 39 | /// 40 | /// If the event handler sets this property to a value other than , 41 | /// command line processing will stop immediately, returning either or 42 | /// an instance of the arguments class according to the value. 43 | /// 44 | /// 45 | /// If you want usage help to be displayed after canceling, set the value to 46 | /// . 47 | /// 48 | /// 49 | /// 50 | /// 51 | /// 52 | public CancelMode CancelParsing { get; set; } 53 | } 54 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/BreakLineMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | internal enum BreakLineMode 4 | { 5 | Backward, 6 | Forward, 7 | Force 8 | } 9 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/CancelMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates whether and how an argument should cancel parsing. 5 | /// 6 | /// 7 | /// 8 | public enum CancelMode 9 | { 10 | /// 11 | /// The argument does not cancel parsing. 12 | /// 13 | None, 14 | /// 15 | /// The argument cancels parsing, discarding the results so far. Parsing, using for example the 16 | /// method, will 17 | /// return a value. The 18 | /// property will be . The 19 | /// property will be 20 | /// . 21 | /// 22 | Abort, 23 | /// 24 | /// The same as , but the property will be . 26 | /// 27 | AbortWithHelp, 28 | /// 29 | /// The argument cancels parsing, returning success using the results so far. Remaining 30 | /// arguments are not parsed, and will be available in the 31 | /// property. The property will be . 32 | /// If not all required arguments have values at this point, an exception will be thrown. 33 | /// 34 | Success, 35 | } 36 | 37 | static class CancelModeExtensions 38 | { 39 | public static bool IsAborted(this CancelMode self) => self is CancelMode.Abort or CancelMode.AbortWithHelp; 40 | 41 | public static bool HelpRequested(this CancelMode self) => self == CancelMode.AbortWithHelp; 42 | } 43 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/CategoryInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace Ookii.CommandLine; 5 | 6 | /// 7 | /// Provides information about an argument category. 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | public readonly struct CategoryInfo 14 | { 15 | private readonly CommandLineParser _parser; 16 | 17 | internal CategoryInfo(CommandLineParser parser, Enum category) 18 | { 19 | _parser = parser; 20 | Category = category; 21 | } 22 | 23 | /// 24 | /// Gets the category. 25 | /// 26 | /// 27 | /// The category's enumeration value. The enumeration type depends on the type used with the 28 | /// property. 29 | /// 30 | public Enum Category { get; } 31 | 32 | /// 33 | /// Gets the description of the category, which is displayed in the usage help. 34 | /// 35 | /// 36 | /// A string with the description of the category. 37 | /// 38 | /// 39 | /// 40 | /// The description for an argument category can be specified by applying the 41 | /// attribute to the enumeration value. If no description 42 | /// specified, the string representation of the enumeration value is used. 43 | /// 44 | /// 45 | public string Description => _parser.GetCategoryDescription(Category); 46 | } 47 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/CommandLineArgumentErrorCategory.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Ookii.CommandLine; 3 | 4 | /// 5 | /// Specifies the kind of error that occurred while parsing arguments. 6 | /// 7 | public enum CommandLineArgumentErrorCategory 8 | { 9 | /// 10 | /// The category was not specified. 11 | /// 12 | Unspecified, 13 | /// 14 | /// The argument value supplied could not be converted to the type of the argument. 15 | /// 16 | ArgumentValueConversion, 17 | /// 18 | /// The argument name supplied does not name a known argument. 19 | /// 20 | UnknownArgument, 21 | /// 22 | /// An argument name was supplied, but without an accompanying value. 23 | /// 24 | MissingNamedArgumentValue, 25 | /// 26 | /// An argument was supplied more than once. 27 | /// 28 | DuplicateArgument, 29 | /// 30 | /// Too many positional arguments were supplied. 31 | /// 32 | TooManyArguments, 33 | /// 34 | /// Not all required arguments were supplied. 35 | /// 36 | MissingRequiredArgument, 37 | /// 38 | /// Invalid value for a dictionary argument; typically the result of a duplicate key. 39 | /// 40 | InvalidDictionaryValue, 41 | /// 42 | /// An error occurred creating an instance of the arguments type (e.g. the constructor threw an exception). 43 | /// 44 | CreateArgumentsTypeError, 45 | /// 46 | /// An error occurred applying the value of the argument (e.g. the property set accessor threw an exception). 47 | /// 48 | ApplyValueError, 49 | /// 50 | /// An argument value was after conversion from a string, and the argument type is a value 51 | /// type or (in .Net 6.0 and later) a non-nullable reference type. 52 | /// 53 | NullArgumentValue, 54 | /// 55 | /// A combined short argument contains an argument that is not a switch. 56 | /// 57 | CombinedShortNameNonSwitch, 58 | /// 59 | /// An instance of a class derived from the 60 | /// class failed to validate the argument. 61 | /// 62 | ValidationFailed, 63 | /// 64 | /// An argument failed a dependency check performed by the 65 | /// or the class. 66 | /// 67 | DependencyFailed, 68 | /// 69 | /// The provided argument name was a prefix of more than one argument name or alias. 70 | /// 71 | AmbiguousPrefixAlias 72 | } 73 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Commands/AsyncCommandBase.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Ookii.CommandLine.Commands; 5 | 6 | /// 7 | /// Base class for asynchronous commands that want the method to 8 | /// invoke the method. 9 | /// 10 | /// 11 | /// 12 | /// This class is provided for convenience for creating asynchronous commands without having to 13 | /// implement the method. 14 | /// 15 | /// 16 | /// 17 | public abstract class AsyncCommandBase : IAsyncCommand 18 | { 19 | /// 20 | /// Calls the method and waits synchronously for it to complete. 21 | /// 22 | /// The exit code of the command. 23 | public virtual int Run() => Task.Run(() => RunAsync(CancellationToken.None)).ConfigureAwait(false).GetAwaiter().GetResult(); 24 | 25 | /// 26 | public abstract Task RunAsync(CancellationToken cancellationToken = default); 27 | } 28 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Commands/AutomaticVersionCommand.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Support; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | namespace Ookii.CommandLine.Commands; 7 | 8 | [Command] 9 | internal class AutomaticVersionCommand : ICommand 10 | { 11 | private class ArgumentProvider : GeneratedArgumentProvider 12 | { 13 | private readonly LocalizedStringProvider _stringProvider; 14 | 15 | public ArgumentProvider(LocalizedStringProvider stringProvider) 16 | : base(typeof(AutomaticVersionCommand), null, null, null, null) 17 | { 18 | _stringProvider = stringProvider; 19 | } 20 | 21 | public override bool IsCommand => true; 22 | 23 | public override string Description => _stringProvider.AutomaticVersionCommandDescription(); 24 | 25 | public override string UsageFooter => string.Empty; 26 | 27 | public override object CreateInstance(CommandLineParser parser, object?[]? requiredPropertyValues) => new AutomaticVersionCommand(parser); 28 | 29 | public override IEnumerable GetArguments(CommandLineParser parser) 30 | { 31 | yield break; 32 | } 33 | } 34 | 35 | private readonly CommandLineParser _parser; 36 | 37 | public AutomaticVersionCommand(CommandLineParser parser) 38 | { 39 | _parser = parser; 40 | } 41 | 42 | public int Run() 43 | { 44 | var assembly = Assembly.GetEntryAssembly(); 45 | if (assembly == null) 46 | { 47 | Console.WriteLine(Properties.Resources.UnknownVersion); 48 | return 1; 49 | } 50 | 51 | // We can't use _parser.ApplicationFriendlyName because we're interested in the entry 52 | // assembly, not the one containing this command. 53 | var attribute = assembly.GetCustomAttribute(); 54 | var friendlyName = attribute?.Name ?? assembly.GetName().Name ?? string.Empty; 55 | CommandLineArgument.ShowVersion(_parser.StringProvider, assembly, friendlyName); 56 | return 0; 57 | } 58 | 59 | public static CommandLineParser CreateParser(ParseOptions options) 60 | => new(new ArgumentProvider(options.StringProvider), options); 61 | } 62 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Commands/AutomaticVersionCommandInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Ookii.CommandLine.Commands; 6 | 7 | internal class AutomaticVersionCommandInfo : CommandInfo 8 | { 9 | public AutomaticVersionCommandInfo(CommandManager manager) 10 | : base(typeof(AutomaticVersionCommand), manager.Options.AutoVersionCommandName(), manager) 11 | { 12 | } 13 | 14 | public override string? Description => Manager.Options.StringProvider.AutomaticVersionCommandDescription(); 15 | 16 | public override bool UseCustomArgumentParsing => false; 17 | 18 | public override IEnumerable Aliases => []; 19 | 20 | public override ICommandWithCustomParsing CreateInstanceWithCustomParsing() 21 | => throw new InvalidOperationException(Properties.Resources.NoCustomParsing); 22 | 23 | public override CommandLineParser CreateParser() 24 | => AutomaticVersionCommand.CreateParser(Manager.Options); 25 | } 26 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Commands/GeneratedCommandManagerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ookii.CommandLine.Commands; 5 | 6 | /// 7 | /// Indicates that the target class is a command manager created using source generation. 8 | /// 9 | /// 10 | /// 11 | /// When using this attribute, source generation is used to determine which classes are available 12 | /// at compile time, either in the assembly being compiled, or the assemblies specified using the 13 | /// property. The target class will be modified to inherit from the 14 | /// class, and should be used instead of the 15 | /// class to find, create, and run commands. 16 | /// 17 | /// 18 | /// Using a class with this attribute avoids the use of runtime reflection to determine which 19 | /// commands are available, improving performance and allowing your application to be trimmed. 20 | /// 21 | /// 22 | /// To use source generation for the command line arguments of individual commands, use the 23 | /// attribute on each command class. 24 | /// 25 | /// 26 | /// 27 | /// Source generation 28 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 29 | public sealed class GeneratedCommandManagerAttribute : Attribute 30 | { 31 | /// 32 | /// Gets or sets the names of the assemblies that contain the commands that the generated 33 | /// will use. 34 | /// 35 | /// 36 | /// An array with assembly names, or to use the commands from the 37 | /// assembly containing the generated manager. 38 | /// 39 | /// 40 | /// 41 | /// The assemblies used must be directly referenced by your project. Dynamically loading 42 | /// assemblies is not supported by this attribute; use the 43 | /// 44 | /// constructor instead for that purpose. 45 | /// 46 | /// 47 | /// The names in this array can be either just the assembly name, or the full assembly 48 | /// identity including version, culture, and public key token. 49 | /// 50 | /// 51 | public string[]? AssemblyNames { get; set; } 52 | } 53 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Commands/IAsyncCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Ookii.CommandLine.Commands; 5 | 6 | /// 7 | /// Represents a subcommand that executes asynchronously. 8 | /// 9 | /// 10 | /// 11 | /// This interface adds a method to the 12 | /// interface, that will be invoked by the 13 | /// 14 | /// method and its overloads. This allows you to write tasks that use asynchronous code. 15 | /// 16 | /// 17 | /// Use the class as a base class for your command to get a default 18 | /// implementation of the 19 | /// 20 | /// 21 | public interface IAsyncCommand : ICommand 22 | { 23 | /// 24 | /// Runs the command asynchronously. 25 | /// 26 | /// 27 | /// The token to monitor for cancellation requests. The default value is 28 | /// . 29 | /// 30 | /// 31 | /// A task that represents the asynchronous run operation. The result of the task is the 32 | /// exit code for the command. 33 | /// 34 | /// 35 | /// 36 | /// Typically, your application's Main() method should return the exit code of the 37 | /// command that was executed. 38 | /// 39 | /// 40 | /// This method will only be invoked if you run commands with the 41 | /// 42 | /// method or one of its overloads. Typically, it's recommended to implement the 43 | /// method to invoke this method and wait for 44 | /// it. Use the class for a default implementation that does 45 | /// this. 46 | /// 47 | /// 48 | /// If a was passed to the 49 | /// method, 50 | /// the parameter will be set to that token. Otherwise, 51 | /// the value will be . 52 | /// 53 | /// 54 | Task RunAsync(CancellationToken cancellationToken = default); 55 | } 56 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine.Commands; 2 | 3 | /// 4 | /// Represents a subcommand of the application. 5 | /// 6 | /// 7 | /// 8 | /// To create a subcommand for your application, create a class that implements this interface, 9 | /// then apply the attribute to it. 10 | /// 11 | /// 12 | /// The class will be used as an arguments type with the . 13 | /// Alternatively, a command can implement its own argument parsing by implementing the 14 | /// interface. 15 | /// 16 | /// 17 | /// 18 | public interface ICommand 19 | { 20 | /// 21 | /// Runs the command. 22 | /// 23 | /// The exit code for the command. 24 | /// 25 | /// 26 | /// Typically, your application's Main() method should return the exit code of the 27 | /// command that was executed. 28 | /// 29 | /// 30 | int Run(); 31 | } 32 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Commands/ICommandWithCustomParsing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine.Commands; 4 | 5 | /// 6 | /// Represents a subcommand that does its own argument parsing. 7 | /// 8 | /// 9 | /// 10 | /// Unlike commands that only implement the interface, commands that 11 | /// implement the interface are not created with the 12 | /// class. Instead, they must have a public constructor with no 13 | /// parameters, and must parse the arguments manually by implementing the 14 | /// method. 15 | /// 16 | /// 17 | /// 18 | public interface ICommandWithCustomParsing : ICommand 19 | { 20 | /// 21 | /// Parses the arguments for the command. 22 | /// 23 | /// The arguments for the command. 24 | /// The that was used to create this command. 25 | void Parse(ReadOnlyMemory args, CommandManager manager); 26 | } 27 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/ArgumentConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Ookii.CommandLine.Conversion; 5 | 6 | /// 7 | /// Base class for converters from a string to the type of an argument. 8 | /// 9 | /// 10 | /// 11 | /// To create a custom argument converter, you must implement the 12 | /// method. 13 | /// 14 | /// 15 | /// The source of the conversion is a that 16 | /// contains the argument text. This may be an entire argument, or a 17 | /// substring of an argument if the user used a non-whitespace argument name 18 | /// separator like -Name:Value. 19 | /// 20 | /// 21 | /// is used because 22 | /// does not 23 | /// allocate when the memory is an entire string. However, since it still 24 | /// allocates for substrings, it's still recommended to convert using the 25 | /// or directly 26 | /// if possible. 27 | /// 28 | /// 29 | /// 30 | public abstract class ArgumentConverter 31 | { 32 | /// 33 | /// Converts a string memory region to the type of the argument. 34 | /// 35 | /// 36 | /// The containing the string to convert. 37 | /// 38 | /// The culture to use for the conversion. 39 | /// 40 | /// The that will use the converted value. 41 | /// 42 | /// An object representing the converted value. 43 | /// 44 | /// or is . 45 | /// 46 | /// 47 | /// The value was not in a correct format for the target type. 48 | /// 49 | /// 50 | /// The value was out of range for the target type. 51 | /// 52 | /// 53 | /// The value was not in a correct format for the target type. Unlike other exceptions, 54 | /// which will be wrapped in a , a 55 | /// thrown by this method will be passed down to 56 | /// the user unmodified. 57 | /// 58 | public abstract object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument); 59 | } 60 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/BooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Ookii.CommandLine.Conversion; 5 | 6 | /// 7 | /// Converter for arguments with values. These are typically switch arguments. 8 | /// 9 | /// 10 | /// 11 | /// For a switch argument, the converter is only used if the value was explicitly specified. 12 | /// 13 | /// 14 | /// 15 | public class BooleanConverter : ArgumentConverter 16 | { 17 | /// 18 | /// A default instance of the converter. 19 | /// 20 | public static readonly BooleanConverter Instance = new(); 21 | 22 | /// 23 | /// Converts a string memory region to a . 24 | /// 25 | /// The containing the string to convert. 26 | /// The culture to use for the conversion. 27 | /// 28 | /// The that will use the converted value. 29 | /// 30 | /// An object representing the converted value. 31 | /// 32 | /// 33 | /// This method performs the conversion using the method. 34 | /// 35 | /// 36 | /// 37 | /// The value was not in a correct format for the target type. 38 | /// 39 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 40 | #if NET6_0_OR_GREATER 41 | => bool.Parse(value.Span); 42 | #else 43 | => bool.Parse(value.ToString()); 44 | #endif 45 | } 46 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/ConstructorConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | using System.Reflection; 5 | using System.Runtime.ExceptionServices; 6 | 7 | namespace Ookii.CommandLine.Conversion; 8 | 9 | internal class ConstructorConverter : ArgumentConverter 10 | { 11 | #if NET6_0_OR_GREATER 12 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] 13 | #endif 14 | private readonly Type _type; 15 | 16 | public ConstructorConverter( 17 | #if NET6_0_OR_GREATER 18 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] 19 | #endif 20 | Type type) 21 | { 22 | _type = type; 23 | } 24 | 25 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 26 | { 27 | try 28 | { 29 | // Since we are passing BindingFlags.Public, the correct annotation is present. 30 | return Activator.CreateInstance(_type, value.ToString()); 31 | } 32 | catch (TargetInvocationException ex) 33 | { 34 | if (ex.InnerException == null) 35 | { 36 | throw; 37 | } 38 | 39 | // Rethrow inner exception with original call stack. 40 | ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); 41 | 42 | // Actually unreachable. 43 | throw; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/GeneratedConverterNamespaceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine.Conversion; 4 | 5 | /// 6 | /// Sets the namespace to use for argument converters generated for arguments classes with the 7 | /// attribute. 8 | /// 9 | /// 10 | /// 11 | /// To convert argument types for which no built-in non-reflection argument converter exists, 12 | /// such as classes that have a constructor taking a parameter, or those 13 | /// that have a Parse method but don't implement , the source 14 | /// generator will create a new argument converter. The generated converter class will be 15 | /// internal to the assembly containing the generated parser, and will be placed in the namespace 16 | /// Ookii.CommandLine.Conversion.Generated by default. 17 | /// 18 | /// 19 | /// Use this attribute to modify the namespace used. 20 | /// 21 | /// 22 | /// 23 | [AttributeUsage(AttributeTargets.Assembly)] 24 | public sealed class GeneratedConverterNamespaceAttribute : Attribute 25 | { 26 | /// 27 | /// Initializes a new instance of the class 28 | /// with the specified namespace. 29 | /// 30 | /// The namespace to use. 31 | /// 32 | /// is . 33 | /// 34 | public GeneratedConverterNamespaceAttribute(string @namespace) 35 | { 36 | Namespace = @namespace ?? throw new ArgumentNullException(nameof(@namespace)); 37 | } 38 | 39 | /// 40 | /// Gets the namespace to use for generated argument converters. 41 | /// 42 | /// 43 | /// The full name of the namespace. 44 | /// 45 | public string Namespace { get; } 46 | } 47 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/KeyValueSeparatorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine.Conversion; 4 | 5 | /// 6 | /// Defines a custom key/value separator for dictionary arguments. 7 | /// 8 | /// 9 | /// 10 | /// By default, dictionary arguments use the equals sign ('=') as a separator. By using this 11 | /// attribute, you can choose a custom separator. This separator cannot appear in the key, 12 | /// but can appear in the value. 13 | /// 14 | /// 15 | /// This attribute is ignored if the dictionary argument uses the 16 | /// attribute, or if the argument is not a dictionary argument. 17 | /// 18 | /// 19 | /// 20 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] 21 | public class KeyValueSeparatorAttribute : Attribute 22 | { 23 | private readonly string _separator; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The separator to use. 29 | /// is . 30 | /// is an empty string. 31 | public KeyValueSeparatorAttribute(string separator) 32 | { 33 | if (separator == null) 34 | { 35 | throw new ArgumentNullException(nameof(separator)); 36 | } 37 | 38 | if (separator.Length == 0) 39 | { 40 | throw new ArgumentException(Properties.Resources.EmptyKeyValueSeparator, nameof(separator)); 41 | } 42 | 43 | _separator = separator; 44 | } 45 | 46 | /// 47 | /// Gets the separator. 48 | /// 49 | /// 50 | /// The separator. 51 | /// 52 | public string Separator => _separator; 53 | } 54 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/NullableConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | 5 | namespace Ookii.CommandLine.Conversion; 6 | 7 | /// 8 | /// Converts from a string to a . 9 | /// 10 | /// 11 | /// 12 | /// This converter uses the specified converter for the type T, except when the input is an 13 | /// empty string, in which case it return . This parallels the behavior 14 | /// of the . 15 | /// 16 | /// 17 | /// 18 | public class NullableConverter : ArgumentConverter 19 | { 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The converter to use for the target type. 24 | /// 25 | /// is . 26 | /// 27 | public NullableConverter(ArgumentConverter baseConverter) 28 | { 29 | BaseConverter = baseConverter ?? throw new ArgumentNullException(nameof(baseConverter)); 30 | } 31 | 32 | /// 33 | /// Gets the converter for the underlying type. 34 | /// 35 | /// 36 | /// The for the underlying type. 37 | /// 38 | public ArgumentConverter BaseConverter { get; } 39 | 40 | /// 41 | /// 42 | /// An object representing the converted value, or if the value was an 43 | /// empty string span. 44 | /// 45 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 46 | { 47 | if (value.Length == 0) 48 | { 49 | return null; 50 | } 51 | 52 | return BaseConverter.Convert(value, culture, argument); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/ParsableConverter.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0_OR_GREATER 2 | 3 | using System; 4 | using System.Globalization; 5 | 6 | namespace Ookii.CommandLine.Conversion; 7 | 8 | /// 9 | /// An argument converter for types that implement the interface. 10 | /// 11 | /// The type to convert to. 12 | /// 13 | /// 14 | /// Conversion is performed using the method. 15 | /// 16 | /// 17 | /// Only use this converter for types that implement the interface, 18 | /// but not the interface. For types that implement the 19 | /// interface, use the 20 | /// class. 21 | /// 22 | /// 23 | /// 24 | public class ParsableConverter : ArgumentConverter 25 | where T : IParsable 26 | { 27 | /// 28 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 29 | => T.Parse(value.ToString(), culture); 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/ParseConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | using System.Runtime.ExceptionServices; 5 | 6 | namespace Ookii.CommandLine.Conversion; 7 | 8 | internal class ParseConverter : ArgumentConverter 9 | { 10 | private readonly MethodInfo _method; 11 | private readonly bool _hasCulture; 12 | 13 | public ParseConverter(MethodInfo method, bool hasCulture) 14 | { 15 | _method = method; 16 | _hasCulture = hasCulture; 17 | } 18 | 19 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 20 | { 21 | object[] parameters = _hasCulture 22 | ? [value.ToString(), culture] 23 | : [value.ToString()]; 24 | 25 | try 26 | { 27 | return _method.Invoke(null, parameters); 28 | } 29 | catch (TargetInvocationException ex) 30 | { 31 | if (ex.InnerException == null) 32 | { 33 | throw; 34 | } 35 | 36 | // Rethrow inner exception with original call stack. 37 | ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); 38 | 39 | // Actually unreachable. 40 | throw; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/SpanParsableConverter.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0_OR_GREATER 2 | 3 | using System; 4 | using System.Globalization; 5 | 6 | namespace Ookii.CommandLine.Conversion; 7 | 8 | /// 9 | /// An argument converter for types that implement the interface. 10 | /// 11 | /// The type to convert to. 12 | /// 13 | /// 14 | /// Conversion is performed using the 15 | /// method. 16 | /// 17 | /// 18 | /// For types that implement the interface, but not the 19 | /// interface, use the class. 20 | /// 21 | /// 22 | /// 23 | public class SpanParsableConverter : ArgumentConverter 24 | where T : ISpanParsable 25 | { 26 | /// 27 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 28 | => T.Parse(value.Span, culture); 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/StringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Ookii.CommandLine.Conversion; 5 | 6 | /// 7 | /// A converter for arguments with string values. 8 | /// 9 | /// 10 | /// This converter does not perform any actual conversion, and returns the existing string as-is. 11 | /// If the input was a for , a new string is 12 | /// allocated for it. 13 | /// 14 | /// 15 | public class StringConverter : ArgumentConverter 16 | { 17 | /// 18 | /// A default instance of the converter. 19 | /// 20 | public static readonly StringConverter Instance = new(); 21 | 22 | /// 23 | /// Converts a string memory region to a string. 24 | /// 25 | /// The string memory region to convert. 26 | /// The culture to use for the conversion. 27 | /// 28 | /// The that will use the converted value. 29 | /// 30 | /// The value of the parameter as a string. 31 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 32 | => value.ToString(); 33 | } 34 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/WrappedDefaultTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Ookii.CommandLine.Conversion; 5 | 6 | /// 7 | /// An that wraps the default for a 8 | /// type. 9 | /// 10 | /// The type to convert to. 11 | /// 12 | /// This class will convert argument values from a string using the default 13 | /// for the type . If you wish to use a specific custom , 14 | /// use the class instead. 15 | /// 16 | /// 17 | #if NET6_0_OR_GREATER 18 | [RequiresUnreferencedCode("Determining the TypeConverter for a type may require the type to be annotated.")] 19 | #endif 20 | public class WrappedDefaultTypeConverter : WrappedTypeConverter 21 | { 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | public WrappedDefaultTypeConverter() 26 | : base(TypeDescriptor.GetConverter(typeof(T))) 27 | { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/WrappedTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | 5 | namespace Ookii.CommandLine.Conversion; 6 | 7 | /// 8 | /// An that wraps an existing . 9 | /// 10 | /// 11 | /// 12 | /// For a convenient way to use to use any with the 13 | /// attribute, use the 14 | /// class. To use the default for a type, use the 15 | /// class. 16 | /// 17 | /// 18 | /// 19 | public class WrappedTypeConverter : ArgumentConverter 20 | { 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The to use. 25 | /// 26 | /// is . 27 | /// 28 | /// 29 | /// The specified by cannot convert 30 | /// from a . 31 | /// 32 | public WrappedTypeConverter(TypeConverter converter) 33 | { 34 | Converter = converter ?? throw new ArgumentNullException(nameof(converter)); 35 | if (!converter.CanConvertFrom(typeof(string))) 36 | { 37 | throw new ArgumentException(Properties.Resources.InvalidTypeConverter, nameof(converter)); 38 | } 39 | } 40 | 41 | /// 42 | /// Gets the type converter used by this instance. 43 | /// 44 | /// 45 | /// An instance of the class. 46 | /// 47 | public TypeConverter Converter { get; } 48 | 49 | /// 50 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 51 | => Converter.ConvertFromString(null, culture, value.ToString()); 52 | } 53 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Conversion/WrappedTypeConverterGeneric.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Ookii.CommandLine.Conversion; 4 | 5 | /// 6 | /// An that wraps an existing . 7 | /// 8 | /// The type of the to wrap. 9 | /// 10 | /// 11 | /// This class will convert argument values from a string using the 12 | /// class . If you wish to use the default 13 | /// for a type, use the class instead. 14 | /// 15 | /// 16 | public class WrappedTypeConverter : WrappedTypeConverter 17 | where T : TypeConverter, new() 18 | { 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | public WrappedTypeConverter() 23 | : base(new T()) 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Convert-SyncMethod.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory = $true)][string[]]$Path, 3 | [Parameter(Mandatory = $true)][string]$OutputDir 4 | ) 5 | 6 | # This script takes async methods and makes the following replacements to generate a non-async 7 | # version of that method. 8 | $replacements = @( 9 | @("Async", ""), # Remove Async from identifiers 10 | @("async Task", "partial void"), # Function signature change 11 | @("await ", ""), # Remove await keyword 12 | @(" is (ReadOnlyMemory, ReadOnlyMemory) splits", ".TryGetValue(out var splits)"), # Replace pattern matching with NullableReadOnlySpan.TryGetValue 13 | @("ReadOnlyMemory", "ReadOnlySpan"), # Async stream functions uses Memory instead of span 14 | @(".Span", ""), # Needed to convert Memory usage to Span. 15 | @("async ", ""), # Remove keyword from async lambda 16 | @(", CancellationToken cancellationToken = default", ""), # Remove cancellation token parameter 17 | @(", CancellationToken cancellationToken", ""), # Remove cancellation token parameter 18 | @("(CancellationToken cancellationToken)", "()"), # Remove cancellation token parameter 19 | @(", cancellationToken", ""), # Remove cancellation token parameter value 20 | @("(cancellationToken)", "()") # Remove cancellation token parameter value 21 | ) 22 | 23 | $files = Get-Item $Path 24 | foreach ($file in $files) 25 | { 26 | $outputPath = Join-Path $OutputDir ($file.Name.Replace("Async", "Sync")) 27 | 28 | &{ 29 | "// " 30 | Get-Content $file | ForEach-Object { 31 | # Regex replace generic Task before the other replacements. 32 | $result = ($_ -creplace 'async Task<(.*?)>','partial $1') 33 | foreach ($item in $replacements) { 34 | $result = $result.Replace($item[0], $item[1]) 35 | } 36 | 37 | $result 38 | } 39 | } | Set-Content $outputPath 40 | } 41 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/DescriptionListFilterMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates which arguments should be included in the description list when generating usage help. 5 | /// 6 | /// 7 | public enum DescriptionListFilterMode 8 | { 9 | /// 10 | /// Include arguments that have any information that is not included in the syntax, 11 | /// such as aliases, a default value, or a description. 12 | /// 13 | Information, 14 | /// 15 | /// Include only arguments that have a description. 16 | /// 17 | Description, 18 | /// 19 | /// Include all arguments. 20 | /// 21 | All, 22 | /// 23 | /// Omit the description list entirely. 24 | /// 25 | None 26 | } 27 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/DescriptionListSortMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates how the arguments in the description list should be sorted when generating usage help. 5 | /// 6 | /// 7 | public enum DescriptionListSortMode 8 | { 9 | /// 10 | /// The descriptions are listed in the same order as the usage syntax: first the positional 11 | /// arguments, then the non-positional required named arguments sorted by name, then the 12 | /// remaining arguments sorted by name. 13 | /// 14 | UsageOrder, 15 | /// 16 | /// The descriptions are listed in alphabetical order by argument name. If the parsing mode 17 | /// is , this uses the long name of the argument, unless 18 | /// the argument has no long name, in which case the short name is used. 19 | /// 20 | Alphabetical, 21 | /// 22 | /// The same as , but in reverse order. 23 | /// 24 | AlphabeticalDescending, 25 | /// 26 | /// The descriptions are listed in alphabetical order by the short argument name. If the 27 | /// argument has no short name, the long name is used. If the parsing mode is not 28 | /// , this has the same effect as . 29 | /// 30 | AlphabeticalShortName, 31 | /// 32 | /// The same as , but in reverse order. 33 | /// 34 | AlphabeticalShortNameDescending, 35 | } 36 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/DictionaryArgumentInfo.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Conversion; 2 | using System; 3 | 4 | namespace Ookii.CommandLine; 5 | 6 | /// 7 | /// Provides information that only applies to dictionary arguments. 8 | /// 9 | /// 10 | public sealed class DictionaryArgumentInfo 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// 16 | /// if duplicate dictionary keys are allowed; otherwise, 17 | /// . 18 | /// 19 | /// The type of the dictionary's keys. 20 | /// The type of the dictionary's values. 21 | /// The separator between the keys and values. 22 | /// 23 | /// or or 24 | /// is . 25 | /// 26 | public DictionaryArgumentInfo(bool allowDuplicateKeys, Type keyType, Type valueType, string keyValueSeparator) 27 | { 28 | AllowDuplicateKeys = allowDuplicateKeys; 29 | KeyType = keyType ?? throw new ArgumentNullException(nameof(keyType)); 30 | ValueType = valueType ?? throw new ArgumentNullException(nameof(valueType)); 31 | KeyValueSeparator = keyValueSeparator ?? throw new ArgumentNullException(nameof(keyValueSeparator)); 32 | } 33 | 34 | /// 35 | /// Gets a value indicating whether this argument allows duplicate keys. 36 | /// 37 | /// 38 | /// if this argument allows duplicate keys; otherwise, . 39 | /// 40 | /// 41 | public bool AllowDuplicateKeys { get; } 42 | 43 | /// 44 | /// Gets the type of the keys of a dictionary argument. 45 | /// 46 | /// 47 | /// The of the keys in the dictionary. 48 | /// 49 | public Type KeyType { get; } 50 | 51 | /// 52 | /// Gets the type of the values of a dictionary argument. 53 | /// 54 | /// 55 | /// The of the values in the dictionary. 56 | /// 57 | public Type ValueType { get; } 58 | 59 | /// 60 | /// Gets the separator for key/value pairs. 61 | /// 62 | /// 63 | /// The custom value specified using the attribute, or 64 | /// if no attribute was 65 | /// present. 66 | /// 67 | /// 68 | public string KeyValueSeparator { get; } 69 | } 70 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/DisposableWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine; 4 | 5 | internal static class DisposableWrapper 6 | { 7 | public static DisposableWrapper Create(T? obj, Func createIfNull) 8 | where T : IDisposable 9 | { 10 | return new DisposableWrapper(obj, createIfNull); 11 | } 12 | } 13 | 14 | /// 15 | /// Helper to either use an existing instance (and not dispose it), or create an instance 16 | /// and dispose it. 17 | /// 18 | /// 19 | internal class DisposableWrapper : IDisposable 20 | where T : IDisposable 21 | { 22 | private readonly T _inner; 23 | private bool _needDispose; 24 | 25 | public DisposableWrapper(T? inner, Func createIfNull) 26 | { 27 | if (inner == null) 28 | { 29 | _inner = createIfNull(); 30 | _needDispose = true; 31 | } 32 | else 33 | { 34 | _inner = inner; 35 | } 36 | } 37 | 38 | public T Inner => _inner; 39 | 40 | protected virtual void Dispose(bool disposing) 41 | { 42 | if (_needDispose) 43 | { 44 | if (disposing) 45 | { 46 | _inner.Dispose(); 47 | } 48 | 49 | _needDispose = false; 50 | } 51 | } 52 | 53 | public void Dispose() 54 | { 55 | Dispose(disposing: true); 56 | GC.SuppressFinalize(this); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/DuplicateArgumentEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine; 4 | 5 | /// 6 | /// Provides data for the event. 7 | /// 8 | /// 9 | public class DuplicateArgumentEventArgs : EventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The argument that was specified more than once. 15 | /// 16 | /// The raw new value of the argument, or if no value was given. 17 | /// 18 | /// 19 | /// is 20 | /// 21 | public DuplicateArgumentEventArgs(CommandLineArgument argument, ReadOnlyMemory? newValue) 22 | { 23 | Argument = argument ?? throw new ArgumentNullException(nameof(argument)); 24 | NewValue = newValue; 25 | } 26 | 27 | /// 28 | /// Gets the argument that was specified more than once. 29 | /// 30 | /// 31 | /// The that was specified more than once. 32 | /// 33 | public CommandLineArgument Argument { get; } 34 | 35 | /// 36 | /// Gets the new value that will be assigned to the argument. 37 | /// 38 | /// 39 | /// The raw string value provided on the command line, before conversion, or 40 | /// if the argument is a switch argument that was provided without an explicit value. 41 | /// 42 | public ReadOnlyMemory? NewValue { get; } 43 | 44 | /// 45 | /// Gets or sets a value that indicates whether the value of the argument should stay 46 | /// unmodified. 47 | /// 48 | /// 49 | /// to preserve the current value of the argument, or 50 | /// to replace it with the value of the property. The default value 51 | /// is . 52 | /// 53 | public bool KeepOldValue { get; set; } 54 | } 55 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ErrorMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates whether something is an error, warning, or allowed. 5 | /// 6 | /// 7 | public enum ErrorMode 8 | { 9 | /// 10 | /// The operation should raise an error. 11 | /// 12 | Error, 13 | /// 14 | /// The operation should display a warning, but continue. 15 | /// 16 | Warning, 17 | /// 18 | /// The operation should continue silently. 19 | /// 20 | Allow, 21 | } 22 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/IParserProvider.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0_OR_GREATER 2 | 3 | using System; 4 | 5 | namespace Ookii.CommandLine; 6 | 7 | /// 8 | /// Defines a mechanism to create a for a type. 9 | /// 10 | /// The type that implements this interface. 11 | /// 12 | /// 13 | /// This type is only available when using .Net 7 or later. 14 | /// 15 | /// 16 | /// This interface is automatically implemented on a class when the 17 | /// is used. Classes without that attribute must create 18 | /// the parser directly by using the 19 | /// constructor; these classes do not support this interface unless it is manually implemented. 20 | /// 21 | /// 22 | /// When using a version of .Net where static interface methods are not supported (versions prior 23 | /// to .Net 7.0), the will still generate the same method 24 | /// as defined by this interface, just without having it implement the interface. 25 | /// 26 | /// 27 | public interface IParserProvider 28 | where TSelf : class, IParserProvider 29 | { 30 | /// 31 | /// Creates a instance using the specified options. 32 | /// 33 | /// 34 | /// The options that control parsing behavior, or to use the 35 | /// default options. 36 | /// 37 | /// 38 | /// An instance of the class for the type 39 | /// . 40 | /// 41 | /// 42 | /// The cannot use type as the 43 | /// command line arguments type, because it violates one of the rules concerning argument 44 | /// names or positions. Even when the parser was generated using the 45 | /// class, not all those rules can be checked at compile time. 46 | /// 47 | /// 48 | /// 49 | /// This method is typically generated for a class that defines command line arguments by 50 | /// the attribute. 51 | /// 52 | /// 53 | /// 54 | public static abstract CommandLineParser CreateParser(ParseOptions? options = null); 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/MultiValueArgumentInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Ookii.CommandLine; 8 | 9 | /// 10 | /// Provides information that only applies to multi-value and dictionary arguments. 11 | /// 12 | /// 13 | public sealed class MultiValueArgumentInfo 14 | { 15 | /// 16 | /// Creates a new instance of the class. 17 | /// 18 | /// 19 | /// The separator between multiple values in the same token, or if no 20 | /// separator is used. 21 | /// 22 | /// 23 | /// if the argument can consume multiple tokens; otherwise, 24 | /// . 25 | /// 26 | public MultiValueArgumentInfo(string? separator, bool allowWhiteSpaceSeparator) 27 | { 28 | Separator = separator; 29 | AllowWhiteSpaceSeparator = allowWhiteSpaceSeparator; 30 | } 31 | 32 | 33 | /// 34 | /// Gets the separator that can be used to supply multiple values in a single argument token. 35 | /// 36 | /// 37 | /// The separator, or if no separator is used. 38 | /// 39 | /// 40 | public string? Separator { get; } 41 | 42 | /// 43 | /// Gets a value that indicates whether or not the argument can consume multiple following 44 | /// argument tokens. 45 | /// 46 | /// 47 | /// if the argument consume multiple following tokens; otherwise, 48 | /// . 49 | /// 50 | /// 51 | /// 52 | /// A multi-value argument that allows white-space separators is able to consume multiple 53 | /// values from the command line that follow it. All values that follow the name, up until 54 | /// the next argument name, are considered values for this argument. 55 | /// 56 | /// 57 | /// 58 | public bool AllowWhiteSpaceSeparator { get; internal set; } 59 | } 60 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/NameTransform.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates how to transform the argument name, subcommand name, or value description if they are 5 | /// not explicitly specified but automatically derived from the member or type name. 6 | /// 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | public enum NameTransform 13 | { 14 | /// 15 | /// The names are used without modification. 16 | /// 17 | None, 18 | /// 19 | /// The names are transformed to PascalCase. This removes all underscores, and the first 20 | /// character, and every character after an underscore, is changed to uppercase. The case of 21 | /// other characters is not changed. 22 | /// 23 | PascalCase, 24 | /// 25 | /// The names are transformed to camelCase. Similar to , but the 26 | /// first character will not be uppercase. 27 | /// 28 | CamelCase, 29 | /// 30 | /// The names are transformed to dash-case. This removes leading and trailing underscores, 31 | /// changes all characters to lower-case, replaces underscores with a dash, and reduces 32 | /// consecutive underscores to a single dash. A dash is inserted before previously 33 | /// capitalized letters. 34 | /// 35 | DashCase, 36 | /// 37 | /// The names are transformed to snake_case. Similar to , but uses an 38 | /// underscore instead of a dash. 39 | /// 40 | SnakeCase 41 | } 42 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/NativeMethods.txt: -------------------------------------------------------------------------------- 1 | CONSOLE_MODE 2 | STD_HANDLE 3 | GetConsoleMode 4 | GetStdHandle 5 | SetConsoleMode -------------------------------------------------------------------------------- /src/Ookii.CommandLine/PackageReadme.md: -------------------------------------------------------------------------------- 1 | # Ookii.CommandLine 2 | 3 | Ookii.CommandLine is a powerful, flexible and highly customizable command line argument parsing 4 | library for .Net applications. 5 | 6 | - Easily define arguments by creating a class with properties. 7 | - Create applications with multiple subcommands. 8 | - Generate fully customizable usage help. 9 | - Supports PowerShell-like and POSIX-like parsing rules. 10 | - Compatible with trimming and native AOT. 11 | 12 | Two styles of command line parsing rules are supported: the default mode uses rules similar to those 13 | used by PowerShell, and the alternative long/short mode uses a style influenced by POSIX 14 | conventions, where arguments have separate long and short names with different prefixes. Many 15 | aspects of the parsing rules are configurable. 16 | 17 | To determine which arguments are accepted, you create a class, with properties and methods that 18 | define the arguments. Attributes are used to specify names, create required or positional arguments, 19 | and to specify descriptions for use in the generated usage help. 20 | 21 | For example, the following class defines four arguments: a required positional argument, an optional 22 | positional argument, a named-only argument, and a switch argument (sometimes also called a flag): 23 | 24 | ```csharp 25 | [GeneratedParser] 26 | partial class MyArguments 27 | { 28 | [CommandLineArgument(IsPositional = true)] 29 | [Description("A required positional argument.")] 30 | public required string Required { get; set; } 31 | 32 | [CommandLineArgument(IsPositional = true)] 33 | [Description("An optional positional argument.")] 34 | public int Optional { get; set; } = 42; 35 | 36 | [CommandLineArgument] 37 | [Description("An argument that can only be supplied by name.")] 38 | public DateTime Named { get; set; } 39 | 40 | [CommandLineArgument] 41 | [Description("A switch argument, which doesn't require a value.")] 42 | public bool Switch { get; set; } 43 | } 44 | ``` 45 | 46 | Each argument has a different type that determines the kinds of values it can accept. 47 | 48 | > If you are using an older version of .Net where the `required` keyword is not available, you can 49 | > use `[CommandLineArgument(IsRequired = true)]` to create a required argument instead. 50 | 51 | To parse these arguments, all you have to do is add the following line to your `Main` method: 52 | 53 | ```csharp 54 | var arguments = MyArguments.Parse(); 55 | ``` 56 | 57 | In addition, Ookii.CommandLine can be used to create applications that have multiple subcommands, 58 | each with their own arguments. 59 | 60 | For more information, including a tutorial and samples, see the [full documentation on GitHub](https://github.com/SvenGroot/Ookii.CommandLine). 61 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ParseStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates the status of the last call to the 5 | /// method or one of its overloads. 6 | /// 7 | /// 8 | public enum ParseStatus 9 | { 10 | /// 11 | /// The method has not been called yet. 12 | /// 13 | None, 14 | /// 15 | /// The operation successfully parsed all arguments, or was canceled using 16 | /// . Check the 17 | /// property to differentiate between 18 | /// the two. 19 | /// 20 | Success, 21 | /// 22 | /// An error occurred while parsing the arguments. 23 | /// 24 | Error, 25 | /// 26 | /// Parsing was canceled by one of the arguments using 27 | /// or 28 | /// 29 | Canceled 30 | } 31 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ParsingMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates what argument parsing rules should be used to interpret the command line. 5 | /// 6 | /// 7 | /// 8 | /// To set the parsing mode for a , use the 9 | /// property or the property. 10 | /// 11 | /// 12 | /// 13 | public enum ParsingMode 14 | { 15 | /// 16 | /// Use the normal Ookii.CommandLine parsing rules. 17 | /// 18 | Default, 19 | /// 20 | /// Allow arguments to have both long and short names, using the 21 | /// to specify a long name, and the regular 22 | /// to specify a short name. 23 | /// 24 | LongShort 25 | } 26 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/PrefixTerminationMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates the effect of an argument that is just the long argument prefix ("--" by default) 5 | /// by itself, not followed by a name. 6 | /// 7 | /// 8 | /// 9 | public enum PrefixTerminationMode 10 | { 11 | /// 12 | /// There is no special behavior for the argument. 13 | /// 14 | None, 15 | /// 16 | /// The argument terminates the use of named arguments. Any following arguments are interpreted 17 | /// as values for positional arguments, even if they begin with a long or short argument name 18 | /// prefix. 19 | /// 20 | PositionalOnly, 21 | /// 22 | /// The argument cancels parsing, returning an instance of the arguments type and making the 23 | /// values after this argument available in the 24 | /// property. This is identical to how an argument with 25 | /// behaves. 26 | /// 27 | CancelWithSuccess 28 | } 29 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [assembly: CLSCompliant(true)] 4 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/RingBuffer.Async.cs: -------------------------------------------------------------------------------- 1 | // The async methods in this file are used to generate the normal, non-async versions using the 2 | // Convert-SyncMethod.ps1 script. 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Ookii.CommandLine; 10 | 11 | internal partial class RingBuffer 12 | { 13 | public async Task WriteToAsync(TextWriter writer, int length, CancellationToken cancellationToken) 14 | { 15 | if (length > Size) 16 | { 17 | throw new ArgumentOutOfRangeException(nameof(length)); 18 | } 19 | 20 | var remaining = _buffer.Length - _bufferStart; 21 | if (remaining < length) 22 | { 23 | await WriteAsyncHelper(writer, _buffer, _bufferStart, remaining, cancellationToken); 24 | remaining = length - remaining; 25 | await WriteAsyncHelper(writer, _buffer, 0, remaining, cancellationToken); 26 | _bufferStart = remaining; 27 | } 28 | else 29 | { 30 | await WriteAsyncHelper(writer, _buffer, _bufferStart, length, cancellationToken); 31 | _bufferStart += length; 32 | Debug.Assert(_bufferStart <= _buffer.Length); 33 | } 34 | 35 | if (_bufferEnd != null && _bufferStart == _bufferEnd.Value) 36 | { 37 | _bufferEnd = null; 38 | } 39 | } 40 | 41 | private static async Task WriteAsyncHelper(TextWriter writer, char[] buffer, int index, int length, CancellationToken cancellationToken) 42 | { 43 | #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER 44 | await writer.WriteAsync(buffer.AsMemory(index, length), cancellationToken); 45 | #else 46 | await writer.WriteAsync(buffer, index, length); 47 | #endif 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ShortAliasAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine; 4 | 5 | /// 6 | /// Defines an alternative short name for a command line argument. 7 | /// 8 | /// 9 | /// 10 | /// To specify multiple aliases, apply this attribute multiple times. 11 | /// 12 | /// 13 | /// This attribute specifies short name aliases used with . 14 | /// It is ignored if the property is not 15 | /// , or if the argument doesn't have a 16 | /// primary . 17 | /// 18 | /// 19 | /// The short aliases for a command line argument can be used instead of the regular short 20 | /// name to specify the parameter on the command line. 21 | /// 22 | /// 23 | /// All short argument names and short aliases defined by a single arguments type must be 24 | /// unique. 25 | /// 26 | /// 27 | /// By default, the command line usage help generated by 28 | /// includes the aliases. Set the 29 | /// property to to exclude them. 30 | /// 31 | /// 32 | /// 33 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true)] 34 | public sealed class ShortAliasAttribute : Attribute 35 | { 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// The alternative short name for the command line argument. 40 | public ShortAliasAttribute(char alias) 41 | { 42 | Alias = alias; 43 | } 44 | 45 | /// 46 | /// Gets the alternative short name for the command line argument. 47 | /// 48 | /// 49 | /// The alternative short name for the command line argument. 50 | /// 51 | public char Alias { get; } 52 | 53 | 54 | /// 55 | /// Gets or sets a value indicating whether the alias should be hidden from the usage help. 56 | /// 57 | /// 58 | /// if the alias should be hidden from the usage help; otherwise, 59 | /// . The default value is . 60 | /// 61 | public bool IsHidden { get; set; } 62 | } 63 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/StringBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_0 2 | 3 | using System.Xml.Linq; 4 | using System; 5 | using System.Text; 6 | using System.Collections.Generic; 7 | 8 | namespace Ookii.CommandLine; 9 | 10 | static class StringBuilderExtensions 11 | { 12 | // This already exists in all targets except .Net Standard 2.0 13 | public static void AppendJoin(this StringBuilder builder, string separator, IEnumerable values) 14 | { 15 | bool first = true; 16 | foreach (var value in values) 17 | { 18 | if (first) 19 | { 20 | first = false; 21 | } 22 | else 23 | { 24 | builder.Append(separator); 25 | } 26 | 27 | builder.Append(value); 28 | } 29 | } 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/StringComparisonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine; 4 | 5 | internal static class StringComparisonExtensions 6 | { 7 | public static StringComparer GetComparer(this StringComparison comparison) 8 | { 9 | #if NETSTANDARD2_0 10 | return comparison switch 11 | { 12 | StringComparison.CurrentCulture => StringComparer.CurrentCulture, 13 | StringComparison.CurrentCultureIgnoreCase => StringComparer.CurrentCultureIgnoreCase, 14 | StringComparison.InvariantCulture => StringComparer.InvariantCulture, 15 | StringComparison.InvariantCultureIgnoreCase => StringComparer.InvariantCultureIgnoreCase, 16 | StringComparison.Ordinal => StringComparer.Ordinal, 17 | StringComparison.OrdinalIgnoreCase => StringComparer.OrdinalIgnoreCase, 18 | _ => throw new ArgumentException(Properties.Resources.InvalidStringComparison, nameof(comparison)) 19 | }; 20 | #else 21 | return StringComparer.FromComparison(comparison); 22 | #endif 23 | } 24 | 25 | public static bool IsCaseSensitive(this StringComparison comparison) 26 | => comparison is StringComparison.Ordinal or StringComparison.InvariantCulture or StringComparison.CurrentCulture; 27 | } 28 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/StringSegmentType.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | enum StringSegmentType 4 | { 5 | Text, 6 | LineBreak, 7 | Formatting, 8 | PartialLineBreak, 9 | // Must be the last group of values in the enum 10 | PartialFormattingUnknown, 11 | PartialFormattingSimple, 12 | PartialFormattingCsi, 13 | PartialFormattingOsc, 14 | PartialFormattingOscWithEscape, 15 | } 16 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Support/CommandProvider.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Commands; 2 | using System.Collections.Generic; 3 | 4 | namespace Ookii.CommandLine.Support; 5 | 6 | /// 7 | /// A source of commands for the . 8 | /// 9 | /// 10 | /// This class is used by the source generator when using the 11 | /// attribute. It should not normally be used by other code. 12 | /// 13 | /// 14 | public abstract class CommandProvider 15 | { 16 | /// 17 | /// Gets the kind of command provider. 18 | /// 19 | /// 20 | /// One of the values of the enumeration. 21 | /// 22 | public virtual ProviderKind Kind => ProviderKind.Unknown; 23 | 24 | /// 25 | /// Gets all the commands supported by this provider. 26 | /// 27 | /// The that the commands belong to. 28 | /// 29 | /// A list of instances for the commands, in arbitrary order. 30 | /// 31 | public abstract IEnumerable GetCommandsUnsorted(CommandManager manager); 32 | 33 | /// 34 | /// Gets the application description. 35 | /// 36 | /// The application description, or if there is none. 37 | public abstract string? GetApplicationDescription(); 38 | } 39 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Support/GeneratedArgument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Ookii.CommandLine.Support; 5 | 6 | /// 7 | /// Represents information about an argument determined by the source generator. Used for all 8 | /// arguments except dictionary arguments. 9 | /// 10 | /// 11 | /// This class is used by the source generator when using the 12 | /// attribute. It should not normally be used by other code. 13 | /// 14 | /// 15 | /// The element type of the argument, including if it is one. 16 | public class GeneratedArgument : GeneratedArgumentBase 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The argument creation information. 22 | public GeneratedArgument(in ArgumentCreationInfo info) : base(info) 23 | { 24 | Debug.Assert(info.Kind != ArgumentKind.Dictionary); 25 | Debug.Assert(typeof(TElementWithNullable) == info.ElementTypeWithNullable); 26 | } 27 | 28 | private protected override IValueHelper CreateDictionaryValueHelper() => throw new NotImplementedException(); 29 | 30 | private protected override IValueHelper CreateMultiValueHelper() => new MultiValueHelper(); 31 | } 32 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Support/GeneratedArgumentBase.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Conversion; 2 | using Ookii.CommandLine.Validation; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Diagnostics; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Reflection; 9 | 10 | namespace Ookii.CommandLine.Support; 11 | 12 | /// 13 | /// Represents information about an argument determined by the source generator. 14 | /// 15 | /// 16 | /// This class is used by the source generator when using the 17 | /// attribute. It should not normally be used by other code. 18 | /// 19 | /// 20 | public abstract class GeneratedArgumentBase : CommandLineArgument 21 | { 22 | private readonly Action? _setProperty; 23 | private readonly Func? _getProperty; 24 | private readonly Func? _callMethod; 25 | private MemberInfo? _member; 26 | 27 | private protected GeneratedArgumentBase(ArgumentCreationInfo info) : base(new ArgumentInfo(info)) 28 | { 29 | _setProperty = info.SetProperty; 30 | _getProperty = info.GetProperty; 31 | _callMethod = info.CallMethod; 32 | } 33 | 34 | /// 35 | public override MemberInfo? Member => _member ??= (MemberInfo?)Parser.ArgumentsType.GetProperty(MemberName) 36 | ?? Parser.ArgumentsType.GetMethod(MemberName, BindingFlags.Public | BindingFlags.Static); 37 | 38 | /// 39 | protected override bool CanSetProperty => _setProperty != null; 40 | 41 | /// 42 | protected override CancelMode CallMethod(object? value) 43 | { 44 | if (_callMethod == null) 45 | { 46 | throw new InvalidOperationException(Properties.Resources.InvalidMethodAccess); 47 | } 48 | 49 | return _callMethod(value, this.Parser); 50 | } 51 | 52 | /// 53 | protected override object? GetProperty(object target) 54 | { 55 | if (_getProperty == null) 56 | { 57 | throw new InvalidOperationException(Properties.Resources.InvalidPropertyAccess); 58 | } 59 | 60 | return _getProperty(target); 61 | } 62 | 63 | /// 64 | protected override void SetProperty(object target, object? value) 65 | { 66 | if (_setProperty == null) 67 | { 68 | throw new InvalidOperationException(Properties.Resources.InvalidPropertyAccess); 69 | } 70 | 71 | _setProperty(target, value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Support/GeneratedCommandInfoWithCustomParsing.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | 6 | namespace Ookii.CommandLine.Support; 7 | 8 | /// 9 | /// Represents information about a subcommand that uses the 10 | /// interface, determined by the source generator. 11 | /// 12 | /// The command class. 13 | /// 14 | /// This class is used by the source generator when using the 15 | /// attribute. It should not normally be used by other code. 16 | /// 17 | /// 18 | public class GeneratedCommandInfoWithCustomParsing : GeneratedCommandInfo 19 | where T : class, ICommandWithCustomParsing, new() 20 | { 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The command manager. 25 | /// The . 26 | /// The . 27 | /// A collection of values. 28 | /// The type of the parent command. 29 | public GeneratedCommandInfoWithCustomParsing(CommandManager manager, 30 | CommandAttribute attribute, 31 | DescriptionAttribute? descriptionAttribute = null, 32 | IEnumerable? aliasAttributes = null, 33 | Type? parentCommandType = null) 34 | : base(manager, typeof(T), attribute, descriptionAttribute, aliasAttributes, parentCommandType: parentCommandType) 35 | { 36 | } 37 | 38 | /// 39 | public override bool UseCustomArgumentParsing => true; 40 | 41 | /// 42 | public override ICommandWithCustomParsing CreateInstanceWithCustomParsing() => new T(); 43 | } 44 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Support/GeneratedDictionaryArgument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Ookii.CommandLine.Support; 5 | 6 | /// 7 | /// Represents information about a dictionary argument determined by the source generator. 8 | /// 9 | /// 10 | /// This class is used by the source generator when using the 11 | /// attribute. It should not normally be used by other code. 12 | /// 13 | /// 14 | /// The key type of the dictionary. 15 | /// The value type of the dictionary. 16 | public class GeneratedDictionaryArgument : GeneratedArgumentBase 17 | where TKey : notnull 18 | { 19 | /// 20 | /// Initializes a new instance of the 21 | /// class. 22 | /// 23 | /// The argument creation information. 24 | public GeneratedDictionaryArgument(in ArgumentCreationInfo info) : base(info) 25 | { 26 | Debug.Assert(info.Kind == ArgumentKind.Dictionary); 27 | Debug.Assert(typeof(TKey) == info.KeyType); 28 | Debug.Assert(typeof(TValue) == info.ValueType); 29 | } 30 | 31 | private protected override IValueHelper CreateDictionaryValueHelper() 32 | => new DictionaryValueHelper(DictionaryInfo!.AllowDuplicateKeys, AllowNull); 33 | 34 | private protected override IValueHelper CreateMultiValueHelper() => throw new NotImplementedException(); 35 | } 36 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Support/ProviderKind.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine.Support; 2 | 3 | /// 4 | /// Specifies the kind of provider that was the source of the arguments or subcommands. 5 | /// 6 | public enum ProviderKind 7 | { 8 | /// 9 | /// A custom provider that was not part of Ookii.CommandLine. 10 | /// 11 | Unknown, 12 | /// 13 | /// An provider that uses reflection. 14 | /// 15 | Reflection, 16 | /// 17 | /// An provider that uses source generation. These are typically created using the 18 | /// and 19 | /// attributes. 20 | /// 21 | Generated 22 | } 23 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Support/ReflectionCommandProvider.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace Ookii.CommandLine.Support; 10 | 11 | #if NET6_0_OR_GREATER 12 | [RequiresUnreferencedCode("Command information cannot be statically determined using reflection. Consider using the GeneratedParserAttribute and GeneratedCommandManagerAttribute.", Url = CommandLineParser.UnreferencedCodeHelpUrl)] 13 | #endif 14 | #if NET7_0_OR_GREATER 15 | [RequiresDynamicCode("Consider using the GeneratedParserAttribute.")] 16 | #endif 17 | internal class ReflectionCommandProvider : CommandProvider 18 | { 19 | private readonly Assembly? _assembly; 20 | private readonly IEnumerable? _assemblies; 21 | private readonly Assembly _callingAssembly; 22 | 23 | public ReflectionCommandProvider(Assembly assembly, Assembly callingAssembly) 24 | { 25 | _assembly = assembly; 26 | _callingAssembly = callingAssembly; 27 | } 28 | 29 | public ReflectionCommandProvider(IEnumerable assemblies, Assembly callingAssembly) 30 | { 31 | _assemblies = assemblies; 32 | _callingAssembly = callingAssembly; 33 | if (_assemblies.Any(a => a == null)) 34 | { 35 | throw new ArgumentNullException(nameof(assemblies)); 36 | } 37 | } 38 | 39 | public override ProviderKind Kind => ProviderKind.Reflection; 40 | 41 | public override IEnumerable GetCommandsUnsorted(CommandManager manager) 42 | { 43 | { 44 | IEnumerable types; 45 | if (_assembly != null) 46 | { 47 | types = _assembly.GetTypes(); 48 | } 49 | else 50 | { 51 | Debug.Assert(_assemblies != null); 52 | types = _assemblies.SelectMany(a => a.GetTypes()); 53 | } 54 | 55 | return from type in types 56 | where type.Assembly == _callingAssembly || type.IsPublic 57 | let info = CommandInfo.TryCreate(type, manager) 58 | where info != null 59 | select info; 60 | } 61 | } 62 | 63 | public override string? GetApplicationDescription() 64 | => (_assembly ?? _assemblies?.FirstOrDefault())?.GetCustomAttribute()?.Description; 65 | } 66 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Terminal/StandardStream.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine.Terminal; 2 | 3 | /// 4 | /// Represents one of the standard console streams. 5 | /// 6 | public enum StandardStream 7 | { 8 | /// 9 | /// The standard output stream. 10 | /// 11 | Output, 12 | /// 13 | /// The standard input stream. 14 | /// 15 | Input, 16 | /// 17 | /// The standard error stream. 18 | /// 19 | Error 20 | } 21 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Terminal/VirtualTerminalSupport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine.Terminal; 4 | 5 | /// 6 | /// Handles the lifetime of virtual terminal support. 7 | /// 8 | /// 9 | /// On Windows, this restores the terminal mode to its previous value when disposed or 10 | /// destructed. On other platforms, this does nothing. 11 | /// 12 | /// 13 | public sealed class VirtualTerminalSupport : IDisposable 14 | { 15 | private StandardStream? _restoreStream; 16 | 17 | internal VirtualTerminalSupport(bool supported) 18 | { 19 | IsSupported = supported; 20 | GC.SuppressFinalize(this); 21 | } 22 | 23 | internal VirtualTerminalSupport(StandardStream restoreStream) 24 | { 25 | IsSupported = true; 26 | _restoreStream = restoreStream; 27 | } 28 | 29 | /// 30 | /// Cleans up resources for the class. 31 | /// 32 | /// 33 | /// 34 | /// This method will disable VT support on Windows if it was enabled by the call to 35 | /// or 36 | /// that 37 | /// created this instance. 38 | /// 39 | /// 40 | ~VirtualTerminalSupport() 41 | { 42 | ResetConsoleMode(); 43 | } 44 | 45 | /// 46 | /// Gets a value that indicates whether virtual terminal sequences are supported. 47 | /// 48 | /// 49 | /// if virtual terminal sequences are supported; otherwise, 50 | /// . 51 | /// 52 | public bool IsSupported { get; } 53 | 54 | /// 55 | /// Cleans up resources for the class. 56 | /// 57 | /// 58 | /// 59 | /// This method will disable VT support on Windows if it was enabled by the call to 60 | /// or 61 | /// that 62 | /// created this instance. 63 | /// 64 | /// 65 | public void Dispose() 66 | { 67 | ResetConsoleMode(); 68 | GC.SuppressFinalize(this); 69 | } 70 | 71 | private void ResetConsoleMode() 72 | { 73 | if (_restoreStream is StandardStream stream) 74 | { 75 | VirtualTerminal.SetVirtualTerminalSequences(stream, false); 76 | _restoreStream = null; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/TriState.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Represents a value that can be either true, false or automatically determined. 5 | /// 6 | /// 7 | /// 8 | /// This enumeration is equivalent to a value, with an additional option that 9 | /// indicates the value should be automatically determined based on some criteria. 10 | /// 11 | /// 12 | public enum TriState : byte 13 | { 14 | /// 15 | /// The value should be automatically determined to be either or 16 | /// . 17 | /// 18 | Auto, 19 | /// 20 | /// Represents a value. 21 | /// 22 | True, 23 | /// 24 | /// Represents a value. 25 | /// 26 | False, 27 | } 28 | 29 | /// 30 | /// Provides extension methods for the enumeration. 31 | /// 32 | /// 33 | public static class TriStateExtensions 34 | { 35 | /// 36 | /// Converts the value to a value. 37 | /// 38 | /// The value to convert. 39 | /// 40 | /// The value to return if is . 41 | /// 42 | /// 43 | /// if is ; 44 | /// if is ; 45 | /// and if is . 46 | /// 47 | public static bool ToBoolean(this TriState value, bool autoValue) => value switch 48 | { 49 | TriState.True => true, 50 | TriState.False => false, 51 | _ => autoValue, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/UsageFooterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace Ookii.CommandLine; 5 | 6 | /// 7 | /// Gets or sets a footer that will be added to the usage help for an arguments class. 8 | /// 9 | /// 10 | /// 11 | /// The attribute provides text that's written at the top of 12 | /// the usage help. The attribute does the same thing, but for 13 | /// text that's written at the bottom of the usage help. 14 | /// 15 | /// 16 | /// The footer will only be used when the full usage help is shown, using 17 | /// . 18 | /// 19 | /// 20 | /// You can derive from this attribute to use an alternative source for the footer, such as a 21 | /// resource table that can be localized. 22 | /// 23 | /// 24 | /// 25 | [AttributeUsage(AttributeTargets.Class)] 26 | public class UsageFooterAttribute : Attribute 27 | { 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// The footer text. 32 | /// 33 | /// is . 34 | /// 35 | public UsageFooterAttribute(string footer) 36 | { 37 | FooterValue = footer ?? throw new ArgumentNullException(nameof(footer)); 38 | } 39 | 40 | /// 41 | /// Gets the footer text. 42 | /// 43 | /// 44 | /// The footer text. 45 | /// 46 | public virtual string Footer => FooterValue; 47 | 48 | /// 49 | /// Gets the footer text stored in this attribute. 50 | /// 51 | /// 52 | /// The footer text. 53 | /// 54 | /// 55 | /// 56 | /// The base class implementation of the property returns the value of 57 | /// this property. 58 | /// 59 | /// 60 | protected string FooterValue { get; } 61 | } 62 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/UsageHelpRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates if and how usage is shown if an error occurred parsing the command line. 5 | /// 6 | /// 7 | public enum UsageHelpRequest 8 | { 9 | /// 10 | /// Only the usage syntax is shown; the argument descriptions are not. In addition, the 11 | /// message is shown. 12 | /// 13 | SyntaxOnly, 14 | /// 15 | /// Full usage help is shown, including the argument descriptions. 16 | /// 17 | Full, 18 | /// 19 | /// No usage help is shown. Instead, the 20 | /// message is shown. 21 | /// 22 | None 23 | } 24 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Validation/ArgumentValidationWithHelpAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine.Validation; 2 | 3 | /// 4 | /// Base class for argument validators that have usage help. 5 | /// 6 | /// 7 | /// 8 | /// It's not required for argument validators that have help to derive from this class; 9 | /// it's sufficient to derive from the class 10 | /// directly and override the method. 11 | /// This class just adds some common functionality to make it easier. 12 | /// 13 | /// 14 | /// 15 | public abstract class ArgumentValidationWithHelpAttribute : ArgumentValidationAttribute 16 | { 17 | /// 18 | /// Gets or sets a value that indicates whether this validator's help should be included 19 | /// in the argument's description. 20 | /// 21 | /// 22 | /// to include it in the description; otherwise, . 23 | /// The default value is . 24 | /// 25 | /// 26 | /// 27 | /// This has no effect if the 28 | /// property is . 29 | /// 30 | /// 31 | /// The help text is the value returned by . 32 | /// 33 | /// 34 | public bool IncludeInUsageHelp { get; set; } = true; 35 | 36 | /// 37 | /// Gets the usage help message for this validator. 38 | /// 39 | /// The argument that the validator is for. 40 | /// 41 | /// The usage help message, or if the 42 | /// property is . 43 | /// 44 | /// 45 | /// 46 | /// This function is only called if the 47 | /// property is . 48 | /// 49 | /// 50 | 51 | public override string? GetUsageHelp(CommandLineArgument argument) 52 | => IncludeInUsageHelp ? GetUsageHelpCore(argument) : null; 53 | 54 | /// 55 | /// Gets the usage help message for this validator. 56 | /// 57 | /// The argument that the validator is for. 58 | /// 59 | /// The usage help message. 60 | /// 61 | protected abstract string GetUsageHelpCore(CommandLineArgument argument); 62 | } 63 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Validation/ValidateNotEmptyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine.Validation; 4 | 5 | /// 6 | /// Validates that the value of an argument is not an empty string. 7 | /// 8 | /// 9 | /// 10 | /// This validator uses the raw string value provided by the user, before type conversion takes 11 | /// place. 12 | /// 13 | /// 14 | /// If the argument is optional, validation is only performed if the argument is specified, 15 | /// so the value may still be an empty string if the argument is not supplied, if that 16 | /// is the default value. 17 | /// 18 | /// 19 | /// 20 | public class ValidateNotEmptyAttribute : ArgumentValidationWithHelpAttribute 21 | { 22 | /// 23 | /// Determines if the argument is not an empty string. 24 | /// 25 | /// The argument being validated. 26 | /// 27 | /// The raw string argument value provided by the user on the command line. 28 | /// 29 | /// 30 | /// if the value is valid; otherwise, . 31 | /// 32 | public override bool IsValidPreConversion(CommandLineArgument argument, ReadOnlyMemory value) 33 | { 34 | return !value.IsEmpty; 35 | } 36 | 37 | /// 38 | /// Gets the error message to display if validation failed. 39 | /// 40 | /// The argument that was validated. 41 | /// Not used. 42 | /// The error message. 43 | public override string GetErrorMessage(CommandLineArgument argument, object? value) 44 | { 45 | if (value == null) 46 | { 47 | return argument.Parser.StringProvider.NullArgumentValue(argument.ArgumentName); 48 | } 49 | else 50 | { 51 | return argument.Parser.StringProvider.ValidateNotEmptyFailed(argument.ArgumentName); 52 | } 53 | } 54 | 55 | /// 56 | protected override string GetUsageHelpCore(CommandLineArgument argument) 57 | => argument.Parser.StringProvider.ValidateNotEmptyUsageHelp(); 58 | } 59 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Validation/ValidateNotNullAttribute.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Conversion; 2 | using System; 3 | using System.ComponentModel; 4 | 5 | namespace Ookii.CommandLine.Validation; 6 | 7 | /// 8 | /// Validates that the value of an argument is not . 9 | /// 10 | /// 11 | /// 12 | /// An argument's value can only be if its 13 | /// returns from the 14 | /// method. For example, the can return . 15 | /// 16 | /// 17 | /// It is not necessary to use this attribute on required arguments with types that can't be 18 | /// , such as value types (except ), and if 19 | /// using .Net 6.0 or later, non-nullable reference types. The 20 | /// already ensures it will not assign to these arguments. 21 | /// 22 | /// 23 | /// If the argument is optional, validation is only performed if the argument is specified, 24 | /// so the value may still be if the argument is not supplied, if that 25 | /// is the default value. 26 | /// 27 | /// 28 | /// This validator does not add any help text to the argument description. 29 | /// 30 | /// 31 | /// 32 | public class ValidateNotNullAttribute : ArgumentValidationAttribute 33 | { 34 | /// 35 | /// Determines if the argument's value is not . 36 | /// 37 | /// The argument being validated. 38 | /// 39 | /// The argument value. If not , this must be an instance of 40 | /// . 41 | /// 42 | /// 43 | /// if the value is valid; otherwise, . 44 | /// 45 | public override bool IsValidPostConversion(CommandLineArgument argument, object? value) 46 | => value != null; 47 | 48 | /// 49 | /// Gets the error message to display if validation failed. 50 | /// 51 | /// The argument that was validated. 52 | /// Not used. 53 | /// The error message. 54 | /// 55 | /// 56 | /// Use a custom class that overrides the 57 | /// method 58 | /// to customize this message. 59 | /// 60 | /// 61 | public override string GetErrorMessage(CommandLineArgument argument, object? value) 62 | => argument.Parser.StringProvider.NullArgumentValue(argument.ArgumentName); 63 | } 64 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/Validation/ValidateNotWhiteSpaceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ookii.CommandLine.Validation; 4 | 5 | 6 | /// 7 | /// Validates that the value of an argument is not an empty string, or a string containing only 8 | /// white-space characters. 9 | /// 10 | /// 11 | /// 12 | /// This validator uses the raw string value provided by the user, before type conversion takes 13 | /// place. 14 | /// 15 | /// 16 | /// If the argument is optional, validation is only performed if the argument is specified, 17 | /// so the value may still be an empty or white-space-only string if the argument is not supplied, 18 | /// if that is the default value. 19 | /// 20 | /// 21 | /// 22 | public class ValidateNotWhiteSpaceAttribute : ArgumentValidationWithHelpAttribute 23 | { 24 | /// 25 | /// Determines if the argument's value is not an empty string, or contains only white-space 26 | /// characters. 27 | /// 28 | /// The argument being validated. 29 | /// 30 | /// The raw string argument value. 31 | /// 32 | /// 33 | /// if the value is valid; otherwise, . 34 | /// 35 | public override bool IsValidPreConversion(CommandLineArgument argument, ReadOnlyMemory value) 36 | => !value.Span.IsWhiteSpace(); 37 | 38 | /// 39 | /// Gets the error message to display if validation failed. 40 | /// 41 | /// The argument that was validated. 42 | /// Not used. 43 | /// The error message. 44 | /// 45 | /// 46 | /// Use a custom class that overrides the 47 | /// 48 | /// method to customize this message. 49 | /// 50 | /// 51 | public override string GetErrorMessage(CommandLineArgument argument, object? value) 52 | { 53 | if (value == null) 54 | { 55 | return argument.Parser.StringProvider.NullArgumentValue(argument.ArgumentName); 56 | } 57 | else 58 | { 59 | return argument.Parser.StringProvider.ValidateNotWhiteSpaceFailed(argument.ArgumentName); 60 | } 61 | } 62 | 63 | /// 64 | /// 65 | /// 66 | /// Use a custom class that overrides the 67 | /// method 68 | /// to customize this message. 69 | /// 70 | /// 71 | protected override string GetUsageHelpCore(CommandLineArgument argument) 72 | => argument.Parser.StringProvider.ValidateNotWhiteSpaceUsageHelp(); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/WrappingMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ookii.CommandLine; 2 | 3 | /// 4 | /// Indicates how the class will wrap text at the maximum 5 | /// line length. 6 | /// 7 | /// 8 | public enum WrappingMode 9 | { 10 | /// 11 | /// The text will not be wrapped at the maximum line length. 12 | /// 13 | Disabled, 14 | /// 15 | /// The text will be white-space wrapped at the maximum line length, and if there is no 16 | /// suitable white-space location to wrap the text, it will be wrapped at the line length. 17 | /// 18 | Enabled, 19 | /// 20 | /// The text will be white-space wrapped at the maximum line length. If there is no suitable 21 | /// white-space location to wrap the text, the line will not be wrapped. 22 | /// 23 | EnabledNoForce 24 | } 25 | -------------------------------------------------------------------------------- /src/Ookii.CommandLine/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/src/Ookii.CommandLine/icon.png -------------------------------------------------------------------------------- /src/Ookii.CommandLine/ookii.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SvenGroot/Ookii.CommandLine/c40e39b380cf4d283f007643656203d9d953ee46/src/Ookii.CommandLine/ookii.snk -------------------------------------------------------------------------------- /src/Samples/ArgumentDependencies/ArgumentDependencies.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Command line parsing sample for Ookii.CommandLine, using argument dependencies. 9 | Copyright (c) Sven Groot (Ookii.org) 10 | This is sample code, so you can use it freely. 11 | 12 | 13 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Samples/ArgumentDependencies/Program.cs: -------------------------------------------------------------------------------- 1 | using ArgumentDependencies; 2 | using Ookii.CommandLine; 3 | 4 | var arguments = ProgramArguments.Parse(); 5 | if (arguments == null) 6 | { 7 | return 1; 8 | } 9 | 10 | using var writer = LineWrappingTextWriter.ForConsoleOut(); 11 | if (arguments.Path != null) 12 | { 13 | writer.WriteLine($"Path: {arguments.Path.FullName}"); 14 | } 15 | else 16 | { 17 | writer.WriteLine($"IP address: {arguments.Ip}"); 18 | writer.WriteLine($"Port: {arguments.Port}"); 19 | } 20 | 21 | return 0; 22 | -------------------------------------------------------------------------------- /src/Samples/ArgumentDependencies/ProgramArguments.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Validation; 3 | using System.ComponentModel; 4 | using System.Net; 5 | 6 | namespace ArgumentDependencies; 7 | 8 | // This sample shows how you can have arguments that require or prohibit usage combined with 9 | // other aruments. In this sample, "-Path" and "-Ip" are mutually exclusive, and "-Port" can 10 | // only be used in combination with "-Ip". 11 | // 12 | // Because "-Path" and "-Ip" can't be used together, we can't make either of them required. 13 | // Doing so would make it impossible to use the other argument. Instead, we use the RequiresAny 14 | // validator on the class to specify that a valid invocation requires either "-Path" or "-Ip". 15 | // 16 | // This sample uses nameof() to refer to the arguments. This is a good idea because there's 17 | // compile-time checks if you change a name, but take note: these attributes all require the 18 | // argument name, *not* the member name, so nameof() only works if they're the same. 19 | // 20 | // If you use a NameTransform that changes the argument names, or use any explicit argument 21 | // names, you CANNOT use nameof()! 22 | [GeneratedParser] 23 | [ApplicationFriendlyName("Ookii.CommandLine Dependency Sample")] 24 | [Description("Sample command line application with argument dependencies. The application parses the command line and prints the results, but otherwise does nothing and none of the arguments are actually used for anything.")] 25 | [RequiresAny(nameof(Path), nameof(Ip))] 26 | partial class ProgramArguments 27 | { 28 | [CommandLineArgument(IsPositional = true)] 29 | [Description("The path to use.")] 30 | public FileInfo? Path { get; set; } 31 | 32 | // This argument uses the ProhibitsAttribute to indicate it's mutually exclusive with "-Path". 33 | [CommandLineArgument] 34 | [Description("The IP address to connect to.")] 35 | [Prohibits(nameof(Path))] 36 | public IPAddress? Ip { get; set; } 37 | 38 | // This argument uses the RequiresAttribute to indicate it can only be used if "-Ip" is also 39 | // specified. 40 | [CommandLineArgument] 41 | [Description("The port to connect to.")] 42 | [Requires(nameof(Ip))] 43 | public int Port { get; set; } = 80; 44 | } 45 | -------------------------------------------------------------------------------- /src/Samples/ArgumentDependencies/README.md: -------------------------------------------------------------------------------- 1 | # Argument dependencies sample 2 | 3 | This sample shows how to use the [argument dependency validators](../../../docs/Validation.md#argument-dependencies-and-restrictions). 4 | These validators let you specify that certain arguments must or cannot be used together. It also 5 | makes it possible to specify that the user must use one of a set of arguments, something that can't 6 | be expressed with regular required arguments. 7 | 8 | The validators in question are the [`RequiresAttribute`][], the [`ProhibitsAttribute`][], and the 9 | [`RequiresAnyAttribute`][]. You can see them in action in 10 | [ProgramArguments.cs](ProgramArguments.cs). 11 | 12 | This is the usage help output for this sample: 13 | 14 | ```text 15 | Sample command line application with argument dependencies. The application parses the command line 16 | and prints the results, but otherwise does nothing and none of the arguments are actually used for 17 | anything. 18 | 19 | Usage: ArgumentDependencies [[-Path] ] [-Help] [-Ip ] [-Port ] 20 | [-Version] 21 | 22 | You must use at least one of: -Path, -Ip. 23 | 24 | -Path 25 | The path to use. 26 | 27 | -Help [] (-?, -h) 28 | Displays this help message. 29 | 30 | -Ip 31 | The IP address to connect to. Cannot be used with: -Path. 32 | 33 | -Port 34 | The port to connect to. Must be used with: -Ip. Default value: 80. 35 | 36 | -Version [] 37 | Displays version information. 38 | ``` 39 | 40 | The validators add their own help messages to the usage help. [`RequiresAnyAttribute`][] does so 41 | before the command list, and the [`RequiresAttribute`][] and [`ProhibitsAttribute`][] added text to 42 | the descriptions of the arguments they were applied to. 43 | 44 | This is, as always fully customizable. You can disable automatic validator help entirely with the 45 | [`UsageWriter.IncludeValidatorsInDescription`][] property (note: this also applies to regular 46 | validators like [`ValidateRangeAttribute`][]), and all the included validators can be included on a 47 | case-by-case basis with the [`IncludeInUsageHelp`][IncludeInUsageHelp_0] property on each validator 48 | attribute. 49 | 50 | [`ProhibitsAttribute`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_Validation_ProhibitsAttribute.htm 51 | [`RequiresAnyAttribute`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_Validation_RequiresAnyAttribute.htm 52 | [`RequiresAttribute`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_Validation_RequiresAttribute.htm 53 | [`UsageWriter.IncludeValidatorsInDescription`]: https://www.ookii.org/docs/commandline-5.0/html/P_Ookii_CommandLine_UsageWriter_IncludeValidatorsInDescription.htm 54 | [`ValidateRangeAttribute`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_Validation_ValidateRangeAttribute.htm 55 | [IncludeInUsageHelp_0]: https://www.ookii.org/docs/commandline-5.0/html/P_Ookii_CommandLine_Validation_ArgumentValidationWithHelpAttribute_IncludeInUsageHelp.htm 56 | -------------------------------------------------------------------------------- /src/Samples/Categories/ArgumentCategory.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Categories; 4 | 5 | // You can use any enumeration to define argument categories. The order of the enumeration defines 6 | // the order in which the categories will be displayed in the usage help. 7 | // 8 | // Use the DescriptionAttribute to specify the text that will be displayed in the header for each 9 | // category. If no DescriptionAttribute is present, the name of the enumeration member is used. 10 | // 11 | // You can use any custom enumeration to define categories. The only restriction is that all 12 | // arguments in a class must use the same enumeration type. 13 | enum ArgumentCategory 14 | { 15 | [Description("Installation options")] 16 | Install, 17 | [Description("User account options")] 18 | UserAccounts, 19 | [Description("Domain options")] 20 | Domain, 21 | [Description("Other options")] 22 | Other, 23 | } 24 | -------------------------------------------------------------------------------- /src/Samples/Categories/Categories.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Samples/Categories/DomainUser.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | 3 | namespace Categories; 4 | 5 | // This custom type has a value description, which will be shown in the usage help. There is no 6 | // need to do this for every argument if it's specified on the type. 7 | [ValueDescription("[Domain\\]User")] 8 | class DomainUser 9 | { 10 | public DomainUser(string? domain, string userName) 11 | { 12 | ArgumentNullException.ThrowIfNull(userName); 13 | Domain = domain; 14 | UserName = userName; 15 | } 16 | 17 | public DomainUser(string userName) 18 | : this(null, userName) 19 | { 20 | } 21 | 22 | public string? Domain { get; } 23 | 24 | public string UserName { get; } 25 | 26 | public override string ToString() 27 | => Domain == null ? UserName : $"{Domain}\\{UserName}"; 28 | 29 | // The CommandLineParser will use this method to convert a string to a DomainUser instance. 30 | // Using a method with ReadOnlySpan is only possible when using the 31 | // GeneratedParserAttribute. 32 | public static DomainUser Parse(ReadOnlySpan value) 33 | { 34 | var index = value.IndexOf('\\'); 35 | if (index == -1) 36 | { 37 | return new(value.ToString()); 38 | } 39 | else 40 | { 41 | return new(value[0..index].ToString(), value[(index+1)..].ToString()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Samples/Categories/InstallMethod.cs: -------------------------------------------------------------------------------- 1 | namespace Categories; 2 | 3 | enum InstallMethod 4 | { 5 | PreInstalled, 6 | ExistingPartition, 7 | CleanEfi, 8 | CleanBios, 9 | Manual 10 | } 11 | -------------------------------------------------------------------------------- /src/Samples/Categories/Program.cs: -------------------------------------------------------------------------------- 1 | using Categories; 2 | using Ookii.CommandLine; 3 | 4 | var options = new ParseOptions() 5 | { 6 | UsageWriter = new UsageWriter() 7 | { 8 | // Because this application has so many arguments, we'll use abbreviated syntax to make the 9 | // usage help look cleaner. 10 | UseAbbreviatedSyntax = true, 11 | }, 12 | // Add a default value description for integer arguments. 13 | DefaultValueDescriptions = new Dictionary() 14 | { 15 | { typeof(int), "Number" } 16 | } 17 | }; 18 | 19 | var arguments = Arguments.Parse(options); 20 | if (arguments == null) 21 | { 22 | return 1; 23 | } 24 | 25 | Console.WriteLine($"This is a sample of how to use categories in the usage help. Please run '{CommandLineParser.GetExecutableName()} -Help' to show the help."); 26 | return 0; 27 | -------------------------------------------------------------------------------- /src/Samples/CustomUsage/CustomStringProvider.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Validation; 3 | 4 | namespace CustomUsage; 5 | 6 | // A custom string provider can be used to customize many of the strings used by Ookii.CommandLine, 7 | // including error messages and automatic argument names. Here, it is used to customize the help 8 | // argument short name and the usage help for the ValidateRangeAttribute. 9 | internal class CustomStringProvider : LocalizedStringProvider 10 | { 11 | // By overriding this, the "--help" argument no longer uses "-?" as its short name. Normally, it 12 | // would also have an alias "-h", but since the short name is already "-h" the alias is no 13 | // longer used. 14 | public override char AutomaticHelpShortName() => 'h'; 15 | 16 | // Customize the help for the ValidateRangeAttribute. 17 | public override string ValidateRangeUsageHelp(ValidateRangeAttribute attribute) 18 | { 19 | // Minimum and maximum are both optional (though one of them must be set), so take that 20 | // into account (even though this sample doesn't use that). 21 | if (attribute.Minimum == null) 22 | { 23 | return $"[max: {attribute.Maximum}"; 24 | } 25 | else if (attribute.Maximum == null) 26 | { 27 | return $"[min: {attribute.Minimum}"; 28 | } 29 | 30 | return $"[range: {attribute.Minimum}-{attribute.Maximum}]"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Samples/CustomUsage/CustomUsage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Command line parsing sample for Ookii.CommandLine, using customized usage help. 9 | Copyright (c) Sven Groot (Ookii.org) 10 | This is sample code, so you can use it freely. 11 | 12 | 13 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Samples/CustomUsage/Program.cs: -------------------------------------------------------------------------------- 1 | using CustomUsage; 2 | using Ookii.CommandLine; 3 | 4 | // Not all options can be set with the ParseOptionsAttribute. 5 | var options = new ParseOptions() 6 | { 7 | // Set the value description of all int arguments to "number", instead of doing it 8 | // separately on each argument. 9 | DefaultValueDescriptions = new Dictionary() 10 | { 11 | { typeof(int), "number" } 12 | }, 13 | // Use our own string provider and usage writer for the custom usage strings. 14 | StringProvider = new CustomStringProvider(), 15 | UsageWriter = new CustomUsageWriter(), 16 | }; 17 | 18 | var arguments = ProgramArguments.Parse(options); 19 | 20 | // No need to do anything when the value is null; Parse() already printed errors and 21 | // usage to the console. We return a non-zero value to indicate failure. 22 | if (arguments == null) 23 | { 24 | return 1; 25 | } 26 | 27 | // We use the LineWrappingTextWriter to neatly wrap console output. 28 | using var writer = LineWrappingTextWriter.ForConsoleOut(); 29 | 30 | // Print the values of the arguments. 31 | writer.WriteLine("The following argument values were provided:"); 32 | writer.WriteLine($"Source: {arguments.Source}"); 33 | writer.WriteLine($"Destination: {arguments.Destination}"); 34 | writer.WriteLine($"OperationIndex: {arguments.OperationIndex}"); 35 | writer.WriteLine($"Date: {arguments.Date?.ToString() ?? "(null)"}"); 36 | writer.WriteLine($"Count: {arguments.Count}"); 37 | writer.WriteLine($"Verbose: {arguments.Verbose}"); 38 | writer.WriteLine($"Process: {arguments.Process}"); 39 | var values = arguments.Values == null ? "(null)" : "{ " + string.Join(", ", arguments.Values) + " }"; 40 | writer.WriteLine($"Values: {values}"); 41 | writer.WriteLine($"Day: {arguments.Day?.ToString() ?? "(null)"}"); 42 | 43 | return 0; 44 | -------------------------------------------------------------------------------- /src/Samples/CustomUsage/ProgramArguments.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Validation; 3 | using System.ComponentModel; 4 | 5 | namespace CustomUsage; 6 | 7 | // This class defines the arguments for the sample. It uses the same arguments as the LongShort 8 | // sample, so see that sample for more detailed descriptions. 9 | // 10 | // This sample sets the mode, case sensitivity and name transform to use POSIX conventions. 11 | // 12 | // See the Parse() method below to see how the usage customization is applied. 13 | [GeneratedParser] 14 | [ApplicationFriendlyName("Ookii.CommandLine Long/Short Mode Sample")] 15 | [Description("Sample command line application with highly customized usage help. The application parses the command line and prints the results, but otherwise does nothing and none of the arguments are actually used for anything.")] 16 | [ParseOptions(IsPosix = true, DuplicateArguments = ErrorMode.Warning)] 17 | partial class ProgramArguments 18 | { 19 | [CommandLineArgument(IsPositional = true, IsShort = true)] 20 | [Description("The source data.")] 21 | public required string Source { get; set; } 22 | 23 | [CommandLineArgument(IsPositional = true, IsShort = true)] 24 | [Description("The destination data.")] 25 | public required string Destination { get; set; } 26 | 27 | [CommandLineArgument(IsPositional = true)] 28 | [Description("The operation's index.")] 29 | public int OperationIndex { get; set; } = 1; 30 | 31 | [CommandLineArgument(ShortName = 'D')] 32 | [Description("Provides a date to the application.")] 33 | public DateTime? Date { get; set; } 34 | 35 | [CommandLineArgument] 36 | [Description("This is an argument using an enumeration type.")] 37 | [ValidateEnumValue] 38 | public DayOfWeek? Day { get; set; } 39 | 40 | [CommandLineArgument(IsShort = true)] 41 | [Description("Provides the count for something to the application.")] 42 | [ValidateRange(0, 100)] 43 | public int Count { get; set; } 44 | 45 | [CommandLineArgument(IsShort = true)] 46 | [Description("Print verbose information; this is an example of a switch argument.")] 47 | public bool Verbose { get; set; } 48 | 49 | [CommandLineArgument(IsShort = true)] 50 | [Description("Does the processing.")] 51 | public bool Process { get; set; } 52 | 53 | [CommandLineArgument("value")] 54 | [Description("This is an example of a multi-value argument, which can be repeated multiple times to set more than one value.")] 55 | [MultiValueSeparator] 56 | public string[]? Values { get; set; } 57 | } 58 | -------------------------------------------------------------------------------- /src/Samples/CustomUsage/README.md: -------------------------------------------------------------------------------- 1 | # Custom usage sample 2 | 3 | This sample shows the flexibility of Ookii.CommandLine's usage help generation. It uses a custom 4 | [`UsageWriter`][], along with a custom [`LocalizedStringProvider`][], to completely transform the way 5 | the usage help looks. 6 | 7 | This sample also uses long/short parsing mode, but everything in it is applicable to default mode as 8 | well. 9 | 10 | It uses the same arguments as the [long/short mode sample](../LongShort), so see that for more 11 | details about each argument. 12 | 13 | The usage help for this sample looks very different: 14 | 15 | ```text 16 | DESCRIPTION: 17 | Sample command line application with highly customized usage help. The application parses the 18 | command line and prints the results, but otherwise does nothing and none of the arguments are 19 | actually used for anything. 20 | 21 | USAGE: 22 | CustomUsage [--source] [--destination] [arguments] 23 | 24 | OPTIONS: 25 | -c|--count Provides the count for something to the application. [range: 0-100] 26 | -d|--destination The destination data. 27 | -D|--date Provides a date to the application. 28 | --day This is an argument using an enumeration type. Possible values: 29 | Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday. 30 | -h|--help Displays this help message. 31 | --operation-index The operation's index. [default: 1] 32 | -p|--process Does the processing. 33 | -s|--source The source data. 34 | -v|--verbose Print verbose information; this is an example of a switch argument. 35 | --value This is an example of a multi-value argument, which can be repeated 36 | multiple times to set more than one value. 37 | --version Displays version information. 38 | ``` 39 | 40 | Customizing the usage help like this is not difficult, thanks to the [`UsageWriter`][] class. The 41 | sample [derives a class from it](CustomUsageWriter.cs), and overrides several methods to customize 42 | the format. This allows it to change the parts it wants, and reuse the code for the parts that are 43 | not different. 44 | 45 | The sample also customizes the colors of the output, as shown in the below screenshot: 46 | 47 | ![Custom usage colors](../../../docs/images/custom_usage.png) 48 | 49 | If you compare this with the usage output of the [parser sample](../Parser), which uses the default 50 | output format, you can see just how much you can change by simply overriding some methods on the 51 | [`UsageWriter`][] class. 52 | 53 | [`LocalizedStringProvider`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_LocalizedStringProvider.htm 54 | [`UsageWriter`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_UsageWriter.htm 55 | -------------------------------------------------------------------------------- /src/Samples/LongShort/LongShort.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Command line parsing sample for Ookii.CommandLine, using long/short parsing mode. 9 | Copyright (c) Sven Groot (Ookii.org) 10 | This is sample code, so you can use it freely. 11 | 12 | 13 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Samples/LongShort/Program.cs: -------------------------------------------------------------------------------- 1 | using LongShort; 2 | using Ookii.CommandLine; 3 | 4 | // Not all options can be set with the ParseOptionsAttribute. 5 | var options = new ParseOptions() 6 | { 7 | // Set the value description of all int arguments to "number", instead of doing it 8 | // separately on each argument. 9 | DefaultValueDescriptions = new Dictionary() 10 | { 11 | { typeof(int), "number" } 12 | }, 13 | }; 14 | 15 | // Use the generated Parse() method. 16 | var arguments = ProgramArguments.Parse(options); 17 | 18 | // No need to do anything when the value is null; Parse() already printed errors and 19 | // usage to the console. We return a non-zero value to indicate failure. 20 | if (arguments == null) 21 | { 22 | return 1; 23 | } 24 | 25 | // We use the LineWrappingTextWriter to neatly wrap console output. 26 | using var writer = LineWrappingTextWriter.ForConsoleOut(); 27 | 28 | // Print the values of the arguments. 29 | writer.WriteLine("The following argument values were provided:"); 30 | writer.WriteLine($"Source: {arguments.Source}"); 31 | writer.WriteLine($"Destination: {arguments.Destination}"); 32 | writer.WriteLine($"OperationIndex: {arguments.OperationIndex}"); 33 | writer.WriteLine($"Date: {arguments.Date?.ToString() ?? "(null)"}"); 34 | writer.WriteLine($"Count: {arguments.Count}"); 35 | writer.WriteLine($"Verbose: {arguments.Verbose}"); 36 | writer.WriteLine($"Process: {arguments.Process}"); 37 | var values = arguments.Values == null ? "(null)" : "{ " + string.Join(", ", arguments.Values) + " }"; 38 | writer.WriteLine($"Values: {values}"); 39 | writer.WriteLine($"Day: {arguments.Day?.ToString() ?? "(null)"}"); 40 | 41 | return 0; 42 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/BaseCommand.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Commands; 3 | using Ookii.CommandLine.Terminal; 4 | using System.ComponentModel; 5 | 6 | namespace NestedCommands; 7 | 8 | // This is a base class that adds am argument and some functionality that is common to all the 9 | // commands in this application. 10 | // It is not exposed as a command itself because it lacks the [Command] attribute, and is abstract. 11 | internal abstract class BaseCommand : AsyncCommandBase 12 | { 13 | // The path argument can be used by any command that inherits from this class. 14 | [CommandLineArgument] 15 | [Description("The json file holding the data.")] 16 | public string Path { get; set; } = "data.json"; 17 | 18 | // Implement the task's RunAsync method to load the database and handle some errors. 19 | public override async Task RunAsync(CancellationToken cancellationToken) 20 | { 21 | try 22 | { 23 | var database = await Database.Load(Path, cancellationToken); 24 | return await RunAsync(database, cancellationToken); 25 | } 26 | catch (IOException ex) 27 | { 28 | VirtualTerminal.WriteLineErrorFormatted(ex.Message); 29 | return (int)ExitCode.IOError; 30 | } 31 | catch (UnauthorizedAccessException ex) 32 | { 33 | VirtualTerminal.WriteLineErrorFormatted(ex.Message); 34 | return (int)ExitCode.IOError; 35 | } 36 | } 37 | 38 | // Derived classes will implement this instead of the normal RunAsync. 39 | protected abstract Task RunAsync(Database db, CancellationToken cancellationToken); 40 | } 41 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/CourseCommands.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Commands; 3 | using Ookii.CommandLine.Validation; 4 | using System.ComponentModel; 5 | 6 | namespace NestedCommands; 7 | 8 | // This is the top-level "course" command. It has no functionality since everything is done in the 9 | // ParentCommand class. 10 | [Command("course")] 11 | [Description("Add or remove a course.")] 12 | internal class CourseCommand : ParentCommand 13 | { 14 | } 15 | 16 | // Command to add courses. Since it inherits from BaseCommand, it has a Path argument in addition 17 | // to the arguments created here. 18 | [GeneratedParser] 19 | [Command("add")] 20 | [ParentCommand(typeof(CourseCommand))] 21 | [Description("Adds a course to the database.")] 22 | internal partial class AddCourseCommand : BaseCommand 23 | { 24 | [CommandLineArgument(IsPositional = true)] 25 | [Description("The name of the course.")] 26 | [ValidateNotWhiteSpace] 27 | public required string Name { get; set; } 28 | 29 | [CommandLineArgument(IsPositional = true)] 30 | [Description("The name of the teacher of the course.")] 31 | [ValidateNotWhiteSpace] 32 | public required string Teacher { get; set; } 33 | 34 | protected override async Task RunAsync(Database db, CancellationToken cancellationToken) 35 | { 36 | int id = db.Courses.Any() ? db.Courses.Keys.Max() + 1 : 1; 37 | db.Courses.Add(id, new Course(Name, Teacher)); 38 | await db.Save(Path, cancellationToken); 39 | Console.WriteLine($"Added a course with ID {id}."); 40 | return (int)ExitCode.Success; 41 | } 42 | } 43 | 44 | // Command to remove courses. Since it inherits from BaseCommand, it has a Path argument in addition 45 | // to the arguments created here. 46 | [GeneratedParser] 47 | [Command("remove")] 48 | [ParentCommand(typeof(CourseCommand))] 49 | [Description("Removes a course from the database.")] 50 | internal partial class RemoveCourseCommand : BaseCommand 51 | { 52 | 53 | [CommandLineArgument(IsPositional = true)] 54 | [Description("The ID of the course to remove.")] 55 | public required int Id { get; set; } 56 | 57 | protected override async Task RunAsync(Database db, CancellationToken cancellationToken) 58 | { 59 | if (db.Students.Any(s => s.Value.Courses.Any(c => c.CourseId == Id))) 60 | { 61 | Console.WriteLine("Can't remove a course referenced by a student."); 62 | return (int)ExitCode.IdError; 63 | } 64 | 65 | if (!db.Courses.Remove(Id)) 66 | { 67 | Console.WriteLine("No such course"); 68 | return (int)ExitCode.IdError; 69 | } 70 | 71 | await db.Save(Path, cancellationToken); 72 | Console.WriteLine("Course removed."); 73 | return (int)ExitCode.Success; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/ExitCode.cs: -------------------------------------------------------------------------------- 1 | namespace NestedCommands; 2 | 3 | // Exit code constants used by this sample. 4 | internal enum ExitCode 5 | { 6 | Success, 7 | CreateCommandFailure, 8 | IOError, 9 | IdError, 10 | } 11 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/GeneratedManager.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Commands; 2 | 3 | namespace NestedCommands; 4 | 5 | // Use source generation to locate commands in this assembly. 6 | [GeneratedCommandManager] 7 | internal partial class GeneratedManager 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/ListCommand.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Commands; 3 | using System.ComponentModel; 4 | 5 | namespace NestedCommands; 6 | 7 | // A top-level command that lists all the values in the database. Since it inherits from 8 | // BaseCommand, it has a Path argument even though no arguments are defined here 9 | [GeneratedParser] 10 | [Command("list")] 11 | [Description("Lists all students and courses.")] 12 | internal partial class ListCommand : BaseCommand 13 | { 14 | protected override Task RunAsync(Database db, CancellationToken cancellationToken) 15 | { 16 | using var writer = LineWrappingTextWriter.ForConsoleOut(); 17 | writer.WriteLine("Students:"); 18 | foreach (var (id, student) in db.Students) 19 | { 20 | writer.WriteLine($"{id}: {student.LastName}, {student.FirstName}; major: {student.Major}"); 21 | if (student.Courses.Count > 0) 22 | { 23 | writer.Indent = 4; 24 | writer.WriteLine(" Courses:"); 25 | foreach (var course in student.Courses) 26 | { 27 | string name = db.Courses.TryGetValue(course.CourseId, out var realCourse) 28 | ? realCourse.Name 29 | : $"Unknown ID {course.CourseId}"; 30 | 31 | writer.WriteLine($"{name}: grade {course.Grade}"); 32 | } 33 | 34 | writer.Indent = 0; 35 | writer.ResetIndent(); 36 | } 37 | } 38 | 39 | writer.WriteLine(); 40 | writer.WriteLine("Courses:"); 41 | foreach (var (id, course) in db.Courses) 42 | { 43 | writer.WriteLine($"{id}: {course.Name}; teacher: {course.Teacher}"); 44 | } 45 | 46 | return Task.FromResult((int)ExitCode.Success); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/Models.cs: -------------------------------------------------------------------------------- 1 | // Models used by the students "database". 2 | using System.Text.Json; 3 | 4 | namespace NestedCommands; 5 | 6 | internal record class Student(string FirstName, string LastName, string? Major, List Courses); 7 | 8 | internal record class Course(string Name, string Teacher); 9 | 10 | internal record class StudentCourse(int CourseId, float Grade); 11 | 12 | internal record class Database(Dictionary Students, Dictionary Courses) 13 | { 14 | public static async Task Load(string path, CancellationToken cancellationToken) 15 | { 16 | Database? result = null; 17 | try 18 | { 19 | using var stream = File.OpenRead(path); 20 | result = await JsonSerializer.DeserializeAsync(stream, cancellationToken: cancellationToken); 21 | } 22 | catch (FileNotFoundException) 23 | { 24 | } 25 | 26 | return result ?? new Database(new(), new()); 27 | } 28 | 29 | public async Task Save(string path, CancellationToken cancellationToken) 30 | { 31 | using var stream = File.Create(path); 32 | var options = new JsonSerializerOptions() 33 | { 34 | WriteIndented = true, 35 | }; 36 | 37 | await JsonSerializer.SerializeAsync(stream, this, options, cancellationToken); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/NestedCommands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Nested subcommands sample for Ookii.CommandLine. 9 | Copyright (c) Sven Groot (Ookii.org) 10 | This is sample code, so you can use it freely. 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Samples/NestedCommands/Program.cs: -------------------------------------------------------------------------------- 1 | using NestedCommands; 2 | using Ookii.CommandLine; 3 | using Ookii.CommandLine.Commands; 4 | 5 | var options = new CommandOptions() 6 | { 7 | UsageWriter = new UsageWriter() 8 | { 9 | // Add the application description. 10 | IncludeApplicationDescriptionBeforeCommandList = true, 11 | // The commands that derive from ParentCommand use ICommandWithCustomParsing, and don't 12 | // technically have a -Help argument. This prevents the instruction from being shown by 13 | // default. However, these commands will ignore -Help ignore it and print their child 14 | // command list anyway, so force the message to be shown. 15 | IncludeCommandHelpInstruction = TriState.True, 16 | }, 17 | }; 18 | 19 | var manager = new GeneratedManager(options); 20 | return await manager.RunCommandAsync() ?? (int)ExitCode.CreateCommandFailure; 21 | -------------------------------------------------------------------------------- /src/Samples/Parser/Parser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Command line parsing sample for Ookii.CommandLine 9 | Copyright (c) Sven Groot (Ookii.org) 10 | This is sample code, so you can use it freely. 11 | 12 | 13 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Samples/Parser/Program.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using ParserSample; 3 | 4 | // Many aspects of the parsing behavior and usage help generation can be customized using the 5 | // ParseOptions. You can also use the ParseOptionsAttribute for some of them (see the LongShort 6 | // sample for an example of that). 7 | var options = new ParseOptions() 8 | { 9 | // By default, repeating an argument more than once (except for multi-value arguments), causes 10 | // an error. By changing this option, we set it to show a warning instead, and use the last 11 | // value supplied. 12 | DuplicateArguments = ErrorMode.Warning, 13 | }; 14 | 15 | // The GeneratedParserAttribute adds a static Parse method to your class, which parses the 16 | // arguments, handles errors, and shows usage help if necessary (using a LineWrappingTextWriter to 17 | // neatly white-space wrap console output). 18 | // 19 | // It takes the arguments from Environment.GetCommandLineArgs(), but also has an overload 20 | // that takes a string[] array, if you prefer. 21 | // 22 | // If you want more control over parsing and error handling, you can create an instance of 23 | // the CommandLineParser class. See docs/ParsingArguments.md for an example of that. 24 | var arguments = ProgramArguments.Parse(options); 25 | 26 | // No need to do anything when the value is null; Parse() already printed errors and 27 | // usage help to the console. We return a non-zero value to indicate failure. 28 | if (arguments == null) 29 | { 30 | return 1; 31 | } 32 | 33 | // We use the LineWrappingTextWriter to neatly white-space wrap console output. 34 | using var writer = LineWrappingTextWriter.ForConsoleOut(); 35 | 36 | // Print the values of the arguments. 37 | writer.WriteLine("The following argument values were provided:"); 38 | writer.WriteLine($"Source: {arguments.Source}"); 39 | writer.WriteLine($"Destination: {arguments.Destination}"); 40 | writer.WriteLine($"OperationIndex: {arguments.OperationIndex}"); 41 | writer.WriteLine($"Date: {arguments.Date?.ToString() ?? "(null)"}"); 42 | writer.WriteLine($"Count: {arguments.Count}"); 43 | writer.WriteLine($"Verbose: {arguments.Verbose}"); 44 | var values = arguments.Values == null ? "(null)" : "{ " + string.Join(", ", arguments.Values) + " }"; 45 | writer.WriteLine($"Values: {values}"); 46 | writer.WriteLine($"Day: {arguments.Day?.ToString() ?? "(null)"}"); 47 | 48 | return 0; 49 | -------------------------------------------------------------------------------- /src/Samples/README.md: -------------------------------------------------------------------------------- 1 | # Samples 2 | 3 | Ookii.CommandLine comes with several samples that demonstrate various aspects of its functionality. 4 | 5 | - The [**parser sample**](Parser) demonstrates the basic functionality of defining, parsing and 6 | using arguments. 7 | - The [**long/short mode sample**](LongShort) demonstrates the POSIX-like long/short parsing mode, 8 | where arguments can have both a long name with `--` and a short name with `-`. 9 | - The [**custom usage sample**](CustomUsage) demonstrates the flexibility of Ookii.CommandLine's 10 | usage help generation, by customizing it to use completely different formatting. 11 | - The [**argument dependencies sample**](ArgumentDependencies) demonstrates how you can have 12 | arguments that require or prohibit the presence of other arguments. 13 | - The [**categories sample**](Categories) demonstrates how to group arguments into categories in the 14 | usage help. 15 | - The [**WPF sample**](Wpf) shows how you can use Ookii.CommandLine with a GUI application. 16 | 17 | There are three samples demonstrating how to use subcommands: 18 | 19 | - The [**subcommand sample**](Subcommand) demonstrates how to create a simple application that has 20 | multiple subcommands. 21 | - The [**nested commands sample**](NestedCommands) demonstrates how to create an application where 22 | commands can contain other commands. It also demonstrates how to create common arguments for 23 | multiple commands using a common base class. 24 | - The [**top-level arguments sample**](TopLevelArguments) demonstrates how to use arguments that 25 | don't belong to any subcommand before the command name. 26 | -------------------------------------------------------------------------------- /src/Samples/Subcommand/EncodingConverter.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Conversion; 3 | using System.Globalization; 4 | using System.Text; 5 | 6 | namespace SubcommandSample; 7 | 8 | // An ArgumentConverter for the Encoding class. 9 | internal class EncodingConverter : ArgumentConverter 10 | { 11 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 12 | { 13 | try 14 | { 15 | return Encoding.GetEncoding(value.ToString()); 16 | } 17 | catch (ArgumentException ex) 18 | { 19 | // This is the expected exception type for a converter. 20 | throw new FormatException(ex.Message, ex); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Samples/Subcommand/ExitCode.cs: -------------------------------------------------------------------------------- 1 | namespace SubcommandSample; 2 | 3 | // Constants for exit codes used by this sample. 4 | internal enum ExitCode 5 | { 6 | Success, 7 | CreateCommandFailure, 8 | ReadWriteFailure, 9 | FileExists, 10 | } 11 | -------------------------------------------------------------------------------- /src/Samples/Subcommand/GeneratedManager.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Commands; 2 | 3 | namespace SubcommandSample; 4 | 5 | // Use source generation to locate commands in this assembly. This, along with the 6 | // GeneratedParserAttribute on all the commands, enables trimming. 7 | [GeneratedCommandManager] 8 | partial class GeneratedManager 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/Samples/Subcommand/Program.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Commands; 3 | using SubcommandSample; 4 | 5 | // For an application using subcommands, set the friendly name used for the automatic version 6 | // command by using this attribute on the assembly rather than an arguments type. 7 | // You can also use the property in the .csproj file. 8 | [assembly: ApplicationFriendlyName("Ookii.CommandLine Subcommand Sample")] 9 | 10 | // You can use the CommandOptions class to customize the parsing behavior and usage help 11 | // output. CommandOptions inherits from ParseOptions so it supports all the same options. 12 | var options = new CommandOptions() 13 | { 14 | // Set options so the command names are determined by the class name, transformed to 15 | // dash-case and with the "Command" suffix stripped. 16 | CommandNameTransform = NameTransform.DashCase, 17 | UsageWriter = new UsageWriter() 18 | { 19 | // Show the application description before the command list. 20 | IncludeApplicationDescriptionBeforeCommandList = true, 21 | }, 22 | }; 23 | 24 | // Create a CommandManager for the commands in the current assembly. We use the manager we 25 | // defined to use source generation, which allows trimming even when using commands. 26 | // 27 | // In addition to our commands, it will also have an automatic "version" command (this can 28 | // be disabled with the options). 29 | var manager = new GeneratedManager(options); 30 | 31 | // Run the command indicated in the first argument to this application, and use the return 32 | // value of its Run method as the application exit code. If the command could not be 33 | // created, we return an error code. 34 | // 35 | // We use the async version because our commands use the IAsyncCommand interface. Note that 36 | // you can use this method even if not all of your commands use IAsyncCommand. 37 | return await manager.RunCommandAsync() ?? (int)ExitCode.CreateCommandFailure; 38 | -------------------------------------------------------------------------------- /src/Samples/Subcommand/Subcommand.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Subcommand sample for Ookii.CommandLine. 9 | Copyright (c) Sven Groot (Ookii.org) 10 | This is sample code, so you can use it freely. 11 | true 12 | 13 | 14 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/CommandUsageWriter.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | 3 | namespace TopLevelArguments; 4 | 5 | // Custom usage writer used for commands. 6 | class CommandUsageWriter : UsageWriter 7 | { 8 | // This lets us exclude the command usage syntax when appending command usage with the 9 | // TopLevelUsageWriter, and include it when we're running a command and may need to show usage 10 | // for that. 11 | public bool IncludeCommandUsageSyntax { get; set; } 12 | 13 | // Indicate there are global arguments in the command usage syntax. 14 | protected override void WriteUsageSyntaxPrefix() 15 | { 16 | WriteColor(UsagePrefixColor); 17 | Write("Usage: "); 18 | ResetColor(); 19 | Write(' '); 20 | Write(ExecutableName); 21 | Writer.Write(" [global arguments]"); 22 | if (CommandName != null) 23 | { 24 | Write(' '); 25 | Write(CommandName); 26 | } 27 | } 28 | 29 | // Omit the usage syntax when writing the command list after the top-level usage help. 30 | protected override void WriteCommandListUsageSyntax() 31 | { 32 | if (IncludeCommandUsageSyntax) 33 | { 34 | base.WriteCommandListUsageSyntax(); 35 | } 36 | } 37 | 38 | // Also include the global arguments in the help instruction. 39 | protected override void WriteCommandHelpInstruction(string name, string argumentNamePrefix, string argumentName) 40 | => base.WriteCommandHelpInstruction(name + " [global arguments]", argumentNamePrefix, argumentName); 41 | } 42 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/EncodingConverter.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Conversion; 3 | using System.Globalization; 4 | using System.Text; 5 | 6 | namespace TopLevelArguments; 7 | 8 | // An ArgumentConverter for the Encoding class, using the utility base class provided by 9 | // Ookii.CommandLine. 10 | internal class EncodingConverter : ArgumentConverter 11 | { 12 | public override object? Convert(ReadOnlyMemory value, CultureInfo culture, CommandLineArgument argument) 13 | { 14 | try 15 | { 16 | return Encoding.GetEncoding(value.ToString()); 17 | } 18 | catch (ArgumentException ex) 19 | { 20 | // This is the expected exception type for a converter. 21 | throw new FormatException(ex.Message, ex); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/ExitCode.cs: -------------------------------------------------------------------------------- 1 | namespace TopLevelArguments; 2 | 3 | // Constants for exit codes used by this sample. 4 | internal enum ExitCode 5 | { 6 | Success, 7 | CreateCommandFailure, 8 | ReadWriteFailure, 9 | FileExists, 10 | } 11 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/GeneratedManager.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine.Commands; 2 | 3 | namespace TopLevelArguments; 4 | 5 | // Use source generation to locate commands in this assembly. 6 | [GeneratedCommandManager] 7 | partial class GeneratedManager 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/Program.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Commands; 3 | using Ookii.CommandLine.Terminal; 4 | 5 | [assembly: ApplicationFriendlyName("Ookii.CommandLine Top-level Arguments Sample")] 6 | 7 | namespace TopLevelArguments; 8 | 9 | static class Program 10 | { 11 | static async Task Main() 12 | { 13 | // Modified usage format for the command list and commands to account for global arguments. 14 | var commandUsageWriter = new CommandUsageWriter(); 15 | 16 | // You can use the CommandOptions class to customize the parsing behavior and usage help 17 | // output. CommandOptions inherits from ParseOptions so it supports all the same options. 18 | var commandOptions = new CommandOptions() 19 | { 20 | IsPosix = true, 21 | // The top-level arguments will have a -Version argument, so no need for a version 22 | // command. 23 | AutoVersionCommand = false, 24 | UsageWriter = commandUsageWriter, 25 | }; 26 | 27 | var manager = new GeneratedManager(commandOptions); 28 | 29 | // Use different options for the top-level arguments. 30 | var parseOptions = new ParseOptions() 31 | { 32 | IsPosix = true, 33 | // Modified usage format to list commands as well as top-level usage. 34 | UsageWriter = new TopLevelUsageWriter(manager) 35 | }; 36 | 37 | // First parse the top-level arguments. 38 | var parser = TopLevelArguments.CreateParser(parseOptions); 39 | Arguments = parser.ParseWithErrorHandling(); 40 | if (Arguments == null) 41 | { 42 | return (int)ExitCode.CreateCommandFailure; 43 | } 44 | 45 | // Run the command indicated in the top-level --command argument, and pass along the 46 | // arguments that weren't consumed by the top-level CommandLineParser. 47 | commandUsageWriter.IncludeCommandUsageSyntax = true; 48 | return await manager.RunCommandAsync(Arguments.Command, parser.ParseResult.RemainingArguments) 49 | ?? (int)ExitCode.CreateCommandFailure; 50 | } 51 | 52 | // Utility method used by the commands to write exception messages to the console. 53 | public static void WriteErrorMessage(string message) 54 | { 55 | using var support = VirtualTerminal.EnableColor(StandardStream.Error); 56 | using var writer = LineWrappingTextWriter.ForConsoleError(); 57 | 58 | // Add some color if we can. 59 | if (support.IsSupported) 60 | { 61 | writer.Write(TextFormat.ForegroundRed); 62 | } 63 | 64 | writer.WriteLine(message); 65 | if (support.IsSupported) 66 | { 67 | writer.Write(TextFormat.Default); 68 | } 69 | } 70 | 71 | // Provides access to the top-level arguments for use by the commands. 72 | public static TopLevelArguments? Arguments { get; private set; } 73 | } 74 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/ReadCommand.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Commands; 3 | using System.ComponentModel; 4 | 5 | namespace TopLevelArguments; 6 | 7 | // This command is identical to the read command of the Subcommand sample; see that for a more 8 | // detailed description. 9 | [GeneratedParser] 10 | [Command] 11 | [Description("Reads and displays data from a file using the specified encoding, wrapping the text to fit the console.")] 12 | partial class ReadCommand : AsyncCommandBase 13 | { 14 | // Run the command after the arguments have been parsed. 15 | public override async Task RunAsync(CancellationToken cancellationToken) 16 | { 17 | try 18 | { 19 | // We use a LineWrappingTextWriter to neatly wrap console output 20 | using var writer = LineWrappingTextWriter.ForConsoleOut(); 21 | 22 | // Write the contents of the file to the console. 23 | await foreach (var line in File.ReadLinesAsync(Program.Arguments!.Path.FullName, Program.Arguments.Encoding, 24 | cancellationToken)) 25 | { 26 | await writer.WriteLineAsync(line); 27 | } 28 | 29 | // The Main method will return the exit status to the operating system. The numbers are 30 | // made up for the sample, they don't mean anything. Usually, 0 means success, and any 31 | // other value indicates an error. 32 | return (int)ExitCode.Success; 33 | } 34 | catch (IOException ex) 35 | { 36 | Program.WriteErrorMessage(ex.Message); 37 | return (int)ExitCode.ReadWriteFailure; 38 | } 39 | catch (UnauthorizedAccessException ex) 40 | { 41 | Program.WriteErrorMessage(ex.Message); 42 | return (int)ExitCode.ReadWriteFailure; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/TopLevelArguments.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Conversion; 3 | using System.ComponentModel; 4 | using System.Text; 5 | 6 | namespace TopLevelArguments; 7 | 8 | [GeneratedParser] 9 | [Description("Subcommands with top-level arguments sample for Ookii.CommandLine.")] 10 | partial class TopLevelArguments 11 | { 12 | // A required, positional argument to specify the file name. 13 | [CommandLineArgument(IsPositional = true)] 14 | [Description("The path of the file to read or write.")] 15 | public required FileInfo Path { get; set; } 16 | 17 | // A required, positional argument to specify what command to run. 18 | // 19 | // When this argument is encountered, parsing is canceled, returning success using the arguments 20 | // so far. The Main() method will then pass the remaining arguments to the specified command. 21 | [CommandLineArgument(IsPositional = true, CancelParsing = CancelMode.Success)] 22 | [Description("The command to run. After this argument, all remaining arguments are passed to the command.")] 23 | public required string Command { get; set; } 24 | 25 | // An argument to specify the encoding. 26 | // Because Encoding doesn't have a default ArgumentConverter, we use a custom one provided in 27 | // this sample. 28 | // Encoding's ToString() implementation just gives the class name, so don't include the default 29 | // value in the usage help; we'll write it ourself instead. 30 | [CommandLineArgument(IsShort = true, IncludeDefaultInUsageHelp = false)] 31 | [Description("The encoding to use to read the file. The default value is utf-8.")] 32 | [ArgumentConverter(typeof(EncodingConverter))] 33 | public Encoding Encoding { get; set; } = Encoding.UTF8; 34 | } 35 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/TopLevelArguments.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Subcommands with top-level arguments sample for Ookii.CommandLine. 9 | Copyright (c) Sven Groot (Ookii.org) 10 | This is sample code, so you can use it freely. 11 | true 12 | 13 | 14 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Samples/TopLevelArguments/TopLevelUsageWriter.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.CommandLine.Commands; 3 | 4 | namespace TopLevelArguments; 5 | 6 | // Custom UsageWriter used for the top-level arguments. 7 | internal class TopLevelUsageWriter : UsageWriter 8 | { 9 | private readonly CommandManager _manager; 10 | 11 | public TopLevelUsageWriter(CommandManager manager) 12 | { 13 | _manager = manager; 14 | } 15 | 16 | // Show the positional arguments last to indicate arguments after --command must be command 17 | // arguments. 18 | protected override IEnumerable GetArgumentsInUsageOrder() 19 | => Parser.Arguments 20 | .Where(a => a.Position == null) 21 | .Concat(Parser.Arguments.Where(a => a.Position != null)); 22 | 23 | // Indicate command arguments can follow the --command argument. 24 | protected override void WriteUsageSyntaxSuffix() 25 | { 26 | Writer.Write(" [command arguments]"); 27 | } 28 | 29 | // Write the command list at the end of the usage. 30 | protected override void WriteArgumentDescriptions() 31 | { 32 | base.WriteArgumentDescriptions(); 33 | _manager.WriteUsage(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Samples/Wpf/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Samples/Wpf/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using Ookii.Dialogs.Wpf; 3 | using System.Windows; 4 | 5 | namespace WpfSample; 6 | 7 | /// 8 | /// Interaction logic for App.xaml 9 | /// 10 | public partial class App : Application 11 | { 12 | protected override void OnStartup(StartupEventArgs e) 13 | { 14 | // Use the CommandLineParser class, instead of the static Parse() method, so we can 15 | // manually handle errors. The GeneratedParserAttribute generates a CreateParser() method 16 | // for this purpose. 17 | var parser = Arguments.CreateParser(); 18 | bool showHelp = true; 19 | try 20 | { 21 | var args = parser.Parse(e.Args); 22 | if (args != null) 23 | { 24 | // Arguments were parsed successfully. 25 | var window = new MainWindow(args); 26 | window.Show(); 27 | return; 28 | } 29 | } 30 | catch (CommandLineArgumentException ex) 31 | { 32 | // Use a TaskDialog (from Ookii.Dialogs) so we can have a help button. 33 | var dialog = new TaskDialog() 34 | { 35 | MainInstruction = "Invalid command line", 36 | Content = ex.Message, 37 | WindowTitle = parser.ApplicationFriendlyName, 38 | MainIcon = TaskDialogIcon.Error, 39 | AllowDialogCancellation = true, 40 | }; 41 | 42 | dialog.Buttons.Add(new TaskDialogButton(ButtonType.Close) { Default = true }); 43 | var helpButton = new TaskDialogButton("&Help"); 44 | dialog.Buttons.Add(helpButton); 45 | var button = dialog.Show(); 46 | 47 | // Don't show help unless the help button was used. 48 | if (button != helpButton) 49 | { 50 | showHelp = false; 51 | } 52 | } 53 | 54 | if (parser.ParseResult.HelpRequested && showHelp) 55 | { 56 | var help = new UsageWindow(parser); 57 | help.Show(); 58 | return; 59 | } 60 | 61 | // Need to shutdown manually if we didn't show any window. 62 | Shutdown(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Samples/Wpf/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /src/Samples/Wpf/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace WpfSample; 4 | 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | private readonly Arguments _arguments; 11 | 12 | public MainWindow(Arguments arguments) 13 | { 14 | _arguments = arguments; 15 | InitializeComponent(); 16 | } 17 | 18 | public Arguments Arguments => _arguments; 19 | 20 | private void HelpButton_Click(object sender, RoutedEventArgs e) 21 | { 22 | var help = new UsageWindow(); 23 | help.Show(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Samples/Wpf/README.md: -------------------------------------------------------------------------------- 1 | # WPF sample 2 | 3 | This sample demonstrates how you can use Ookii.CommandLine in an application with a graphical user 4 | interface. It uses the same arguments as the [parser sample](../Parser). 5 | 6 | Running this sample requires Microsoft Windows. 7 | 8 | This sample does not use the generated static [`Parse()`][Parse()_7] method, but instead uses the generated 9 | [`CreateParser()`][CreateParser()_1] method, and handles errors manually so it can show a dialog with the error message 10 | and a help button, and show the usage help only if that button was clicked, or the `-Help` argument 11 | was used. 12 | 13 | To use as much of the built-in usage help generation as possible, this sample uses a class derived 14 | from the [`UsageWriter`][] class (see [HtmlUsageWriter.cs](HtmlUsageWriter.cs)) that wraps the 15 | various components of the help in an HTML page, and then displays that to the user using a 16 | [WebView2 control](https://learn.microsoft.com/microsoft-edge/webview2/). 17 | 18 | ![WPF usage help in a WebView2 control](../../../docs/images/wpf.png) 19 | 20 | The sample uses a simple CSS stylesheet to format the usage help; you can make this as fancy as you 21 | like, of course. 22 | 23 | This is by no means the only way. Since all the information needed to generate usage help is 24 | available in the [`CommandLineParser`][] class, you could for example use a custom XAML page to 25 | show the usage help. 26 | 27 | This sample also defines a custom `-Version` argument; the automatic one that gets added by 28 | Ookii.CommandLine writes to the console, so it isn't useful here. This manual implementation shows 29 | the same version information in a dialog box. 30 | 31 | A similar approach would work for Windows Forms, or any other GUI framework. 32 | 33 | This application is very basic; it's just a sample, and I don't do a lot of GUI work nowadays. It's 34 | just intended to show how the [`UsageWriter`][] can be adapted to work in the context of a GUI app. 35 | 36 | [`CommandLineParser`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_CommandLineParser_1.htm 37 | [`UsageWriter`]: https://www.ookii.org/docs/commandline-5.0/html/T_Ookii_CommandLine_UsageWriter.htm 38 | [CreateParser()_1]: https://www.ookii.org/docs/commandline-5.0/html/M_Ookii_CommandLine_IParserProvider_1_CreateParser.htm 39 | [Parse()_7]: https://www.ookii.org/docs/commandline-5.0/html/Overload_Ookii_CommandLine_IParser_1_Parse.htm 40 | -------------------------------------------------------------------------------- /src/Samples/Wpf/UsageWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Samples/Wpf/UsageWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Ookii.CommandLine; 2 | using System.Windows; 3 | 4 | namespace WpfSample; 5 | 6 | /// 7 | /// Interaction logic for MainWindow.xaml 8 | /// 9 | public partial class UsageWindow : Window 10 | { 11 | private readonly CommandLineParser _parser; 12 | 13 | public UsageWindow(CommandLineParser? parser = null) 14 | { 15 | InitializeComponent(); 16 | _parser = parser ?? new(); 17 | } 18 | 19 | private async void Window_Loaded(object sender, RoutedEventArgs e) 20 | { 21 | await _webView.EnsureCoreWebView2Async(); 22 | _webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; 23 | 24 | // Generate and display usage. 25 | string usage = _parser.GetUsage(new HtmlUsageWriter()); 26 | _webView.NavigateToString(usage); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Samples/Wpf/Wpf.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | true 9 | true 10 | WPF sample for Ookii.CommandLine. 11 | Copyright (c) Sven Groot (Ookii.org) 12 | This is sample code, so you can use it freely. 13 | app.manifest 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net60 5 | X64 6 | 7 | --------------------------------------------------------------------------------