fclp / fluent-command-line-parser

A simple, strongly typed .NET C# command line parser library using a fluent easy to use interface
Other
530 stars 86 forks source link

Support positional arguments that "correspond" to the configured parameters #121

Open astrohart opened 8 months ago

astrohart commented 8 months ago

For example, I am writing a console app that renames folders (after retrying 60 times; you know how sometimes, when you go to rename a folder, you cannot because another process is using it).

I wish to configure my console app to be called thus:

C:\>FolderRenamder "C:\Path\To\Current\Folder" "C:\New\Folder\Path"

(I know, I know, there is a move command, but again...I want to have a "retry.")

I've coded up my command-line parser thus:


public static ICommandLineInfo CommandLine(string[] args)
{
    ICommandLineInfo result = default;

    try
    {
        // Dump the collection args to the log
        DebugUtils.WriteLine(
            DebugLevel.Debug,
            $"Parse.CommandLine: args = '{args.ToSetString()}'"
        );

        var p = new FluentCommandLineParser<CommandLineInfo>();

        p.Setup(arg => arg.OldPathname)
         .As('o', "old-pathname")
         .Required();

        p.Setup(arg => arg.NewPathname)
         .As('n', "new-pathname")
         .Required();

        p.Setup(arg => arg.MaxRetries)
         .As('m', "max-retries")
         .SetDefault(60);

        p.SetupHelp("?", "--help")
         .Callback(PrintHelpMessage);

        if (args.Length == 0)
        {
            DebugUtils.WriteLine(
                DebugLevel.Error,
                "*** ERROR *** Zero command line argument(s) were passed.  The old and new folder paths must be passed, at least.  Showing Help message and quitting..."
            );

            p.HelpOption.ShowHelp(p.Options);
            return result;
        }

        var parseResults = p.Parse(args);

        if (parseResults.HasErrors ||
            !CommandLineInfoValidator.IsValid(p.Object)) return result;

        result = p.Object;
    }
    catch (Exception ex)
    {
        // dump all the exception info to the log
        DebugUtils.LogException(ex);

        result = default;
    }

    return result;
}

Assume that the method is in some static class and that all interfaces and properties are defined and imported as you might expect.  The issue I am having is, when I run the app with the _positional command-line arguments_ as shown above, I get that `parseResults.HasErrors` is `true`.  The `CommandLineParserResult.ErrorText` property is equal to `"Option 'o:old-pathname' parse error. option is required but was not specified.\r\nOption 'n:new-pathname' parse error. option is required but was not specified.\r\n"` (I just copied the value straight out of the debugger visualizer btw).

It would be nice to have a fluent method to call on my `Setup()` lines to tell `fclp` to just assume that the first element of `args` is indeed the value of the `OldFilename` and that the second value of the `args` is the `NewFilename`. 

That is, it would be cool if `fclp` supported _positional_ command-line arguments, where it could keep track of _in what order_ command-line options are configured, so that if I then simply pass stuff in in that same order, it will be assumed to be matched with whatever `Setup()` method was called when before `Parse()` is called.