Good evening
For 5 years, M$ has not been willing to solve this simple issue:
System.CommandLine: Consider optional case insensitive parsing #133
How can we create command line tools by using System.CommandLine has great usability for customers that just work?
It’s a shame that small tools expect users to remember in which case a parameter has to be passed.
Thanks a lot, kind regards,
Thomas
The answer is pretty simple.
Unfortunately, pure greed makes Microsoft more and more unbearable and instead of offering professional solutions to the customers, small topics like “Consider optional case insensitive parsing” are discussed for 5 years and a basic, central packages like System.CommandLine
is kept in the beta stage for years instead of being professionally developed and finalized.
As it turns out, a Microsoft developer could have done this Job in about 60 lines of code by writing generic Middleware processor, as ScottArbeit showed here:
Scotts Comment and
Scotts example code in f#.
Here is a small example in c# which perfectly works: You can call the compiled App and all parameters are working as expected: --version
, --VerSion
, -v
and -V
public class Program {
private static readonly bool CaseInsensitive = true; // Enable case-insensitive parsing
static async Task<int> Main(string[] args) {
var rootCommand2 = new RootCommand("Sample app for System.CommandLine");
// Create the CommandLineBuilder with and add the Middleware
var builder = new CommandLineBuilder(rootCommand2)
// Set option verb & shortcut
.UseVersionOption("--version", "-v")
.AddMiddleware(CaseInsensitiveMiddleware, MiddlewareOrder.ExceptionHandler)
.UseDefaults();
var parser = builder.Build();
return await parser.InvokeAsync(args);
}
public static void CaseInsensitiveMiddleware(InvocationContext context) {
if (!CaseInsensitive) return;
var commandOptions = context.ParseResult.CommandResult.Command.Options
.Concat(context.ParseResult.RootCommandResult.Command.Options);
var allAliases = commandOptions.SelectMany(option => option.Aliases);
string[] GetCorrectTokenCase(string[] tokens) {
var newTokens = new List<string>(tokens.Length);
for (int i = 0; i < tokens.Length; i++) {
var token = tokens[i];
var matchingAlias = allAliases.FirstOrDefault
(alias =>
alias.Equals(token, StringComparison.InvariantCultureIgnoreCase));
if (matchingAlias != null) {
newTokens.Add(matchingAlias);
continue;
}
if (i > 0) {
var previousToken = tokens[i - 1];
var matchingOption = commandOptions.FirstOrDefault
(option =>
option.Aliases.Contains(previousToken, StringComparer.InvariantCultureIgnoreCase));
if (matchingOption != null) {
var completions = matchingOption.GetCompletions()
.Select(completion => completion.InsertText)
.ToArray();
if (completions.Length > 0) {
var matchingCompletion = completions.FirstOrDefault
(completion =>
token.Equals(completion, StringComparison.InvariantCultureIgnoreCase));
newTokens.Add(matchingCompletion ?? token);
}
else {
newTokens.Add(token);
}
}
else {
newTokens.Add(token);
}
}
else {
newTokens.Add(token);
}
}
return newTokens.ToArray();
}
var tokens = context.ParseResult.Tokens.Select(token => token.Value).ToArray();
string[] newTokens;
if (tokens.Length == 0) {
newTokens = Array.Empty<string>();
}
else if (tokens.Length == 1) {
newTokens = new[] { tokens[0].ToLowerInvariant() };
}
else {
if (tokens[0].StartsWith("-")) {
newTokens = tokens;
}
else {
newTokens = new[] { tokens[0].ToLowerInvariant(), tokens[1].ToLowerInvariant() }
.Concat(GetCorrectTokenCase(tokens.Skip(2).ToArray()))
.ToArray();
}
}
context.ParseResult = context.Parser.Parse(newTokens);
}
}