Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I use a custom HelpText for help/error output? #259

Closed
tmillican opened this issue Mar 21, 2018 · 2 comments
Closed

How can I use a custom HelpText for help/error output? #259

tmillican opened this issue Mar 21, 2018 · 2 comments

Comments

@tmillican
Copy link

More particularly, I'm trying to force a line break spacer between the copyright notice and the "USAGE:" line (and between "USAGE:" and "ERROR(S):". But in the general case, I can't figure out how to inject a custom HelpText instance into the output process. I've read #224, but this approach doesn't seem to actually work -- at least not in 2.2.1.

Example code:

namespace ClpTest
{
    using System;
    using System.Collections.Generic;
    using CommandLine;
    using CommandLine.Text;

    internal class Options
    {
        [Option('f', "foo", Required = true, HelpText = "Uses the specified foo.")]
        public string Foo { get; }

        [Option('b', "bar", Required = true, HelpText = "Uses the specified bar.")]
        public string Bar { get; }

        public Options(string foo, string bar)
        {
            Foo = foo;
            Bar = bar;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var parser = Parser.Default;
            var result = parser.ParseArguments<Options>(args)
                .WithParsed<Options>(opts => DoParsed(opts))
                .WithNotParsed(errs => DoError(errs));
        }

        internal static void DoParsed(Options opts)
        {
            Console.WriteLine($"DoParsed invoked with Foo={opts.Foo}, Bar={opts.Bar}.");
        }

        internal static void DoError(IEnumerable<Error> errs)
        {
            Console.WriteLine($"DoError() invoked.");
        }
    }
}

If you run this, you'll see that DoError(...) isn't invoked until after the help text is output. If you define a custom HelpText in DoError(...) and output it there, as suggested in #242 and elsewhere, you can get whatever output you'd like, but only after the default help text has already been printed.

After poking at the source code, it seems that Parser.ParseArguments<T>(...) invokes Parser.MakeParserResult<T>(...), which invokes Parser.DisplayHelp<T>(...), which ultimately invokes HelpText.AutoBuild<<T>(...). The only control the user has over this process (that I can determine) is the ParserSettings object used to construct the Parser. This lets you specify which TextWriter to use, and whether to print line breaks between options, but that's all.

As a workaround, I can specify a TextWriter object that blackholes the output and then substitute my own output in DoError(...), but it's pretty kludgey.

TL;DR: Help/error output occurs before ParseArguments<T>(...) returns, so nothing you subsequently do with WithUnparsed<T>(...) or MapResult<...>(...) can affect the output. Since HelpOptionAttribute went away, what's the injection point for customizing error output?

@nemec
Copy link
Contributor

nemec commented Mar 21, 2018

Hi @tmillican, your workaround is mostly the same as the solution in #242 - just set it to null instead of a black hole writer.

Parser myParser = new Parser(config => config.HelpWriter = null);

This is pretty much the standard customization point - if you want to change the default help output you need to suppress it and then print it yourself on error. The AutoBuild method does most of the work for you, but it does require you to capture the value from var result, something like this:

static void Main(...)
{
    // ... parsing here
    result
        .WithParsed<Options>(opts => DoParsed(opts))
        .WithNotParsed(errs => DoError(result, errs));
}


static void DoError(ParseResult<Options> result, IEnumerable<Error> errs)
{
    // helptext autobuild
}

@tmillican
Copy link
Author

tmillican commented Mar 21, 2018

Thanks for the reply.

In my actual project code I'm capturing the result, as you point out. I just couldn't tell if I was missing some direct way to furnish a "top level" HelpText object to the parser. I also hadn't realized that you could get away with setting HelpWriter to null. Out of curiosity, do does that cause most of AutoBuild's internal machinations to no-op? I haven't dug that deeply into the code.

It would be nice if we could get a page for help text customization on the wiki. Judging from several issues, it looks like I'm not the only one having trouble figuring out the standard methodology for this in 2.2.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants