commandline icon indicating copy to clipboard operation
commandline copied to clipboard

Why does using --help do this?

Open ajtruckle opened this issue 7 years ago • 13 comments

Out of interest now when I use --help I get the help listed twice:

C:\Program Files (x86)\Meeting Schedule Assistant\OutlookCalIFConsole>outlookcalifconsole --help
OutlookCalIFConsole 2018.4.4.1
Copyright c  2017 - 2018

  -l, --list          Builds a list of available calendars. Eg: -l ".\CalendarList.xml"

  -m, --mode          Type of events to add to the calendar. Modes: mwb. Eg: -m mwb

  -p, --path          Path to the events data file. Eg: -p ".\EventsToAdd.xml"

  -t, --tokencache    Required. The location of the token cache data file. Eg: -t "file.dat"

  -e, --errorlog      Required. The location for the error log. Eg: -e "<path to error folder>"

  -s, --signout       Sign out from Outlook calendar

  --help              Display this help screen.

  --version           Display version information.

OutlookCalIFConsole 2018.4.4.1
Copyright c  2017 - 2018

  -l, --list          Builds a list of available calendars. Eg: -l
                      ".\CalendarList.xml"

  -m, --mode          Type of events to add to the calendar. Modes: mwb. Eg: -m
                      mwb

  -p, --path          Path to the events data file. Eg: -p ".\EventsToAdd.xml"

  -t, --tokencache    Required. The location of the token cache data file. Eg:
                      -t "file.dat"

  -e, --errorlog      Required. The location for the error log. Eg: -e "<path
                      to error folder>"

  -s, --signout       Sign out from Outlook calendar

  --help              Display this help screen.

  --version           Display version information.

Why might this be? If I use --version it is not listed twice.

I am using standard code to build the help:

        static int Main(string[] args)
        {
            var parserResult = CommandLine.Parser.Default.ParseArguments<Options>(args);

            parserResult.WithParsed<Options>(options => OnSuccessfulParse(options));
            parserResult.WithNotParsed<Options>(errs =>
            {
                var helpText = HelpText.AutoBuild(parserResult, h =>
                {
                    return HelpText.DefaultParsingErrorsHandler(parserResult, h);
                }, e =>
                {
                    return e;
                });
                Console.WriteLine(helpText);
                ReturnErrorCode = ErrorCode.CommandLineArguments;
            });

            return (int)ReturnErrorCode;
        }

Also, why doesn't the default help keyword have a short version of h?

Looking here:

       /// <summary>
        /// Creates a new instance of the <see cref="CommandLine.Text.HelpText"/> class,
        /// automatically handling verbs or options scenario.
        /// </summary>
        /// <param name='parserResult'>The <see cref="CommandLine.ParserResult{T}"/> containing the instance that collected command line arguments parsed with <see cref="CommandLine.Parser"/> class.</param>
        /// <param name="maxDisplayWidth">The maximum width of the display.</param>
        /// <returns>
        /// An instance of <see cref="CommandLine.Text.HelpText"/> class.
        /// </returns>
        /// <remarks>This feature is meant to be invoked automatically by the parser, setting the HelpWriter property
        /// of <see cref="CommandLine.ParserSettings"/>.</remarks>
        public static HelpText AutoBuild<T>(ParserResult<T> parserResult, int maxDisplayWidth = DefaultMaximumLength)
        {
            if (parserResult.Tag != ParserResultType.NotParsed)
                throw new ArgumentException("Excepting NotParsed<T> type.", "parserResult");

            var errors = ((NotParsed<T>)parserResult).Errors;

            if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError))
                return new HelpText(HeadingInfo.Default){MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine);

            if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError))
                return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, maxDisplayWidth: maxDisplayWidth);

            var err = errors.OfType<HelpVerbRequestedError>().Single();
            var pr = new NotParsed<object>(TypeInfo.Create(err.Type), Enumerable.Empty<Error>());
            return err.Matched
                ? AutoBuild(pr, current => DefaultParsingErrorsHandler(pr, current), e => e, maxDisplayWidth: maxDisplayWidth)
                : AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, true, maxDisplayWidth);
        }

