commandlineparser / commandline

The best C# command line parser that brings standardized *nix getopt style, for .NET. Includes F# support
MIT License
4.6k stars 480 forks source link

After customizing help generation, hep VERB does not work #633

Open p0wertiger opened 4 years ago

p0wertiger commented 4 years ago

Did this

static async Task Main( string[] args )
{
    _logger.Info(CommandLine.Text.HeadingInfo.Default);

    var parser = new Parser(p => { p.AutoVersion = false; p.CaseInsensitiveEnumValues = true;  });
    var parserResult = parser.ParseArguments<SyncOptions, QueryOptions, TokenOptions>(args);
    await parserResult.MapResult(
            (SyncOptions opts) => SyncAsync(opts),
            (QueryOptions opts) => QueryAsync(opts),
            (TokenOptions opts) => ShowTokenAsync(opts),
            errors => Task.FromResult(DisplayHelp(parserResult, errors))
        )
        .ContinueWith(t => {
            if ( System.Diagnostics.Debugger.IsAttached )
            {
                Console.WriteLine("Press enter...");
                Console.ReadLine();
            }
        });
}

private static int DisplayHelp<T>( ParserResult<T> result, IEnumerable<Error> errs )
{
    var helpText = CommandLine.Text.HelpText.AutoBuild(result, h =>
    {
        h.AdditionalNewLineAfterOption = false;
        h.AddNewLineBetweenHelpSections = true;
        h.AddEnumValuesToHelpText = true;
        h.AutoVersion = false;
        h.Heading = CommandLine.Text.HeadingInfo.Empty;
        h.MaximumDisplayWidth = 110;
        h.AddVerbs(typeof(SyncOptions), typeof(QueryOptions), typeof(TokenOptions));
        return CommandLine.Text.HelpText.DefaultParsingErrorsHandler(result, h);
    }, e => e,
    true);
    Console.WriteLine(helpText);
    return 1;
}

Before this I could type MyProgram help VERB and it would tell me the same as MyProgram VERB minus errors section. Now this gives me only verb list, like if help lost its first argument. This does not work either: MyProgram VERB help but this does: MyProgram VERB --help.

Am I doing something wrong here?

moh-hassan commented 4 years ago

starting v2.7+, there is a new simplified overload method:

 private static int DisplayHelp<T>(ParserResult<T> result, IEnumerable<Error> errs)
        {
            var helpText = CommandLine.Text.HelpText.AutoBuild(result, h =>
            {
                h.AdditionalNewLineAfterOption = false;
                h.AddNewLineBetweenHelpSections = true;
                h.AddEnumValuesToHelpText = true;
                h.AutoVersion = false;
                h.Heading = CommandLine.Text.HeadingInfo.Empty;
                h.MaximumDisplayWidth = 110;
                //  h.AddVerbs(typeof(SyncOptions), typeof(QueryOptions), typeof(TokenOptions));
                return h;  // only h
            });
            Console.WriteLine(helpText);
            return 1;
        }

Let me know if this working with you

DkAngelito commented 4 years ago

