├── CommandLineParser.DependencyInjection ├── Interfaces │ ├── ICommandLineOptions.cs │ ├── IValidateCommandLineOptions.cs │ ├── IExecuteCommandLineOptions.cs │ ├── IExecuteParsingFailure.cs │ ├── IValidateCommandLineOptionsAsync.cs │ ├── IExecuteCommandLineOptionsAsync.cs │ ├── IExecuteParsingFailureAsync.cs │ ├── ICommandLineOptionsValidator.cs │ ├── ICommandLineParserSyncExecutionFactory.cs │ ├── ICommandLineParserAsyncExecutionFactory.cs │ └── ICommandLineParser.cs ├── Exceptions │ ├── CommandLineOptionsValidationException.cs │ └── NoExecuteCommandLineServiceFoundException.cs ├── Extensions │ ├── TaskExtensions.cs │ └── ServiceCollectionExtensions.cs ├── AsyncHelper.cs ├── CommandLineParser.DependencyInjection.csproj ├── Models │ └── CommandLineParserDiOptions.cs ├── CommandLineParserSyncExecutionFactory.cs ├── CommandLineParserAsyncExecutionFactory.cs ├── CommandLineOptionsValidator.cs └── CommandLineParser.cs ├── QuickStart ├── Properties │ └── launchSettings.json ├── QuickStart.csproj └── Program.cs ├── CommandLineParser.DependencyInjection.Tests ├── Services │ └── DoYouLikeService.cs ├── ExecuteOptions │ ├── ExecuteParsingFailure.cs │ ├── ExecuteAskOptions.cs │ ├── ExecuteParsingFailureAsync.cs │ └── ExecuteAskOptionsAsync.cs ├── Options │ ├── AskOptions.cs │ └── AskOptionsAsync.cs ├── CommandLineParser.DependencyInjection.Tests.csproj └── CommandLineParserDiTests.cs ├── license.txt ├── CommandLineParser.DependencyInjection.sln ├── .gitignore └── readme.md /CommandLineParser.DependencyInjection/Interfaces/ICommandLineOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CommandLineParser.DependencyInjection.Interfaces; 2 | 3 | /// 4 | /// Command Line Interface. 5 | /// 6 | public interface ICommandLineOptions; -------------------------------------------------------------------------------- /QuickStart/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "QuickStart": { 4 | "commandName": "Project" 5 | }, 6 | "WSL": { 7 | "commandName": "WSL2", 8 | "distributionName": "" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/Services/DoYouLikeService.cs: -------------------------------------------------------------------------------- 1 | namespace CommandLineParser.DependencyInjection.Tests.Services; 2 | 3 | class DoYouLikeService 4 | { 5 | public string DoILikeThis(string thing, bool like, bool async) => 6 | async 7 | ? like 8 | ? $"Yes, I do like ASYNC {thing}! Thank you, Thank you Sam I Am!" 9 | : $"I do not like them, Sam I Am! I do not like ASYNC {thing}." 10 | : like 11 | ? $"Yes, I do like {thing}! Thank you, Thank you Sam I Am!" 12 | : $"I do not like them, Sam I Am! I do not like {thing}."; 13 | } 14 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/IValidateCommandLineOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CommandLineParser.DependencyInjection.Interfaces; 2 | 3 | /// 4 | /// Validate Command Line Options Synchronously. 5 | /// 6 | /// Command Line Options to Validate 7 | public interface IValidateCommandLineOptions 8 | where TOptions : class, ICommandLineOptions 9 | { 10 | /// 11 | /// Validate Options Synchronously. 12 | /// 13 | /// Options to validate. 14 | /// Validation Result 15 | bool Validate(TOptions options); 16 | } -------------------------------------------------------------------------------- /QuickStart/QuickStart.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/IExecuteCommandLineOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CommandLineParser.DependencyInjection.Interfaces; 2 | 3 | /// 4 | /// Execute Command Line Synchronously. 5 | /// 6 | /// Command Line Options this executor handles. 7 | /// Results 8 | public interface IExecuteCommandLineOptions where TCommandLineOptions : ICommandLineOptions 9 | { 10 | /// 11 | /// Execute Command Synchronously. 12 | /// 13 | /// Command Line Options 14 | /// Result 15 | TResult Execute(TCommandLineOptions options); 16 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/IExecuteParsingFailure.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommandLine; 3 | 4 | namespace CommandLineParser.DependencyInjection.Interfaces; 5 | 6 | /// 7 | /// Synchronously Execute on Parsing Failure. 8 | /// 9 | /// Results 10 | public interface IExecuteParsingFailure 11 | { 12 | /// 13 | /// Execute Command Synchronously. 14 | /// 15 | /// Arguments that were passed into the parser. 16 | /// Errors as reported from the parser. 17 | /// Result 18 | TResult Execute(string[] args, IEnumerable errors); 19 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Exceptions/CommandLineOptionsValidationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandLineParser.DependencyInjection.Interfaces; 3 | 4 | namespace CommandLineParser.DependencyInjection.Exceptions; 5 | 6 | /// 7 | /// Command Line Options Validation Exception 8 | /// 9 | /// Options that are invalid. 10 | /// Exception that was thrown 11 | public class CommandLineOptionsValidationException(ICommandLineOptions options, Exception? innerException = null) 12 | : Exception($"Command Line Options '{options.GetType().Name}' are invalid.", innerException) 13 | { 14 | /// 15 | /// Invalid Options 16 | /// 17 | public ICommandLineOptions Options { get; } = options; 18 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/IValidateCommandLineOptionsAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace CommandLineParser.DependencyInjection.Interfaces; 5 | 6 | /// 7 | /// Validate Command Line Options Asynchronously. 8 | /// 9 | /// Command Line Options to Validate 10 | public interface IValidateCommandLineOptionsAsync 11 | where TOptions : class, ICommandLineOptions 12 | { 13 | /// 14 | /// Validate Options Asynchronously. 15 | /// 16 | /// Options to validate. 17 | /// Cancellation Token 18 | /// Validation Result 19 | Task ValidateAsync(TOptions options, CancellationToken ctx); 20 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/ExecuteOptions/ExecuteParsingFailure.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommandLine; 3 | using CommandLineParser.DependencyInjection.Interfaces; 4 | 5 | namespace CommandLineParser.DependencyInjection.Tests.ExecuteOptions; 6 | 7 | class ExecuteParsingFailure : IExecuteParsingFailure 8 | { 9 | #region Implementation of IExecuteParsingFailure 10 | 11 | /// 12 | /// Execute Command Synchronously. 13 | /// 14 | /// Arguments that were passed into the parser. 15 | /// Errors as reported from the parser. 16 | /// Result 17 | public string Execute(string[] args, IEnumerable errors) => $"Unable to parse \"{string.Join(' ', args)}\"."; 18 | 19 | #endregion 20 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/ExecuteOptions/ExecuteAskOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLineParser.DependencyInjection.Interfaces; 2 | using CommandLineParser.DependencyInjection.Tests.Options; 3 | using CommandLineParser.DependencyInjection.Tests.Services; 4 | 5 | namespace CommandLineParser.DependencyInjection.Tests.ExecuteOptions; 6 | 7 | class ExecuteAskOptions(DoYouLikeService doYouLikeService) : IExecuteCommandLineOptions 8 | { 9 | #region Implementation of IExecuteCommandLineOptions 10 | 11 | /// 12 | /// Execute Command Synchronously. 13 | /// 14 | /// Command Line Options 15 | /// Result 16 | public string Execute(AskOptions options) => doYouLikeService.DoILikeThis(options.DoYouLike, options.Like, false); 17 | 18 | #endregion 19 | } 20 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Threading.Tasks; 3 | 4 | namespace CommandLineParser.DependencyInjection.Extensions; 5 | 6 | public static class TaskExtensions 7 | { 8 | /// 9 | /// Invoke Async Method. 10 | /// 11 | /// Result Type 12 | /// Method Info 13 | /// Class method is being invoked on 14 | /// Method Parameters to use 15 | /// Result 16 | public static async Task InvokeAsync(this MethodInfo @this, object obj, params object[] parameters) 17 | { 18 | var awaitable = (Task)@this.Invoke(obj, parameters); 19 | await awaitable; 20 | return awaitable.GetAwaiter().GetResult(); 21 | } 22 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/IExecuteCommandLineOptionsAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace CommandLineParser.DependencyInjection.Interfaces; 5 | 6 | /// 7 | /// Execute Command Line Asynchronously. 8 | /// 9 | /// Command Line Options this executor handles. 10 | /// Results 11 | public interface IExecuteCommandLineOptionsAsync where TCommandLineOptions : ICommandLineOptions 12 | { 13 | /// 14 | /// Execute Command Asynchronously. 15 | /// 16 | /// Command Line Options 17 | /// Cancellation Token 18 | /// Result 19 | Task ExecuteAsync(TCommandLineOptions options, CancellationToken ctx = default); 20 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/IExecuteParsingFailureAsync.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace CommandLineParser.DependencyInjection.Interfaces; 7 | 8 | /// 9 | /// Asynchronously Execute on Parsing Failure. 10 | /// 11 | /// Results 12 | public interface IExecuteParsingFailureAsync 13 | { 14 | /// 15 | /// Execute Command Asynchronously. 16 | /// 17 | /// Arguments that were passed into the parser. 18 | /// Errors as reported from the parser. 19 | /// Cancellation Token 20 | /// Result 21 | Task ExecuteAsync(string[] args, IEnumerable errors, CancellationToken ctx = default); 22 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/Options/AskOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommandLine; 3 | using CommandLine.Text; 4 | using CommandLineParser.DependencyInjection.Interfaces; 5 | 6 | namespace CommandLineParser.DependencyInjection.Tests.Options; 7 | 8 | [Verb("ask", HelpText = "Ask a question.")] 9 | class AskOptions : ICommandLineOptions 10 | { 11 | [Option("like", Required = false, Default = false, HelpText = "Should we like this?")] 12 | public bool Like { get; set; } 13 | 14 | [Value(0, Required = true, HelpText = "What do we like?")] 15 | public string DoYouLike { get; set; } 16 | 17 | [Usage(ApplicationAlias = "CommandLineParserDiTests")] 18 | public static IEnumerable Examples => 19 | new List() { 20 | new Example("Do you like green eggs and ham?", new AskOptions { DoYouLike = "Green Eggs and Ham?", Like = true }) 21 | }; 22 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/Options/AskOptionsAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommandLine; 3 | using CommandLine.Text; 4 | using CommandLineParser.DependencyInjection.Interfaces; 5 | 6 | namespace CommandLineParser.DependencyInjection.Tests.Options; 7 | 8 | [Verb("askAsync", HelpText = "Ask a question ASYNC.")] 9 | class AskOptionsAsync : ICommandLineOptions 10 | { 11 | [Option("like", Required = false, Default = false, HelpText = "Should we like this?")] 12 | public bool Like { get; set; } 13 | 14 | [Value(0, Required = true, HelpText = "What do we like?")] 15 | public string DoYouLike { get; set; } 16 | 17 | [Usage(ApplicationAlias = "CommandLineParserDiTests")] 18 | public static IEnumerable Examples => 19 | new List() { 20 | new Example("Do you like green eggs and ham?", new AskOptions { DoYouLike = "Green Eggs and Ham?", Like = true }) 21 | }; 22 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/ExecuteOptions/ExecuteParsingFailureAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using CommandLine; 5 | using CommandLineParser.DependencyInjection.Interfaces; 6 | 7 | namespace CommandLineParser.DependencyInjection.Tests.ExecuteOptions; 8 | 9 | class ExecuteParsingFailureAsync : IExecuteParsingFailureAsync 10 | { 11 | #region Implementation of IExecuteParsingFailureAsync 12 | 13 | /// 14 | /// Execute Command Asynchronously. 15 | /// 16 | /// Arguments that were passed into the parser. 17 | /// Errors as reported from the parser. 18 | /// Cancellation Token 19 | /// Result 20 | public Task ExecuteAsync(string[] args, IEnumerable errors, CancellationToken ctx = default) => 21 | Task.FromResult($"Unable to parse \"{string.Join(' ', args)}\" ASYNC."); 22 | 23 | #endregion 24 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/CommandLineParser.DependencyInjection.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | latest 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/ExecuteOptions/ExecuteAskOptionsAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CommandLineParser.DependencyInjection.Interfaces; 4 | using CommandLineParser.DependencyInjection.Tests.Options; 5 | using CommandLineParser.DependencyInjection.Tests.Services; 6 | 7 | namespace CommandLineParser.DependencyInjection.Tests.ExecuteOptions; 8 | 9 | class ExecuteAskOptionsAsync(DoYouLikeService doYouLikeService) 10 | : IExecuteCommandLineOptionsAsync 11 | { 12 | #region Implementation of IExecuteCommandLineOptionsAsync 13 | 14 | /// 15 | /// Execute Command Asynchronously. 16 | /// 17 | /// Command Line Options 18 | /// Cancellation Token 19 | /// Result 20 | public Task ExecuteAsync(AskOptionsAsync options, CancellationToken ctx = default) => 21 | Task.FromResult(doYouLikeService.DoILikeThis(options.DoYouLike, options.Like, true)); 22 | 23 | #endregion 24 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024, Jaron Horst 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. -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/AsyncHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace CommandLineParser.DependencyInjection; 6 | 7 | /// 8 | /// Async Helper to run Async Task's Synchronously. 9 | /// 10 | public static class AsyncHelper 11 | { 12 | private static readonly TaskFactory TaskFactory = new 13 | TaskFactory(CancellationToken.None, 14 | TaskCreationOptions.None, 15 | TaskContinuationOptions.None, 16 | TaskScheduler.Default); 17 | 18 | /// 19 | /// Run an Async task Synchronously and return the result. 20 | /// 21 | /// Result Type 22 | /// Async Task 23 | /// Async Task's Result 24 | public static TResult RunSync(Func> func) 25 | { 26 | return TaskFactory 27 | .StartNew(func) 28 | .Unwrap() 29 | .GetAwaiter() 30 | .GetResult(); 31 | } 32 | 33 | /// 34 | /// Run an Async task Synchronously. 35 | /// 36 | /// Async Task 37 | public static void RunSync(Func func) 38 | { 39 | TaskFactory 40 | .StartNew(func) 41 | .Unwrap() 42 | .GetAwaiter() 43 | .GetResult(); 44 | } 45 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/ICommandLineOptionsValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CommandLineParser.DependencyInjection.Exceptions; 4 | 5 | namespace CommandLineParser.DependencyInjection.Interfaces; 6 | 7 | /// 8 | /// Command Line Options Validator that supports both Synchronous and Asynchronous validation. 9 | /// 10 | public interface ICommandLineOptionsValidator 11 | { 12 | /// 13 | /// Validate Options Synchronously. 14 | /// 15 | /// Prefers Synchronous validators but looks for Asynchronous validators as a fallback. 16 | /// Options to validate. 17 | /// Validation Result 18 | /// Thrown when the validator has an exception. 19 | bool Validate(ICommandLineOptions options); 20 | 21 | /// 22 | /// Validate Options Asynchronously. 23 | /// 24 | /// Prefers Asynchronous validators but looks for Synchronous validators as a fallback. 25 | /// Options to validate. 26 | /// Cancellation Token 27 | /// Validation Result 28 | /// Thrown when the validator has an exception. 29 | Task ValidateAsync(ICommandLineOptions options, CancellationToken ctx); 30 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/ICommandLineParserSyncExecutionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CommandLine; 4 | using CommandLineParser.DependencyInjection.Exceptions; 5 | 6 | namespace CommandLineParser.DependencyInjection.Interfaces; 7 | 8 | /// 9 | /// Factory used to execute command line parser results asynchronously. 10 | /// 11 | /// Result Type 12 | public interface ICommandLineParserSyncExecutionFactory 13 | { 14 | /// 15 | /// Priority. Higher number is higher priority. 16 | /// 17 | int Priority { get; } 18 | 19 | /// 20 | /// Execute requested command asynchronously. 21 | /// 22 | /// Original Arguments 23 | /// Options Type 24 | /// Options 25 | /// Result 26 | /// Exception thrown when the Command Line Parser was able to get the Options but there was no service found to handle it. 27 | (TResult? Result, bool Handled) ExecuteCommand(string[] args, Type optionsType, object options); 28 | 29 | /// 30 | /// Execute parsing failure asynchronously. 31 | /// 32 | /// Original Arguments 33 | /// Command Line Parsing Errors 34 | /// Result, if handled 35 | (TResult? Result, bool Handled) ExecuteParsingFailure(string[] args, IEnumerable errors); 36 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Exceptions/NoExecuteCommandLineServiceFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandLineParser.DependencyInjection.Interfaces; 3 | 4 | namespace CommandLineParser.DependencyInjection.Exceptions; 5 | 6 | /// 7 | /// Exception thrown when the Command Line Parser was able to get the Options but there was no service found to handle it. 8 | /// 9 | public class NoExecuteCommandLineServiceFoundException : Exception 10 | { 11 | /// 12 | /// Create new Exception 13 | /// 14 | /// Options Type 15 | /// Result Type 16 | /// Was run synchronously? 17 | /// Was the sync/async allowed to fallback to async/sync? 18 | public NoExecuteCommandLineServiceFoundException(Type optionsType, Type resultType, bool isSynchronous, bool allowFallback) 19 | { 20 | OptionsType = optionsType; 21 | ResultType = resultType; 22 | IsSynchronous = isSynchronous; 23 | AllowFallback = allowFallback; 24 | } 25 | 26 | /// 27 | /// Options Type 28 | /// 29 | public Type OptionsType { get; } 30 | 31 | /// 32 | /// Result Type 33 | /// 34 | public Type ResultType { get; } 35 | 36 | /// 37 | /// Was run synchronously? 38 | /// 39 | public bool IsSynchronous { get; } 40 | 41 | /// 42 | /// Was the sync/async allowed to fallback to async/sync? 43 | /// 44 | public bool AllowFallback { get; } 45 | } 46 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/CommandLineParser.DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | Microsoft Dependency Injection extensions for CommandLineParser (https://github.com/commandlineparser/commandline) 6 | Added support for Async parsing/execution. 7 | CommandLineParser.DependencyInjection 8 | CommandLineParser.DependencyInjection 9 | True 10 | 1.5 11 | False 12 | CommandLineParser.DependencyInjection 13 | CommandLineParser.DependencyInjection 14 | readme.md 15 | license.txt 16 | Jaron Horst 17 | https://github.com/JaronrH/CommandLineParser.DependencyInjection 18 | https://github.com/JaronrH/CommandLineParser.DependencyInjection 19 | latest 20 | enable 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/ICommandLineParserAsyncExecutionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CommandLine; 6 | using CommandLineParser.DependencyInjection.Exceptions; 7 | 8 | namespace CommandLineParser.DependencyInjection.Interfaces; 9 | 10 | /// 11 | /// Factory used to execute command line parser results asynchronously. 12 | /// 13 | /// Result Type 14 | public interface ICommandLineParserAsyncExecutionFactory 15 | { 16 | /// 17 | /// Priority. Higher number is higher priority. 18 | /// 19 | int Priority { get; } 20 | 21 | /// 22 | /// Execute requested command asynchronously. 23 | /// 24 | /// Original Arguments 25 | /// Options Type 26 | /// Options 27 | /// Cancellation Token 28 | /// Result 29 | /// Exception thrown when the Command Line Parser was able to get the Options but there was no service found to handle it. 30 | Task<(TResult? Result, bool Handled)> ExecuteCommandAsync(string[] args, Type optionsType, object options, CancellationToken ctx); 31 | 32 | /// 33 | /// Execute parsing failure asynchronously. 34 | /// 35 | /// Original Arguments 36 | /// Command Line Parsing Errors 37 | /// Cancellation Token 38 | /// Result, if handled 39 | Task<(TResult? Result, bool Handled)> ExecuteParsingFailureAsync(string[] args, IEnumerable errors, CancellationToken ctx); 40 | } -------------------------------------------------------------------------------- /QuickStart/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLineParser.DependencyInjection.Interfaces; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | 6 | new ServiceCollection() // Create Service Collection 7 | .AddCommandLineParser(typeof(Options).Assembly) // Add CommandLineParser registrations to DI 8 | .AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Debug)) // Add Console Logging 9 | .BuildServiceProvider() // Build Service Provider 10 | .GetRequiredService>() // Get Parser Service 11 | .ParseArguments(args, -1) // Call Parser with Arguments (Options and ExecuteOptions will be loaded from DI as needed) 12 | ; 13 | 14 | public class Options: ICommandLineOptions 15 | { 16 | [Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages.")] 17 | public bool Verbose { get; set; } 18 | 19 | [Option('i', "treatAsInvalid", Required = false, HelpText = "Set to true to trigger validation error.")] 20 | public bool TreatAsInvalid { get; set; } 21 | } 22 | 23 | public class OptionsValidator : IValidateCommandLineOptions 24 | { 25 | public bool Validate(Options options) => !options.TreatAsInvalid; 26 | } 27 | 28 | public class ExecuteOptions(ILogger log) : IExecuteCommandLineOptions 29 | { 30 | #region Implementation of IExecuteCommandLineOptions 31 | 32 | /// 33 | /// Execute Command Synchronously. 34 | /// 35 | /// Command Line Options 36 | /// Result 37 | public int Execute(Options options) 38 | { 39 | if (options.Verbose) 40 | { 41 | log.LogInformation($"Verbose output enabled. Current Arguments: -v {options.Verbose}"); 42 | log.LogWarning("Quick Start Example! App is in Verbose mode!"); 43 | } 44 | else 45 | { 46 | log.LogInformation($"Current Arguments: -v {options.Verbose}"); 47 | log.LogInformation("Quick Start Example!"); 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | #endregion 54 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Models/CommandLineParserDiOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLineParser.DependencyInjection.Interfaces; 2 | 3 | namespace CommandLineParser.DependencyInjection.Models; 4 | 5 | /// 6 | /// Command Line Parser DI Options 7 | /// 8 | public class CommandLineParserDiOptions 9 | { 10 | /// 11 | /// When true, only public types will be registered (default). 12 | /// 13 | public bool PublicOnly { get; set; } = true; 14 | 15 | /// 16 | /// Always include the calling assembly when scanning for services even if assemblies are provided. True by default. 17 | /// 18 | public bool AlwaysIncludeCallingAssembly { get; set; } = true; 19 | 20 | /// 21 | /// Scan and import services for , & and factories for . True by default. 22 | /// 23 | /// Services will be transient while factories will be singleton. 24 | public bool FindAsyncServices { get; set; } = true; 25 | 26 | /// 27 | /// Include default factory for async execution. True by default. 28 | /// 29 | public bool IncludeDefaultAsyncFactory { get; set; } = true; 30 | 31 | /// 32 | /// Scan and import services for , & and factories for . True by default. 33 | /// 34 | /// Services will be transient while factories will be singleton. 35 | public bool FindSyncServices { get; set; } = true; 36 | 37 | /// 38 | /// Include default factory for async execution. True by default. 39 | /// 40 | public bool IncludeDefaultSyncFactory { get; set; } = true; 41 | 42 | /// 43 | /// Include default validator for options validation. True by default. 44 | /// 45 | /// Turn off if not using validators! 46 | public bool IncludeDefaultOptionsValidator { get; set; } = true; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35027.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLineParser.DependencyInjection", "CommandLineParser.DependencyInjection\CommandLineParser.DependencyInjection.csproj", "{7B3F7B26-B85B-4D35-BD5E-A2FAE8375008}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLineParser.DependencyInjection.Tests", "CommandLineParser.DependencyInjection.Tests\CommandLineParser.DependencyInjection.Tests.csproj", "{72B442E6-654D-4CC9-9995-0F10B86B686A}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A7353A99-C73A-4750-B658-4823E3FF3A63}" 11 | ProjectSection(SolutionItems) = preProject 12 | license.txt = license.txt 13 | readme.md = readme.md 14 | EndProjectSection 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickStart", "QuickStart\QuickStart.csproj", "{18B8EC8C-D181-4057-9160-C0A8E6B8E3EE}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {7B3F7B26-B85B-4D35-BD5E-A2FAE8375008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {7B3F7B26-B85B-4D35-BD5E-A2FAE8375008}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {7B3F7B26-B85B-4D35-BD5E-A2FAE8375008}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {7B3F7B26-B85B-4D35-BD5E-A2FAE8375008}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {72B442E6-654D-4CC9-9995-0F10B86B686A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {72B442E6-654D-4CC9-9995-0F10B86B686A}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {72B442E6-654D-4CC9-9995-0F10B86B686A}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {72B442E6-654D-4CC9-9995-0F10B86B686A}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {18B8EC8C-D181-4057-9160-C0A8E6B8E3EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {18B8EC8C-D181-4057-9160-C0A8E6B8E3EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {18B8EC8C-D181-4057-9160-C0A8E6B8E3EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {18B8EC8C-D181-4057-9160-C0A8E6B8E3EE}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {55911917-AEF0-4E83-BCA4-6C31A8A4DF72} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/CommandLineParserSyncExecutionFactory.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLineParser.DependencyInjection.Exceptions; 3 | using CommandLineParser.DependencyInjection.Interfaces; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Collections.Generic; 8 | using System.Reflection; 9 | 10 | 11 | namespace CommandLineParser.DependencyInjection; 12 | 13 | /// 14 | /// Default Factory that uses to get the appropriate async service to execute command line parser results synchronously. 15 | /// 16 | public class CommandLineParserSyncExecutionFactory(IServiceProvider serviceProvider) : ICommandLineParserSyncExecutionFactory 17 | { 18 | // ReSharper disable once StaticMemberInGenericType 19 | private static readonly Type ExecuteCommandLineOptionsInterfaceType = typeof(IExecuteCommandLineOptions<,>); 20 | 21 | /// 22 | /// Options Type to Service Type Map 23 | /// 24 | // ReSharper disable once StaticMemberInGenericType 25 | private static readonly ConcurrentDictionary OptionTypeToServiceType = new(); 26 | 27 | /// 28 | /// Priority. Higher number is higher priority. 29 | /// 30 | public int Priority => 0; 31 | 32 | /// 33 | /// Execute requested command asynchronously. 34 | /// 35 | /// Original Arguments 36 | /// Options Type 37 | /// Options 38 | /// Result 39 | /// Exception thrown when the Command Line Parser was able to get the Options but there was no service found to handle it. 40 | public virtual (TResult? Result, bool Handled) ExecuteCommand(string[] args, Type optionsType, object options) 41 | { 42 | var serviceInfo = OptionTypeToServiceType.GetOrAdd(optionsType, t => 43 | { 44 | var type = ExecuteCommandLineOptionsInterfaceType.MakeGenericType(optionsType, typeof(TResult)); 45 | var methodType = type.GetMethod("Execute"); 46 | return (type, methodType); 47 | }); 48 | var executingService = serviceProvider.GetService(serviceInfo.ServiceType); 49 | return executingService != null && serviceInfo.ExecuteMethodInfo != null 50 | ? ((TResult)serviceInfo.ExecuteMethodInfo.Invoke(executingService, [options]), true) 51 | : (default, false); 52 | } 53 | 54 | /// 55 | /// Execute parsing failure asynchronously. 56 | /// 57 | /// Original Arguments 58 | /// Command Line Parsing Errors 59 | /// Result, if handled 60 | public virtual (TResult? Result, bool Handled) ExecuteParsingFailure(string[] args, IEnumerable errors) 61 | { 62 | var service = serviceProvider.GetService>(); // Only create when needed 63 | return service == null 64 | ? (default, false) 65 | : (service.Execute(args, errors), true); 66 | } 67 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/CommandLineParserAsyncExecutionFactory.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLineParser.DependencyInjection.Exceptions; 3 | using CommandLineParser.DependencyInjection.Extensions; 4 | using CommandLineParser.DependencyInjection.Interfaces; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using System; 7 | using System.Collections.Concurrent; 8 | using System.Collections.Generic; 9 | using System.Reflection; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | 14 | namespace CommandLineParser.DependencyInjection; 15 | 16 | /// 17 | /// Default Factory that uses to get the appropriate async service to execute command line parser results asynchronously. 18 | /// 19 | public class CommandLineParserAsyncExecutionFactory(IServiceProvider serviceProvider) : ICommandLineParserAsyncExecutionFactory 20 | { 21 | // ReSharper disable once StaticMemberInGenericType 22 | private static readonly Type ExecuteCommandLineOptionsAsyncInterfaceType = typeof(IExecuteCommandLineOptionsAsync<,>); 23 | 24 | /// 25 | /// Options Type to Service Type Map 26 | /// 27 | // ReSharper disable once StaticMemberInGenericType 28 | private static readonly ConcurrentDictionary OptionTypeToServiceType = new(); 29 | 30 | /// 31 | /// Priority. Higher number is higher priority. 32 | /// 33 | public int Priority => 0; 34 | 35 | /// 36 | /// Execute requested command asynchronously. 37 | /// 38 | /// Original Arguments 39 | /// Options Type 40 | /// Options 41 | /// Cancellation Token 42 | /// Result 43 | /// Exception thrown when the Command Line Parser was able to get the Options but there was no service found to handle it. 44 | public virtual async Task<(TResult? Result, bool Handled)> ExecuteCommandAsync(string[] args, Type optionsType, object options, CancellationToken ctx) 45 | { 46 | var serviceInfo = OptionTypeToServiceType.GetOrAdd(optionsType, t => 47 | { 48 | var type = ExecuteCommandLineOptionsAsyncInterfaceType.MakeGenericType(optionsType, typeof(TResult)); 49 | var methodType = type.GetMethod("ExecuteAsync"); 50 | return (type, methodType); 51 | }); 52 | var executingService = serviceProvider.GetService(serviceInfo.ServiceType); 53 | return executingService != null && serviceInfo.ExecuteMethodInfo != null 54 | ? (await serviceInfo.ExecuteMethodInfo.InvokeAsync(executingService, new[] { options, ctx }), true) 55 | : (default, false); 56 | } 57 | 58 | /// 59 | /// Execute parsing failure asynchronously. 60 | /// 61 | /// Original Arguments 62 | /// Command Line Parsing Errors 63 | /// Cancellation Token 64 | /// Result, if handled 65 | public virtual async Task<(TResult? Result, bool Handled)> ExecuteParsingFailureAsync(string[] args, IEnumerable errors, CancellationToken ctx) 66 | { 67 | var service = serviceProvider.GetService>(); // Only create when needed 68 | return service == null 69 | ? (default, false) 70 | : (await service.ExecuteAsync(args, errors, ctx), true); 71 | } 72 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection.Tests/CommandLineParserDiTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using CommandLineParser.DependencyInjection.Interfaces; 8 | using CommandLineParser.DependencyInjection.Tests.Services; 9 | using FluentAssertions; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Xunit; 12 | 13 | namespace CommandLineParser.DependencyInjection.Tests; 14 | 15 | public class CommandLineParserDiTests 16 | { 17 | protected IServiceProvider ServiceProvider { get; set; } 18 | 19 | public CommandLineParserDiTests() 20 | { 21 | var collection = new ServiceCollection() 22 | .AddCommandLineParser(i => i.PublicOnly = false, typeof(CommandLineParserDiTests).Assembly) 23 | .AddSingleton() 24 | ; 25 | ServiceProvider = collection.BuildServiceProvider(); 26 | } 27 | 28 | public static IEnumerable AskOptionsTestData => 29 | new List 30 | { 31 | new object[] { (string[])["ask", "Green Eggs and Ham"], "I do not like them, Sam I Am! I do not like Green Eggs and Ham." }, 32 | new object[] { (string[])["ask", "Green Eggs and Ham", "--like", "true"], "Yes, I do like Green Eggs and Ham! Thank you, Thank you Sam I Am!" }, 33 | new object[] { (string[])["askAsync", "Green Eggs and Ham"], "I do not like them, Sam I Am! I do not like ASYNC Green Eggs and Ham." }, 34 | new object[] { (string[])["askAsync", "Green Eggs and Ham", "--like", "true"], "Yes, I do like ASYNC Green Eggs and Ham! Thank you, Thank you Sam I Am!" }, 35 | }; 36 | 37 | public static IEnumerable AskOptionsSyncTestData => AskOptionsTestData.Concat( 38 | new List 39 | { 40 | new object[] { (string[])["-filename", "testfile.txt"], "Unable to parse \"-filename testfile.txt\"." }, 41 | }); 42 | 43 | public static IEnumerable AskOptionsAsyncTestData => AskOptionsTestData.Concat( 44 | new List 45 | { 46 | new object[] { (string[])["-filename", "testfile.txt"], "Unable to parse \"-filename testfile.txt\" ASYNC." }, 47 | }); 48 | 49 | [Theory] 50 | [MemberData(nameof(AskOptionsSyncTestData))] 51 | public void AskOptionsExecutionTest(string[] arguments, string expectedResult) 52 | { 53 | var service = ServiceProvider.GetRequiredService>(); 54 | service 55 | .ParseArguments(arguments) 56 | .Should() 57 | .Be(expectedResult); 58 | } 59 | 60 | [Theory] 61 | [MemberData(nameof(AskOptionsAsyncTestData))] 62 | public async Task AskOptionsExecutionAsyncTest(string[] arguments, string expectedResult) 63 | { 64 | var service = ServiceProvider.GetRequiredService>(); 65 | (await service 66 | .ParseArgumentsAsync(arguments)) 67 | .Should() 68 | .Be(expectedResult); 69 | } 70 | 71 | [Fact] 72 | public void HelpTests() 73 | { 74 | var name = Assembly.GetEntryAssembly()!.GetCustomAttribute()?.Title ?? Assembly.GetCallingAssembly().GetName().Name; 75 | var version = Assembly.GetEntryAssembly()!.GetCustomAttribute()?.InformationalVersion ?? Assembly.GetCallingAssembly().GetName().Version.ToString(); 76 | var service = ServiceProvider.GetRequiredService>(); 77 | using (var writer = new StringWriter()) 78 | { 79 | service.ParseArguments([], o => o.HelpWriter = writer); 80 | writer 81 | .ToString() 82 | .Should() 83 | .Be($"{name} {version}\r\nCopyright (C) 2025 JetBrains s.r.o.\r\n\r\nERROR(S):\r\n No verb selected.\r\n\r\n ask Ask a question.\r\n\r\n askAsync Ask a question ASYNC.\r\n\r\n help Display more information on a specific command.\r\n\r\n version Display version information.\r\n\r\n"); 84 | } 85 | using (var writer = new StringWriter()) 86 | { 87 | service.ParseArguments(["ask", "--help"], o => o.HelpWriter = writer); 88 | writer 89 | .ToString() 90 | .Should() 91 | .Be($"{name} {version}\r\nCopyright (C) 2025 JetBrains s.r.o.\r\nUSAGE:\r\nDo you like green eggs and ham?:\r\n CommandLineParserDiTests ask --like \"Green Eggs and Ham?\"\r\n\r\n --like (Default: false) Should we like this?\r\n\r\n --help Display this help screen.\r\n\r\n --version Display version information.\r\n\r\n value pos. 0 Required. What do we like?\r\n\r\n"); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Interfaces/ICommandLineParser.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CommandLineParser.DependencyInjection.Exceptions; 6 | 7 | namespace CommandLineParser.DependencyInjection.Interfaces; 8 | 9 | /// 10 | /// Wrapper for the which leverages to get the Options () 11 | /// and execute either the corresponding or . 12 | /// 13 | /// All services are Singleton's and should only be registered once in the DI container. , while resolved, are not 14 | /// used from DI but rather just used to get the types from DI to register in the parser. 15 | /// 16 | /// 17 | public interface ICommandLineParser 18 | { 19 | /// 20 | /// Parse Command Line Arguments using . 21 | /// 22 | /// Command Line Arguments. 23 | /// Optional Parser Configuration Action. 24 | /// Default Result to return when parser was unable to parse out options. 25 | /// Fall back to and/or implementations and run them synchronously when synchronous version are not available? 26 | /// Result [code]. 27 | /// Options was found to be invalid (or there was an exception while validating the options) 28 | /// No handler for Command line Options. 29 | TResult? ParseArguments(string[] args, Action? configuration = null, TResult? defaultResult = default, bool allowAsyncImplementations = true); 30 | 31 | /// 32 | /// Parse Command Line Arguments using . 33 | /// 34 | /// Command Line Arguments. 35 | /// Optional Parser Configuration Action. 36 | /// Default Result to return when parser was unable to parse out options. 37 | /// Fall back to and/or implementations asynchronous version are not available? 38 | /// Cancellation Token 39 | /// Result [code]. 40 | /// Options was found to be invalid (or there was an exception while validating the options) 41 | /// No handler for Command line Options. 42 | Task ParseArgumentsAsync(string[] args, Action? configuration = null, TResult? defaultResult = default, bool allowSyncImplementations = true, CancellationToken ctx = default); 43 | 44 | /// 45 | /// Parse Command Line Arguments using . 46 | /// 47 | /// Command Line Arguments. 48 | /// Default Result to return when parser was unable to parse out options. 49 | /// Fall back to and/or implementations and run them synchronously when synchronous version are not available? 50 | /// Result [code]. 51 | /// Options was found to be invalid (or there was an exception while validating the options) 52 | /// No handler for Command line Options. 53 | TResult? ParseArguments(string[] args, TResult defaultResult, bool allowAsyncImplementations = true) => ParseArguments(args, null, defaultResult, allowAsyncImplementations); 54 | 55 | /// 56 | /// Parse Command Line Arguments using . 57 | /// 58 | /// Command Line Arguments. 59 | /// Default Result to return when parser was unable to parse out options. 60 | /// Fall back to and/or implementations asynchronous version are not available? 61 | /// Cancellation Token 62 | /// Result [code]. 63 | /// Options was found to be invalid (or there was an exception while validating the options) 64 | /// No handler for Command line Options. 65 | Task ParseArgumentsAsync(string[] args, TResult defaultResult, bool allowSyncImplementations = true, CancellationToken ctx = default) => 66 | ParseArgumentsAsync(args, null, defaultResult, allowSyncImplementations, ctx); 67 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Snippet 2 | 3 | # CommandLineParser.DependencyInjection 4 | 5 | This is a simple wrapper around the amazing [commandlineparser/commandline](https://github.com/commandlineparser/commandline) library which adds support for Microsoft's DI with help from [Scrutor](https://github.com/khellang/Scrutor). This is done by allow the following interfaces to be implemented which DI will find and user for executing the commandlineparser. 6 | 7 | |Interface|Description | 8 | |--|--| 9 | |ICommandLineOptions | This is an empty interface that is used solely for DI to find options! Make a class with [Options](https://github.com/commandlineparser/commandline/wiki/Option-Attribute) just like the CommandLineParser library instructs! | 10 | |ICommandLineParser< TResult >| This is the DI Service you will use to access the [Parser](https://github.com/commandlineparser/commandline/wiki/Getting-Started) and execute the relevant DI Service for IExecuteCommandLineOptions/IExecuteCommandLineOptionsAsync. It has the ability to run both synchronously or asynchronously. | 11 | |IExecuteCommandLineOptions| Interface that implements Execute(options) synchronously with the Parser's Command Line Options. | 12 | |IExecuteCommandLineOptionsAsync| Interface that implements ExecuteAsync(options, ctx) asynchronously with the Parser's Command Line Options. | 13 | |IExecuteParsingFailure< TResult >| If no Options/Handler found, this is called synchronously with the executed arguments array and parser errors to handle the inability to handle the command line input arguments. | 14 | |IExecuteParsingFailureAsync< TResult >|If no Options/Handler found, this is called asynchronously with the executed arguments array and parser errors to handle the inability to handle the command line input arguments. | 15 | |IValidateCommandLineOptions< TCommandLineOptions >|Synchronous Validator for Command Line Options.| 16 | |IValidateCommandLineOptionsAsync< TCommandLineOptions >|Asynchronous Validator for Command Line Options.| 17 | 18 | There are also some service that are available to be used or replaced. 19 | |Interface|Description | 20 | |--|--| 21 | |ICommandLineParser | Master command line processor which will parse the arguments, find the best options, validate the options (if available) and execute the appropriate handler (IExecuteCommandLineOptions/IExecuteCommandLineOptionsAsync). 22 | |ICommandLineParserSyncExecutionFactory and ICommandLineParserAsyncExecutionFactory| Services used to actually get the service to be used and execute it with the options from the CommandLineParser. Default implementation uses DI to get the service it needs and run it. Also takes care of executing whatever handles a parsing error.| 23 | |ICommandLineOptionsValidator|Validator that calls IValidateCommandLineOptionsAsync< TCommandLineOptions >/IValidateCommandLineOptions< TCommandLineOptions > to handle validation. 24 | 25 | ## Example 26 | 27 | Here is an example of the QuickStart from [commandlineparser/commandline](https://github.com/commandlineparser/commandline) implemented using DI. In this case though, we're writing to Microsoft's Logging ILogger that is getting injected using DI instead of writing directly to console! Also, when the logger is present, debug information can be logged as well. 28 | 29 | ``` 30 | using CommandLine; 31 | using CommandLineParser.DependencyInjection.Interfaces; 32 | using Microsoft.Extensions.DependencyInjection; 33 | using Microsoft.Extensions.Logging; 34 | 35 | new ServiceCollection() // Create Service Collection 36 | .AddCommandLineParser(typeof(Options).Assembly) // Add CommandLineParser registrations to DI 37 | .AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Debug)) // Add Console Logging 38 | .BuildServiceProvider() // Build Service Provider 39 | .GetRequiredService>() // Get Parser Service 40 | .ParseArguments(args, -1) // Call Parser with Arguments (Options and ExecuteOptions will be loaded from DI as needed) 41 | ; 42 | 43 | public class Options: ICommandLineOptions 44 | { 45 | [Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages.")] 46 | public bool Verbose { get; set; } 47 | 48 | [Option('i', "treatAsInvalid", Required = false, HelpText = "Set to true to trigger validation error.")] 49 | public bool TreatAsInvalid { get; set; } 50 | } 51 | 52 | public class OptionsValidator : IValidateCommandLineOptions 53 | { 54 | public bool Validate(Options options) => !options.TreatAsInvalid; 55 | } 56 | 57 | public class ExecuteOptions(ILogger log) : IExecuteCommandLineOptions 58 | { 59 | #region Implementation of IExecuteCommandLineOptions 60 | 61 | /// 62 | /// Execute Command Synchronously. 63 | /// 64 | /// Command Line Options 65 | /// Result 66 | public int Execute(Options options) 67 | { 68 | if (options.Verbose) 69 | { 70 | log.LogInformation($"Verbose output enabled. Current Arguments: -v {options.Verbose}"); 71 | log.LogWarning("Quick Start Example! App is in Verbose mode!"); 72 | } 73 | else 74 | { 75 | log.LogInformation($"Current Arguments: -v {options.Verbose}"); 76 | log.LogInformation("Quick Start Example!"); 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | #endregion 83 | } 84 | ``` 85 | 86 | 87 | ### Unit Tests 88 | 89 | ***See the Test's project for example.*** 90 | 91 | The Tests project has 3 folders: 92 | - Options: These are the CommandLineOptions, exactly how the [commandlineparser/commandline](https://github.com/commandlineparser/commandline) library defines them, with the ICommandLineOptions interface added for DI discovering. *The only difference is that one is used for calling async instead of sync in tests.* 93 | - ExecuteAskOptions: These are both Sync and Async dummy implementations of services that handle the AskOptions as well as handle Parsing failures. 94 | - Services: Dummy service that returns a string based on the arguments provided. -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/CommandLineOptionsValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Reflection; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using CommandLineParser.DependencyInjection.Exceptions; 7 | using CommandLineParser.DependencyInjection.Extensions; 8 | using CommandLineParser.DependencyInjection.Interfaces; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace CommandLineParser.DependencyInjection; 12 | 13 | public class CommandLineOptionsValidator( 14 | IServiceProvider serviceProvider, 15 | ILogger? log = null 16 | ) : ICommandLineOptionsValidator 17 | { 18 | private static readonly Type SyncValidatorType = typeof(IValidateCommandLineOptions<>); 19 | private static readonly Type AsyncValidatorType = typeof(IValidateCommandLineOptionsAsync<>); 20 | 21 | /// 22 | /// Options Type to Service Type Map 23 | /// 24 | // ReSharper disable once StaticMemberInGenericType 25 | private static readonly ConcurrentDictionary OptionTypeToSyncValidatorType = new(); 26 | private static readonly ConcurrentDictionary OptionTypeToAsyncValidatorType = new(); 27 | 28 | /// 29 | /// Try to Validate Options Synchronously. 30 | /// 31 | /// Options to Validate 32 | /// Options Type 33 | /// If there was a service available and if it was considered valid or not. 34 | private (bool Available, bool Valid) ValidateUsingSyncValidator(ICommandLineOptions options, Type type) 35 | { 36 | var serviceInfo = OptionTypeToSyncValidatorType.GetOrAdd(type, t => 37 | { 38 | var validatorType = SyncValidatorType.MakeGenericType(t); 39 | var methodInfo = validatorType.GetMethod("Validate"); 40 | return (ServiceType: validatorType, ValidateMethodInfo: methodInfo); 41 | }); 42 | var validatorService = serviceProvider.GetService(serviceInfo.ServiceType); 43 | if (validatorService == null) 44 | return (false, false); 45 | var isValid = (bool)serviceInfo.ValidateMethodInfo.Invoke(validatorService, [options])!; 46 | return (true, isValid); 47 | } 48 | 49 | /// 50 | /// Try to Validate Options Synchronously. 51 | /// 52 | /// Options to Validate 53 | /// Options Type 54 | /// Cancellation Token 55 | /// If there was a service available and if it was considered valid or not. 56 | private async Task<(bool Available, bool Valid)> ValidateUsingAsyncValidatorAsync(ICommandLineOptions options, Type type, CancellationToken ctx) 57 | { 58 | var serviceInfo = OptionTypeToAsyncValidatorType.GetOrAdd(type, t => 59 | { 60 | var validatorType = AsyncValidatorType.MakeGenericType(t); 61 | var methodInfo = validatorType.GetMethod("ValidateAsync"); 62 | return (ServiceType: validatorType, ValidateMethodInfo: methodInfo); 63 | }); 64 | var validatorService = serviceProvider.GetService(serviceInfo.ServiceType); 65 | if (validatorService == null) 66 | return (false, false); 67 | var isValid = await serviceInfo.ValidateMethodInfo.InvokeAsync(validatorService, [options, ctx]); 68 | return (true, isValid); 69 | } 70 | 71 | #region Implementation of ICommandLineOptionsValidator 72 | 73 | /// 74 | /// Validate Options Synchronously. 75 | /// 76 | /// Prefers Synchronous validators but looks for Asynchronous validators as a fallback. 77 | /// Options to validate. 78 | /// Validation Result 79 | /// Thrown when the validator has an exception. 80 | public bool Validate(ICommandLineOptions options) 81 | { 82 | var type = options.GetType(); 83 | 84 | try 85 | { 86 | // Try to find Synchronous Validators 87 | var validationResults = ValidateUsingSyncValidator(options, type); 88 | if (validationResults.Available) 89 | { 90 | log?.LogDebug($"Validated Options '{type.Name}' with Sync validator. Options are {(validationResults.Valid ? "valid" : "not valid")}."); 91 | return validationResults.Valid; 92 | } 93 | 94 | // Try to find Asynchronous Validators 95 | var asyncValidationResults = AsyncHelper.RunSync(async () => await ValidateUsingAsyncValidatorAsync(options, type, CancellationToken.None)); 96 | if (asyncValidationResults.Available) 97 | { 98 | log?.LogDebug($"Validated Options '{type.Name}' with Async validator. Options are {(asyncValidationResults.Valid ? "valid" : "not valid")}."); 99 | return asyncValidationResults.Valid; 100 | } 101 | 102 | // No Validators found 103 | log?.LogDebug($"No Validator found for Options '{type.Name}'."); 104 | return true; 105 | } 106 | catch (OperationCanceledException) 107 | { 108 | throw; 109 | } 110 | catch (Exception e) 111 | { 112 | var ex = new CommandLineOptionsValidationException(options, e); 113 | log?.LogError(ex, $"Unable to validate options '{type.Name}'."); 114 | throw ex; 115 | } 116 | } 117 | 118 | /// 119 | /// Validate Options Asynchronously. 120 | /// 121 | /// Prefers Asynchronous validators but looks for Synchronous validators as a fallback. 122 | /// Options to validate. 123 | /// Cancellation Token 124 | /// Validation Result 125 | /// Thrown when the validator has an exception. 126 | public async Task ValidateAsync(ICommandLineOptions options, CancellationToken ctx) 127 | { 128 | var type = options.GetType(); 129 | 130 | try 131 | { 132 | // Try to find Asynchronous Validators 133 | var asyncValidationResults = await ValidateUsingAsyncValidatorAsync(options, type, ctx); 134 | if (asyncValidationResults.Available) 135 | { 136 | log?.LogDebug($"Validated Options '{type.Name}' with Async validator. Options are {(asyncValidationResults.Valid ? "valid" : "not valid")}."); 137 | return asyncValidationResults.Valid; 138 | } 139 | 140 | // Try to find Synchronous Validators 141 | var validationResults = ValidateUsingSyncValidator(options, type); 142 | if (validationResults.Available) 143 | { 144 | log?.LogDebug($"Validated Options '{type.Name}' with Sync validator. Options are {(validationResults.Valid ? "valid" : "not valid")}."); 145 | return validationResults.Valid; 146 | } 147 | 148 | // No Validators found 149 | log?.LogDebug($"No Validator found for Options '{type.Name}'."); 150 | return true; 151 | } 152 | catch (OperationCanceledException) 153 | { 154 | throw; 155 | } 156 | catch (Exception e) 157 | { 158 | var ex = new CommandLineOptionsValidationException(options, e); 159 | log?.LogError(ex, $"Unable to validate options '{type.Name}'."); 160 | throw ex; 161 | } 162 | } 163 | 164 | #endregion 165 | } -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using CommandLineParser.DependencyInjection; 5 | using CommandLineParser.DependencyInjection.Interfaces; 6 | using CommandLineParser.DependencyInjection.Models; 7 | 8 | // ReSharper disable once CheckNamespace 9 | namespace Microsoft.Extensions.DependencyInjection; 10 | 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// Add Command Line Parser Extensions. 15 | /// 16 | /// Only scans public classes. 17 | /// Service Collection to add service to. 18 | /// Assemblies to scan. 19 | public static IServiceCollection AddCommandLineParser(this IServiceCollection services, params Assembly[] assemblies) => 20 | services.AddCommandLineParser(new CommandLineParserDiOptions(), assemblies); 21 | 22 | /// 23 | /// Add Command Line Parser Extensions. 24 | /// 25 | /// Only scans public classes. 26 | /// Service Collection to add service to. 27 | /// Configure Options 28 | /// Assemblies to scan. 29 | public static IServiceCollection AddCommandLineParser(this IServiceCollection services, Action configAction, params Assembly[] assemblies) 30 | { 31 | var options = new CommandLineParserDiOptions(); 32 | configAction(options); 33 | return services.AddCommandLineParser(options, assemblies); 34 | } 35 | 36 | /// 37 | /// Add Command Line Parser Extensions. 38 | /// 39 | /// Service Collection to add service to. 40 | /// Command Line Parser DI Options 41 | /// Assemblies to scan. 42 | public static IServiceCollection AddCommandLineParser(this IServiceCollection services, CommandLineParserDiOptions? options = null, params Assembly[] assemblies) 43 | { 44 | var executeCommandLineOptionsInterface = typeof(IExecuteCommandLineOptions<,>); 45 | var executeParsingFailureInterface = typeof(IExecuteParsingFailure<>); 46 | var executeCommandLineOptionsAsyncInterface = typeof(IExecuteCommandLineOptionsAsync<,>); 47 | var executeParsingFailureAsyncInterface = typeof(IExecuteParsingFailureAsync<>); 48 | var commandLineParserSyncExecutionFactory = typeof(ICommandLineParserSyncExecutionFactory<>); 49 | var commandLineParserAsyncExecutionFactory = typeof(ICommandLineParserAsyncExecutionFactory<>); 50 | var defaultCommandLineParserAsyncExecutionFactory = typeof(CommandLineParserAsyncExecutionFactory<>); 51 | var defaultCommandLineParserSyncExecutionFactory = typeof(CommandLineParserSyncExecutionFactory<>); 52 | var validateCommandLineOptions = typeof(IValidateCommandLineOptions<>); 53 | var validateCommandLineOptionsAsync = typeof(IValidateCommandLineOptionsAsync<>); 54 | 55 | // Default Options 56 | options ??= new CommandLineParserDiOptions(); 57 | 58 | // Include Calling assembly? 59 | if (options.AlwaysIncludeCallingAssembly) 60 | { 61 | var callingAssembly = Assembly.GetCallingAssembly(); 62 | if (!assemblies.Contains(callingAssembly)) 63 | { 64 | var assembliesList = assemblies.ToList(); 65 | assembliesList.Add(callingAssembly); 66 | assemblies = assembliesList.ToArray(); 67 | } 68 | } 69 | 70 | // Command Line Options 71 | services 72 | .Scan(a => a 73 | .FromAssemblies(assemblies) 74 | .AddClasses(i => i.AssignableTo(), publicOnly: options.PublicOnly) 75 | .As() 76 | .WithTransientLifetime() 77 | ); 78 | 79 | // Async Services 80 | if (options.FindAsyncServices) 81 | services 82 | .Scan(a => a 83 | .FromAssemblies(assemblies) 84 | .AddClasses(i => i 85 | .AssignableTo(executeCommandLineOptionsAsyncInterface) 86 | .Where(t => !t.IsAbstract) 87 | , publicOnly: options.PublicOnly) 88 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && executeCommandLineOptionsAsyncInterface.IsAssignableFrom(i.GetGenericTypeDefinition()))) 89 | .WithTransientLifetime() 90 | ) 91 | .Scan(a => a 92 | .FromAssemblies(assemblies) 93 | .AddClasses(i => i 94 | .AssignableTo(executeParsingFailureAsyncInterface) 95 | .Where(t => !t.IsAbstract) 96 | , publicOnly: options.PublicOnly) 97 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && executeParsingFailureAsyncInterface.IsAssignableFrom(i.GetGenericTypeDefinition()))) 98 | .WithTransientLifetime() 99 | ) 100 | .Scan(a => a 101 | .FromAssemblies(assemblies) 102 | .AddClasses(i => i 103 | .AssignableTo(commandLineParserAsyncExecutionFactory) 104 | .Where(t => !t.IsAbstract && t != defaultCommandLineParserAsyncExecutionFactory) 105 | , publicOnly: options.PublicOnly) 106 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && commandLineParserAsyncExecutionFactory.IsAssignableFrom(i.GetGenericTypeDefinition()))) 107 | .WithSingletonLifetime() 108 | ) 109 | .Scan(a => a 110 | .FromAssemblies(assemblies) 111 | .AddClasses(i => i 112 | .AssignableTo(validateCommandLineOptionsAsync) 113 | .Where(t => !t.IsAbstract) 114 | , publicOnly: options.PublicOnly) 115 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && validateCommandLineOptionsAsync.IsAssignableFrom(i.GetGenericTypeDefinition()))) 116 | .WithTransientLifetime() 117 | ) 118 | ; 119 | if (options.IncludeDefaultAsyncFactory) 120 | services 121 | .AddSingleton(typeof(ICommandLineParserAsyncExecutionFactory<>), typeof(CommandLineParserAsyncExecutionFactory<>)) 122 | ; 123 | 124 | // Sync Services 125 | if (options.FindSyncServices) 126 | services 127 | .Scan(a => a 128 | .FromAssemblies(assemblies) 129 | .AddClasses(i => i 130 | .AssignableTo(executeCommandLineOptionsInterface) 131 | .Where(t => !t.IsAbstract) 132 | , publicOnly: options.PublicOnly) 133 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && executeCommandLineOptionsInterface.IsAssignableFrom(i.GetGenericTypeDefinition()))) 134 | .WithTransientLifetime() 135 | ) 136 | .Scan(a => a 137 | .FromAssemblies(assemblies) 138 | .AddClasses(i => i 139 | .AssignableTo(executeParsingFailureInterface) 140 | .Where(t => !t.IsAbstract) 141 | , publicOnly: options.PublicOnly) 142 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && executeParsingFailureInterface.IsAssignableFrom(i.GetGenericTypeDefinition()))) 143 | .WithTransientLifetime() 144 | ) 145 | .Scan(a => a 146 | .FromAssemblies(assemblies) 147 | .AddClasses(i => i 148 | .AssignableTo(commandLineParserSyncExecutionFactory) 149 | .Where(t => !t.IsAbstract && t != defaultCommandLineParserSyncExecutionFactory) 150 | , publicOnly: options.PublicOnly) 151 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && commandLineParserSyncExecutionFactory.IsAssignableFrom(i.GetGenericTypeDefinition()))) 152 | .WithSingletonLifetime() 153 | ) 154 | .Scan(a => a 155 | .FromAssemblies(assemblies) 156 | .AddClasses(i => i 157 | .AssignableTo(validateCommandLineOptions) 158 | .Where(t => !t.IsAbstract) 159 | , publicOnly: options.PublicOnly) 160 | .As(t => t.GetInterfaces().Where(i => i.IsConstructedGenericType && validateCommandLineOptions.IsAssignableFrom(i.GetGenericTypeDefinition()))) 161 | .WithTransientLifetime() 162 | ) 163 | ; 164 | if (options.IncludeDefaultSyncFactory) 165 | services 166 | .AddSingleton(typeof(ICommandLineParserSyncExecutionFactory<>), typeof(CommandLineParserSyncExecutionFactory<>)) 167 | ; 168 | 169 | // Validator 170 | if (options.IncludeDefaultOptionsValidator) 171 | services 172 | .AddSingleton() 173 | ; 174 | 175 | // Core Services 176 | services 177 | .AddSingleton(typeof(ICommandLineParser<>), typeof(CommandLineParser<>)) 178 | ; 179 | 180 | return services; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /CommandLineParser.DependencyInjection/CommandLineParser.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLineParser.DependencyInjection.Exceptions; 3 | using CommandLineParser.DependencyInjection.Interfaces; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace CommandLineParser.DependencyInjection; 13 | 14 | public class CommandLineParser : ICommandLineParser 15 | { 16 | private readonly Type[] _commandLineOptionTypes; 17 | private readonly ILogger>? _log; 18 | private readonly IEnumerable> _asyncExecutionFactories; 19 | private readonly IEnumerable> _syncExecutionFactories; 20 | private readonly ICommandLineOptionsValidator? _commandLineOptionsValidator; 21 | 22 | public CommandLineParser(IEnumerable commandLineOptions, IEnumerable> asyncExecutionFactories, IEnumerable> syncExecutionFactories, ICommandLineOptionsValidator? commandLineOptionsValidator = null, ILogger>? log = null) 23 | { 24 | _commandLineOptionsValidator = commandLineOptionsValidator; 25 | _log = log; 26 | _asyncExecutionFactories = asyncExecutionFactories 27 | .OrderByDescending(i => i.Priority) 28 | .ToArray(); 29 | _syncExecutionFactories = syncExecutionFactories 30 | .OrderByDescending(i => i.Priority) 31 | .ToArray(); 32 | _commandLineOptionTypes = commandLineOptions.Select(i => i.GetType()).ToArray(); 33 | 34 | if (_commandLineOptionTypes.Length == 0) 35 | log?.LogWarning("No ICommandLineOptions implementations were found. Ensure that you have registered at least one implementation of ICommandLineOptions with the DI container."); 36 | } 37 | 38 | #region Implementation of ICommandLineParser 39 | 40 | /// 41 | /// Parse Command Line Arguments using . 42 | /// 43 | /// Command Line Arguments. 44 | /// Optional Parser Configuration Action. 45 | /// Default Result to return when parser was unable to parse out options. 46 | /// Fall back to and/or implementations and run them synchronously when synchronous version are not available? 47 | /// Result [code]. 48 | /// Options was found to be invalid (or there was an exception while validating the options) 49 | /// No handler for Command line Options. 50 | public TResult? ParseArguments(string[] args, Action? configuration = null, TResult? defaultResult = default, 51 | bool allowAsyncImplementations = true) 52 | { 53 | // Create Parser 54 | using var parser = configuration == null 55 | ? new Parser() 56 | : new Parser(configuration); 57 | 58 | // Execute Parser 59 | var result = _commandLineOptionTypes.Length == 1 && _commandLineOptionTypes.All(i => !i.GetCustomAttributes().Any()) 60 | ? parser.ParseArguments(() => Activator.CreateInstance(_commandLineOptionTypes.First()), args) 61 | : parser.ParseArguments(args, _commandLineOptionTypes); 62 | var errors = ((result as NotParsed)?.Errors ?? []).ToArray(); 63 | if (result.Tag == ParserResultType.Parsed) 64 | _log?.LogInformation("Command Line Arguments Parser determined that the '{Type}' type is to be used for options.", result.TypeInfo.Current.Name); 65 | else 66 | _log?.LogError("Command Line Arguments Parser was unable to parse arguments."); 67 | 68 | // Parser Execute successfully? 69 | if (result.Tag == ParserResultType.Parsed) 70 | { 71 | // Validate Options 72 | if (_commandLineOptionsValidator != null && result.Value is ICommandLineOptions clo && !_commandLineOptionsValidator.Validate(clo)) 73 | throw new CommandLineOptionsValidationException(clo); 74 | 75 | // Get Parsed Value 76 | if (result is Parsed parsed) 77 | { 78 | // Look for Sync Types to execute 79 | foreach (var syncExecutionFactory in _syncExecutionFactories) 80 | try 81 | { 82 | _log?.LogDebug("Handling options '{Type}' using the Sync Execution Factory '{Factory}'.", 83 | result.TypeInfo.Current.Name, syncExecutionFactory.GetType().Name); 84 | var executionResult = 85 | syncExecutionFactory.ExecuteCommand(args, result.TypeInfo.Current, parsed.Value); 86 | if (executionResult.Handled) return executionResult.Result; 87 | } 88 | catch (OperationCanceledException) 89 | { 90 | throw; 91 | } 92 | catch (Exception e) 93 | { 94 | _log?.LogError(e, "Exception thrown while handling options '{Type}' using the Sync Execution Factory '{Factory}'.", result.TypeInfo.Current.Name, syncExecutionFactory.GetType().Name); 95 | } 96 | 97 | // Look for Async? 98 | if (allowAsyncImplementations) 99 | foreach (var asyncExecutionFactory in _asyncExecutionFactories) 100 | try 101 | { 102 | _log?.LogDebug("Handling options '{Type}' using the Async Execution Factory '{Factory}'.", result.TypeInfo.Current.Name, asyncExecutionFactory.GetType().Name); 103 | var executionResult = 104 | AsyncHelper.RunSync(async () => await asyncExecutionFactory.ExecuteCommandAsync(args, result.TypeInfo.Current, parsed.Value, CancellationToken.None)); 105 | if (executionResult.Handled) return executionResult.Result; 106 | } 107 | catch (OperationCanceledException) 108 | { 109 | throw; 110 | } 111 | catch (Exception e) 112 | { 113 | _log?.LogError(e, "Exception thrown while handling options '{Type}' using the Async Execution Factory '{Factory}'.", result.TypeInfo.Current.Name, asyncExecutionFactory.GetType().Name); 114 | } 115 | } 116 | 117 | // Throw exception if Parser ran but no service was found to handle the results. 118 | throw new NoExecuteCommandLineServiceFoundException(result.TypeInfo.Current, typeof(TResult), true, 119 | allowAsyncImplementations); 120 | } 121 | 122 | // ...Parser failed? 123 | 124 | // Look for Sync 125 | foreach (var syncExecutionFactory in _syncExecutionFactories) 126 | try 127 | { 128 | _log?.LogDebug("Handling parsing failure using the Sync Execution Factory '{Factory}'.", syncExecutionFactory.GetType().Name); 129 | var executionResult = syncExecutionFactory.ExecuteParsingFailure(args, errors); 130 | if (executionResult.Handled) return executionResult.Result; 131 | } 132 | catch (OperationCanceledException) 133 | { 134 | throw; 135 | } 136 | catch (Exception e) 137 | { 138 | _log?.LogError(e, "Exception thrown while handling parsing failure using the Sync Execution Factory '{Factory}'.", syncExecutionFactory.GetType().Name); 139 | } 140 | 141 | // Look for Async 142 | if (allowAsyncImplementations) 143 | foreach (var asyncExecutionFactory in _asyncExecutionFactories) 144 | try 145 | { 146 | _log?.LogDebug( 147 | "Handling parsing failure using the Async Execution Factory '{Factory}'.", 148 | asyncExecutionFactory.GetType().Name); 149 | var executionResult = AsyncHelper.RunSync(async () => await asyncExecutionFactory.ExecuteParsingFailureAsync(args, errors, CancellationToken.None)); 150 | if (executionResult.Handled) return executionResult.Result; 151 | } 152 | catch (OperationCanceledException) 153 | { 154 | throw; 155 | } 156 | catch (Exception e) 157 | { 158 | _log?.LogError(e, 159 | "Exception thrown while handling parsing failure using the Async Execution Factory '{Factory}'.", asyncExecutionFactory.GetType().Name); 160 | } 161 | 162 | // Return Default 163 | return defaultResult; 164 | } 165 | 166 | /// 167 | /// Parse Command Line Arguments using . 168 | /// 169 | /// Command Line Arguments. 170 | /// Optional Parser Configuration Action. 171 | /// Default Result to return when parser was unable to parse out options. 172 | /// Fall back to and/or implementations asynchronous version are not available? 173 | /// Cancellation Token 174 | /// Result [code]. 175 | /// Options was found to be invalid (or there was an exception while validating the options) 176 | /// No handler for Command line Options. 177 | public async Task ParseArgumentsAsync(string[] args, Action? configuration = null, TResult? defaultResult = default, 178 | bool allowSyncImplementations = true, CancellationToken ctx = default) 179 | { 180 | // Create Parser 181 | using var parser = configuration == null 182 | ? new Parser() 183 | : new Parser(configuration); 184 | 185 | // Execute Parser 186 | var result = _commandLineOptionTypes.Length == 1 && _commandLineOptionTypes.All(i => !i.GetCustomAttributes().Any()) 187 | ? parser.ParseArguments(() => Activator.CreateInstance(_commandLineOptionTypes.First()), args) 188 | : parser.ParseArguments(args, _commandLineOptionTypes); 189 | var errors = ((result as NotParsed)?.Errors ?? []).ToArray(); 190 | if (result.Tag == ParserResultType.Parsed) 191 | _log?.LogInformation("Command Line Arguments Parser determined that the '{Type}' type is to be used for options.", result.TypeInfo.Current.Name); 192 | else 193 | _log?.LogError("Command Line Arguments Parser was unable to parse arguments."); 194 | 195 | // Parser Execute successfully? 196 | if (result.Tag == ParserResultType.Parsed) 197 | { 198 | // Validate Options 199 | if (_commandLineOptionsValidator != null && result.Value is ICommandLineOptions clo && !await _commandLineOptionsValidator.ValidateAsync(clo, ctx)) 200 | throw new CommandLineOptionsValidationException(clo); 201 | 202 | // Get Parsed Value 203 | if (result is Parsed parsed) 204 | { 205 | // Look for Async Types to execute 206 | foreach (var asyncExecutionFactory in _asyncExecutionFactories) 207 | try 208 | { 209 | _log?.LogDebug("Handling options '{Type}' using the Async Execution Factory '{Factory}'.", result.TypeInfo.Current.Name, asyncExecutionFactory.GetType().Name); 210 | var executionResult = await asyncExecutionFactory.ExecuteCommandAsync(args, result.TypeInfo.Current, parsed.Value, ctx); 211 | if (executionResult.Handled) return executionResult.Result; 212 | } 213 | catch (OperationCanceledException) 214 | { 215 | throw; 216 | } 217 | catch (Exception e) 218 | { 219 | _log?.LogError(e, "Exception thrown while handling options '{Type}' using the Async Execution Factory '{Factory}'.", result.TypeInfo.Current.Name, asyncExecutionFactory.GetType().Name); 220 | } 221 | 222 | // Look for Sync? 223 | if (allowSyncImplementations) 224 | foreach (var syncExecutionFactory in _syncExecutionFactories) 225 | try 226 | { 227 | _log?.LogDebug("Handling options '{Type}' using the Sync Execution Factory '{Factory}'.", result.TypeInfo.Current.Name, syncExecutionFactory.GetType().Name); 228 | var executionResult = 229 | syncExecutionFactory.ExecuteCommand(args, result.TypeInfo.Current, parsed.Value); 230 | if (executionResult.Handled) return executionResult.Result; 231 | } 232 | catch (OperationCanceledException) 233 | { 234 | throw; 235 | } 236 | catch (Exception e) 237 | { 238 | _log?.LogError(e, "Exception thrown while handling options '{Type}' using the Sync Execution Factory '{Factory}'.", result.TypeInfo.Current.Name, syncExecutionFactory.GetType().Name); 239 | } 240 | } 241 | 242 | // Throw exception if Parser ran but no service was found to handle the results. 243 | throw new NoExecuteCommandLineServiceFoundException(result.TypeInfo.Current, typeof(TResult), true, 244 | allowSyncImplementations); 245 | } 246 | 247 | // ...Parser failed? 248 | 249 | // Look for Async 250 | foreach (var asyncExecutionFactory in _asyncExecutionFactories) 251 | try 252 | { 253 | _log?.LogDebug( 254 | "Handling parsing failure using the Async Execution Factory '{Factory}'.", 255 | asyncExecutionFactory.GetType().Name); 256 | var executionResult = await asyncExecutionFactory.ExecuteParsingFailureAsync(args, errors, ctx); 257 | if (executionResult.Handled) return executionResult.Result; 258 | } 259 | catch (OperationCanceledException) 260 | { 261 | throw; 262 | } 263 | catch (Exception e) 264 | { 265 | _log?.LogError(e, 266 | "Exception thrown while handling parsing failure using the Async Execution Factory '{Factory}'.", asyncExecutionFactory.GetType().Name); 267 | } 268 | 269 | // Look for Sync 270 | if (allowSyncImplementations) 271 | foreach (var syncExecutionFactory in _syncExecutionFactories) 272 | try 273 | { 274 | _log?.LogDebug("Handling parsing failure using the Sync Execution Factory '{Factory}'.", syncExecutionFactory.GetType().Name); 275 | var executionResult = syncExecutionFactory.ExecuteParsingFailure(args, errors); 276 | if (executionResult.Handled) return executionResult.Result; 277 | } 278 | catch (OperationCanceledException) 279 | { 280 | throw; 281 | } 282 | catch (Exception e) 283 | { 284 | _log?.LogError(e, "Exception thrown while handling parsing failure using the Sync Execution Factory '{Factory}'.", syncExecutionFactory.GetType().Name); 285 | } 286 | 287 | // Return Default 288 | return defaultResult; 289 | } 290 | 291 | #endregion 292 | } --------------------------------------------------------------------------------