It seems to me that it itself calls DefaultParsingErrorsHandler. Yet in my code it looks like we do it again. Is this a mistake in my code or something?

ajtruckle avatar Apr 05 '18 11:04 ajtruckle

Try removing the call to DefaultParsingErrorsHandler:

var helpText = HelpText.AutoBuild(parserResult, h => h, e =>
{
	return e;
});
Console.WriteLine(helpText);

nemec avatar Apr 05 '18 15:04 nemec

Thanks for the suggestion. I have just compiled using that code and tried --help and it is still doubled.

ajtruckle avatar Apr 05 '18 15:04 ajtruckle

Is that so? I tested it on the latest version just before posting the suggestion, so it's odd that the solution doesn't work for you. Below is what I tried.

void Main()
{
	var parserResult = CommandLine.Parser.Default.ParseArguments<Options>("--help".Split());

	parserResult.WithParsed<Options>(options => options.Dump());
	parserResult.WithNotParsed<Options>(errs =>
	{
	var helpText = HelpText.AutoBuild(parserResult, h => h, e =>
	{
		return e;
	});
	Console.WriteLine(helpText);

	});
}

class Options
{
	[Option('n', "name")]
	public string Name { get; set; }
}

nemec avatar Apr 05 '18 15:04 nemec

Then I am doing something wrong at my end. Please see this:

https://www.dropbox.com/s/hzm9k9x570tapa4/CommandLine.mp4?dl=0

These are my command options:

// Define a class to receive parsed values
    public class Options
    {
        [Option('l', "list", Required = false,  HelpText = "Builds a list of available calendars. Eg: -l \".\\CalendarList.xml\"")]
        public string CalendarListPath { get; set; }

        [Option('m', "mode", Required = false, HelpText = "Type of events to add to the calendar. Modes: mwb. Eg: -m mwb")]
        public string CalendarEventsMode { get; set; }

        [Option('p', "path", Required = false, HelpText = "Path to the events data file. Eg: -p \".\\EventsToAdd.xml\"")]
        public string CalendarEventsPath { get; set; }

        [Option('t', "tokencache", Required = true, HelpText = "The location of the token cache data file. Eg: -t \"file.dat\"")]
        public string TokenCachePath { get; set; }

        [Option('e', "errorlog", Required = true, HelpText = "The location for the error log. Eg: -e \"<path to error folder>\"")]
        public string ErrorLogFolder { get; set; }

        [Option('s', "signout", Required = false, HelpText = "Sign out from Outlook calendar")]
        public bool SignOut { get; set; }

        //[ParserState]
        //public IParserState LastParserState { get; set; }
    }

ajtruckle avatar Apr 05 '18 15:04 ajtruckle

It looks like you are doing everything right. If you run dir in that release folder, does the last modified date of the exe you're running match the last time you rebuilt the project?

nemec avatar Apr 05 '18 15:04 nemec

Yes

ajtruckle avatar Apr 05 '18 15:04 ajtruckle

Even if I clean the project and try a normal switch, like -l:

D:\My Programs\2017\OutlookCalIFConsole\OutlookCalIFConsole\bin\Release>outlookcalifconsole --l
OutlookCalIFConsole 2018.4.4.1
Copyright c  2017 - 2018
ERROR(S):
Option 'l, list' has no value.
Required option 't, tokencache' is missing.
Required option 'e, errorlog' is missing.

  -l, --list          Builds a list of available calendars. Eg: -l ".\CalendarList.xml"

  -m, --mode          Type of events to add to the calendar. Modes: mwb. Eg: -m mwb

  -p, --path          Path to the events data file. Eg: -p ".\EventsToAdd.xml"

  -t, --tokencache    Required. The location of the token cache data file. Eg: -t "file.dat"

  -e, --errorlog      Required. The location for the error log. Eg: -e "<path to error folder>"

  -s, --signout       Sign out from Outlook calendar

  --help              Display this help screen.

  --version           Display version information.