I was trying one of the examples on the FAQ section ([https://dotnetfiddle.net/IbwLbX]) and it seems that the help doesn't work for verbs. I was using the following list of options

new string[] { "help" }
            ,new string[] { "help clone" }
            ,new string[] { "help commit" }
            ,new string[] { "commit help" }
            ,new string[] { "clone help" }
            ,new string[] { "help clone" }
            ,new string[] { "clone -help" }
            ,new string[] { "clone --help" }
            ,new string[] { "clone -?" }
            ,new string[] { "?" }

But all of them fails except for the first one. Which of them should work?

Since On .netfiddler I can't see the Error output, I modified the __YourMain method as follows

    static int __YourMain(string[] args)
    {
        int exitcode;
        var helpWriter = new StringWriter ();
        var parser = new CommandLine.Parser (with => with.HelpWriter = helpWriter);

        exitcode = parser.ParseArguments<CommitOptions,CloneOptions>(args)
            .MapResult(
            (CommitOptions opts) => RunCommitCommand(opts),
            (CloneOptions opts) => RunCloneCommand(opts), 
            //(AddOptions opts) => RunAddCommand(opts), 
            (parserErrors) => DisplayHelp (parserErrors, helpWriter)
        );
        //this works because there's a Dummy verb "NextVerbOptions" class
        //exitcode = CommandLine.Parser.Default
        //  .ParseArguments<CommitOptions,CloneOptions>(args)
        //  .MapResult(
        //  (CommitOptions opts) => RunCommitCommand(opts),
        //  (CloneOptions opts) => RunCloneCommand(opts), 
        //  //(AddOptions opts) => RunAddCommand(opts), 
        //  (parserErrors) => 1
        //);

        return exitcode;
    }

    static int DisplayHelp (IEnumerable<Error> errs, TextWriter helpWriter) {
        //if (errs.IsVersion () || errs.IsHelp ())
            Console.WriteLine (helpWriter.ToString ());
        //else
        //  Console.Error.WriteLine (helpWriter.ToString ());
        return 1;
    }

I'm doing something wrong or I'm missing something here?

moh-hassan commented 4 years ago

@DkAngelito Just modify args as:

new string[] { "--help" } // --help,  show help for non verb optons
,new string[] { "clone", "--help" } // clone --help , show help for clone verb
,new string[] { "clone", "git://github.com/commandlineparser/commandline.git" } 
,new string[] { "add","--help" }  //add --help , show help for add verb
,new string[] { "add", "addfile.cs" }
,new string[] { "commit", "-m", "initial commit" }
,new string[] { "push", "-f" }
,new string[] { "push", "--force" }
,new string[] { "push" }

The concatenated args is the actuall command line. Wiki will be modified to a new modified version

Tsaukpaetra commented 4 years ago

I don't understand the above instructions. My heavily-cut adjustment from the example can be found here (My actual code is using a set of verb classes, but the setup here is the same)

What do I "modify args as"?

moh-hassan commented 4 years ago

My actual code is using a set of verb classes, but the setup here is the same

You should add VerbAttribute to your classes like:

[Verb("myverb")] //whatever name, here  verb is named 'myverb'
class Options
{
...
}

To display help for verbs use this syntax:

<MyVerbName> --help
# for the above verb, type:
myverb --help

I added a comment to explain args[] I modified your code here and adding verb attribute, and it's working fine.

Tsaukpaetra commented 4 years ago

Hmm. I was trying to use the help auto-verb, is that not possible with a custom help text?

moh-hassan commented 4 years ago

You can not customize auto generated help.

Tsaukpaetra commented 4 years ago

I don't want that, I want there to be the implicit "help" verb that uses the positional argument 0 as the verb to get help with.

i.e. help myverb gets auto translated to myverb --help

Do I just have to make this myself?

DkAngelito commented 4 years ago

I don't want that, I want there to be the implicit "help" verb that uses the positional argument 0 as the verb to get help with.

i.e. help myverb gets auto translated to myverb --help

Do I just have to make this myself?

That works by default you could try it

in this example from @moh-hassan new string[] { "clone", "--help" }

would be equivalent to new string[] { "help", "clone" }

Tsaukpaetra commented 4 years ago

That's what I'm saying is not working. Adjusting my example here, it just shows... nothing. Presumably because there is not help verb.

DkAngelito commented 4 years ago

Upgrade your example to the latest version (2.8+) and change your helptext class for the following and it should work

var helpText = HelpText.AutoBuild(result, h =>
{
    h.AdditionalNewLineAfterOption = false; //remove the extra newline between options
    h.Heading = "Myapp 2.0.0-beta"; //change header
    h.Copyright = "Copyright (c) 2019 Global.com"; //change copyrigt text
    return HelpText.DefaultParsingErrorsHandler(result, h);
}
//      , e => e); Remove this line 
); // add this one
Tsaukpaetra commented 4 years ago

That worked!

Just gotta get the errors lines to stop repeating and I'm golden...

JosKrause commented 4 years ago

Hi @Tsaukpaetra

You need to change the return HelpText.DefaultParsingErrorsHandler(result, h); to just return h; as the AutoBuild will already call this method internally.

sflanker commented 4 years ago

@DkAngelito Thank You!

// , e => e); Remove this line

This one line was what was throwing me off and I was banging my head on my keyboard. The overload with this should be marked obsolete and/or killed with fire. What a headache!

mcflux commented 3 years ago

Did this


static async Task Main( string[] args )
{
    _logger.Info(CommandLine.Text.HeadingInfo.Default);

    var parser = new Parser(p => { p.AutoVersion = false; p.CaseInsensitiveEnumValues = true;  });
    var parserResult = parser.ParseArguments<SyncOptions, QueryOptions, TokenOptions>(args);
    await parserResult.MapResult(
            (SyncOptions opts) => SyncAsync(opts),
            (QueryOptions opts) => QueryAsync(opts),
            (TokenOptions opts) => ShowTokenAsync(opts),
            errors => Task.FromResult(DisplayHelp(parserResult, errors))
        )
        .ContinueWith(t => {
            if ( System.Diagnostics.Debugger.IsAttached )
            {
                Console.WriteLine("Press enter...");
                Console.ReadLine();
            }
        });
}

How you got that async to work? I cannot see any async/await support at all.
nkoudelia commented 2 years ago

Did this:

static void Main(string[] args)
{
    Parser.Default
        .ParseArguments<ListItemsOptions, AddItemOptions>(args)
        .WithParsed(Run<ListItemsOptions>(ListItems))
        .WithParsed(Run<AddItemOptions>(AddItem));
}

private static Action<T> Run<T>(Func<T, Task> asyncAction) =>
    (T item) => asyncAction(item).GetAwaiter().GetResult();

private static async Task AddItem(AddItemOptions args)
{
    // do some async stuf...
}

private static async Task ListItems(ListItemsOptions args)
{
    // do more async stuf...
}