OutlookCalIFConsole 2018.4.4.1
Copyright c  2017 - 2018

  -l, --list          Builds a list of available calendars. Eg: -l
                      ".\CalendarList.xml"

  -m, --mode          Type of events to add to the calendar. Modes: mwb. Eg: -m
                      mwb

  -p, --path          Path to the events data file. Eg: -p ".\EventsToAdd.xml"

  -t, --tokencache    Required. The location of the token cache data file. Eg:
                      -t "file.dat"

  -e, --errorlog      Required. The location for the error log. Eg: -e "<path
                      to error folder>"

  -s, --signout       Sign out from Outlook calendar

  --help              Display this help screen.

  --version           Display version information.

Notice that they are rendered differently. The second is wrapped at shorter width and excludes the error descriptions.

ajtruckle avatar Apr 05 '18 15:04 ajtruckle

If I revert back to the older code then we get the "erros" listed twice, once in each. So for me, removing the call simply removes the errors from showing a second time but not the parsed options.

ajtruckle avatar Apr 05 '18 15:04 ajtruckle

OK, I sorted it. I changed to your code and I had to remove this line:

Console.WriteLine(helpText);

I did not realise that the helpText was sent to the console already for me. Doh!

Although, I still ahev my related question about maybe supporting -h and -? for help as standard. And, my other question about why using --version doesn't just show the version info.

ajtruckle avatar Apr 05 '18 16:04 ajtruckle

Although it works by removing the console writeline code, I am trying to find in your source where it must automatically send the info the the console window. Can’t find it.

And I don’t understand why you didn’t get duplicates since you did use that console line.

Has there been any changes to affect this? Is code newer than the nuget package?

ajtruckle avatar Apr 06 '18 04:04 ajtruckle

If I'm reading the source correctly (in Parser.cs), the console output happens when you call ParseArguments<T>. Ignoring the use Maybe wrappers, the call stack is:

ParseArguments<T> -> MakeParserResult<T> -> DisplayHelp<T> -> writer.Write

Where writer is ultimately bound to the Parser.HelpWriter property. Unfortunately, parsing and output are tightly coupled in CommandLineParser, so if you need to customize its output beyond the provided facilities (ex. setting the copyright string), you pretty much just have to squelch its output entirely and substitute your own. You can do that by setting ParserSettings.HelpWriter to null and pass that in when you construct your Parser<T>. Or set it to your own TextWriter implementation and try to "mutate" the output as it passes through, but that sounds like an awful, brittle mess.

tmillican avatar Apr 18 '18 16:04 tmillican

Nulling the HelpWriter works: var parserResult = new Parser(with => with.HelpWriter = null).ParseArguments<Options>(args); Instead of: var parserResult = CommandLine.Parser.Default.ParseArguments<Options>(args);

Sire avatar Jul 04 '18 19:07 Sire

Nulling the HelpWriter still shows a default help in my case :

public class Options
{
    [Value(0, MetaName = "instructions", HelpText = "Path to instructions file.")]
    public string InstructionsFilePath { get; set; }
}
var parser = new Parser(with => with.HelpWriter = null);
var parserResult = parser.ParseArguments<Options>(args);
Parser.Default.ParseArguments<Options>(args)
    .WithParsed(_start);

Calling myProgram.exe --help in a CLI returns :

MyProgram 1.0.0.0
Copyright ©  2021

  --help                   Display this help screen.

  --version                Display version information.

  instructions (pos. 0)    Path to instructions file.

While my understanding is that it should print nothing. Tested on 2.8.0

ArthurAttout avatar Mar 24 '21 22:03 ArthurAttout