gsscoder / commandline

Terse syntax C# command line parser for .NET with F# support
1.63k stars 293 forks source link

Using 2.1 in C# with Visual Studio #188

Closed diversteve closed 9 years ago

diversteve commented 9 years ago

I am trying to build a console app using CommandLine 2.1 in Visual Studio 2013 using C#. This is my first attempt to use it, so I was going to start out using the sample code. Unfortunately I find the documentation very hard to follow.

The steps I took are:

  1. Create my console app: bkupcopy.
  2. Installed the Command Line Parser Library 2.1 using NuGet.
  3. Created an Options.cs class file and included the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommandLine;
using CommandLine.Text;
using CommandLine.Core;
using CommandLine.Infrastructure;

namespace bkupcopy
{
class Options
{
    [Option('r', "read", Required = true,
        HelpText = "Input file to be processed.")]
    public string InputFile { get; set; }

    [Option('v', "verbose", DefaultValue = true,
        HelpText = "Prints all messages to standard output.")]
    public bool Verbose { get; set; }

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

    [HelpOption]
    public string GetUsage()
    {
        return HelpText.AutoBuild(this,
            (HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
    }
}
}
  1. Added the following code to my program.cs file: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text;

    namespace bkupcopy { class Program { static void Main(string[] args) { // see https://commandline.codeplex.com/ for information on using // the commandline tool to parse command-line arguments var options = new Options(); if (CommandLine.Parser.Default.ParseArguments(args, options)) { // Values are available here if (options.Verbose) Console.WriteLine("Filename: {0}", options.InputFile); } } } }

I can't build the project. I have the following errors: clperrors

Can you please tell me what I am doing wrong?

Many thanks!

gsscoder commented 9 years ago

@diversteve, you're using samples from 1.9.* stable. As stated in the wiki home every information is about that version.

At the moment 2.0.x-x info are condensed in one paragraph: this one.

Anyway fixing it is simple:

  1. Remove LastParserState and GetUsage completey.
  2. Invoke ParseArguments without passing options instance (don't build it)

Hope it helped. Please let me know...

diversteve commented 9 years ago

Thank you for the quick response! Awesome!

So I changed my code to:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; using CommandLine.Core; using CommandLine.Infrastructure;

namespace bkupcopy { class Options { [Option('r', "read", Required = true, HelpText = "Input file to be processed.")] public string InputFile { get; set; }

          [Option('v', "verbose", DefaultValue = true,
                 HelpText = "Prints all messages to standard output.")]
          public bool Verbose { get; set; }

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

          //[HelpOption]
          //public string GetUsage()
          //{
          //     return HelpText.AutoBuild(this,
          //            (HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
          //}
   }

}

And

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; // using CommandLine.Core; // using CommandLine.Infrastructure;

namespace bkupcopy { class Program { static void Main(string[] args) { // see https://commandline.codeplex.com/ for information on using the commandline tool to parse command-line arguments var options = new Options(); if (CommandLine.Parser.Default.ParseArguments(args) != null) { // Values are available here if (options.Verbose) Console.WriteLine("Filename: {0}", options.InputFile); } } } }

I then get the build time error Cannot implicitly convert type 'CommandLine.ParserResult' to 'bool' c:\users\slaforge\documents\visual studio 2013\Projects\bkupcopy\bkupcopy\Program.cs 19 8 bkupcopy

That is why I change the code from if (CommandLine.Parser.Default.ParseArguments(args)) to if (CommandLine.Parser.Default.ParseArguments(args) != null)

It builds cleanly, but now I am getting the error:

Unhandled Exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. Parameter name: types at CommandLine.Parser.ParseArguments(String[] args, Type[] types) at bkupcopy.Program.Main(String[] args) in c:\Users\slaforge\Documents\Visual Studio 2013\Projects\bkupcopy\bkupcopy\Program.cs:line 19

I tried to understand the documentation, but since I am not familiar with the old version, most of this makes no sense. I thought I understood how the options worked, and I really like what I found documented for the Option statement. But according to the documentation that you referenced for 2.x, I don’t see how to replicate many of the features. For example, how do I specify the HelpText? Or what are the steps for generating and displaying help text?

I am building my app with Visual Studio 2013 using .NET 4.5. Can I use version 1.9 of your app?

It would be great to be able to reference a working example from somewhere.

Thank you!

EDIT: I've removed quoted post. Giacomo.

gsscoder commented 9 years ago

@diversteve, change Main as above:

static int Main(string[] args) { // use int as return code
  var result = CommandLine.Parser.Default.ParseArguments(args);
  var exitCode = result
    .Return(opts => {
      if (opts.Verbose) Console.WriteLine("Filename: {0}", options.InputFile);
      return 0; // 0 means OK
      }, _ =>  1 // != 0 means NOT OK
   );
  return exitCode;
}

I insist you should read: https://github.com/gsscoder/commandline/wiki/Latest-Version.

ParseArguments<T>(string[]) returns a ParserResult<T> which can be of type Parsed<T> or NotParsed<T>.

In my example I've used Return<TSource,TResult>(...) for making easy gaining access to values through lambda functions (doing something with options and skipping with errors).

But you can check everything manually, also if you prefer:

var result = CommandLine.Parser.Default.ParseArguments(args);
switch (result.Tag) {
  case ParserResultType.Parsed:
    var parsed = (Parsed<Options>)result;
    var options = parsed.Value;
    // do your stuff here
    break;
  case ParserResultType.NotParsed:
    var notParsed = (NotParsed<Options>)result;
    var errors = notParsed.Errors;
    // do your stuff with errors here
    break;
}

Just remember that when you use the prebuilt Default instance, help screen is automatically handled and generated by the library.

P.S.: as a side note, if you handle issue using emails, please not include previous post... It could affect readability for users accessing info from GitHub web site. Thank you!

diversteve commented 9 years ago

Thank you.

I have my code mostly working. I did reference the wiki page before I contacted you the second time. I’m sorry, but that needs a lot of work. If I had not found the documentation for version 1.9 I would have never even attempted to try to use your program. I would not have known where to start.

I think I found a bug also. I have 2 Option items in my Options class that are marked as required. If I provide either one, then your program does not produce an error or show the help text. I have shown a sample below.

I do suspect that I am probably not creating the Options.cs correctly, but again I cannot really determine what it should look like from reading your documentation. I know that this is at least getting me close.

At least I can use it to parse my parameters, and I have been able to add code to verify that I got my required parameters. That’s all that I need for now.

Have a great day! Steve

Here is my Options.cs file: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; using CommandLine.Core; using CommandLine.Infrastructure;

namespace bkupcopy { class Options {

          [Option('s', "src", Required = true,
                 HelpText = "Source directory.")]
          public string srcDirectory { get; set; }

          [Option('d', "dest", Required = true,
                 HelpText = "Destination directory.")]
          public string destDirectory { get; set; }

          //[Option('v', "verbose", DefaultValue = true,
          //     HelpText = "Prints all messages to standard output.")]
          //public bool Verbose { get; set; }

   }

}

And here is my program.cs file: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text;

namespace bkupcopy { class Program { static void Main(string[] args) { var options = new Options();

                 var result = Parser.Default.ParseArguments<Options>(args);
                 if ((result.Value.srcDirectory == null) || (result.Value.destDirectory == null))
                 {
                       Console.WriteLine("Please provide all required parameters!");
                       return;
                 }

                 Console.WriteLine("Source = " + result.Value.srcDirectory);
                 Console.WriteLine("Destination = " + result.Value.destDirectory);

          }
   }

}

Finally, here is a few test runs, displaying what I think is the bug that I found: C:\Users\slaforge\Documents\Visual Studio 2013\Projects\bkupcopy\bkupcopy\bin\Debug>bkupcopy --help bkupcopy 1.0.0.0 Copyright c 2015 -s, --src Required. Source directory. -d, --dest Required. Destination directory. --help Display this help screen.

Please provide all required parameters!

C:\Users\slaforge\Documents\Visual Studio 2013\Projects\bkupcopy\bkupcopy\bin\Debug>bkupcopy bkupcopy 1.0.0.0 Copyright c 2015

ERROR(S): Required option 'src, s' is missing. Required option 'dest, d' is missing.

-s, --src Required. Source directory. -d, --dest Required. Destination directory. --help Display this help screen.

Please provide all required parameters!

C:\Users\slaforge\Documents\Visual Studio 2013\Projects\bkupcopy\bkupcopy\bin\Debug>bkupcopy -s "c:\inetpub\wwwroot" Please provide all required parameters! <== did not flag that I did not provide the –d parameter!

C:\Users\slaforge\Documents\Visual Studio 2013\Projects\bkupcopy\bkupcopy\bin\Debug>bkupcopy -s "c:\inetpub\wwwroot" -d "c:\bkups" Source = c:\inetpub\wwwroot Destination = c:\bkups

Steve LaForge Lead Network Engineer Sodexo Clinical Technology Management 615 844 8828 (Office) 413 383 8362 (Fax) Steve.LaForge@sodexo.commailto:Steve.LaForge@sodexo.com

Sodexo A world leader in food and facilities management services. www.sodexoUSA.comhttp://www.sodexousa.com/ Join the fight against hunger: www.helpstophunger.orghttp://www.helpstophunger.org/

This message is confidential and the property of Sodexo. It may also be privileged and otherwise protected by work product immunity or other legal rules. If you have received it by mistake please let us know by reply and then delete it from your system. You should not copy the message or disclose its contents to anyone.

From: Giacomo Stelluti Scala [mailto:notifications@github.com] Sent: Wednesday, July 08, 2015 10:59 AM To: gsscoder/commandline Cc: Laforge, Steve Subject: Re: [commandline] Using 2.1 in C# with Visual Studio (#188)

@diverstevehttps://github.com/diversteve, change Main as above:

static int Main(string[] args) { // use int as return code

var result = CommandLine.Parser.Default.ParseArguments(args);

var exitCode = result

.Return(opts => {

  if (opts.Verbose) Console.WriteLine("Filename: {0}", options.InputFile);

  return 0; // 0 means OK

  }, _ =>  1 // != 0 means NOT OK

);

return exitCode;

}

I insist you should read: https://github.com/gsscoder/commandline/wiki/Latest-Version.

ParseArguments(string[]) returns a ParserResult which can be of type Parsed or NotParsed.

In my example I've used Return<TSource,TResult>(...) for making easy gaining access to values through lambda functions (doing something with options and skipping with errors).

But you can check everything manually, also if you prefer:

var result = CommandLine.Parser.Default.ParseArguments(args);

switch (result.Tag) {

case ParserResultType.Parsed:

var parsed = (Parsed<Options>)result;

var options = parsed.Value;

// do your stuff here

break;

case ParserResultType.NotParsed:

var notParsed = (NotParsed<Options>)result;

var errors = notParsed.Errors;

// do your stuff with errors here

}

Just remember that when you use the prebuilt Default instance, help screen is automatically handled and generated by the library.

— Reply to this email directly or view it on GitHubhttps://github.com/gsscoder/commandline/issues/188#issuecomment-119635021.


This e-mail, attachments included, is confidential. It is intended solely for the addressees. If you are not an intended recipient, any use, copy or diffusion, even partial of this message is prohibited. Please delete it and notify the sender immediately. Since the integrity of this message cannot be guaranteed on the Internet, SODEXO cannot therefore be considered liable for its content.

Ce message, pieces jointes incluses, est confidentiel. Il est etabli a l'attention exclusive de ses destinataires. Si vous n'etes pas un destinataire, toute utilisation, copie ou diffusion, meme partielle de ce message est interdite. Merci de le detruire et d'en avertir immediatement l'expediteur. L'integrite de ce message ne pouvant etre garantie sur Internet, SODEXO ne peut etre tenu responsable de son contenu.

gsscoder commented 9 years ago

I'm glad you was able to start using the library now! :+1:

What you reported here (off-topic, every issue must regard a single topic) it's the predefined behaviour. Two required options -> two required options errors when omitted. What's wrong with that?

I've completed a running demo: https://github.com/gsscoder/commandline/tree/master/demo/ReadText.Demo. Hope it will be useful to you and other users too.

Bye, Giacomo

nemec commented 9 years ago
  1. @diversteve unless I'm understanding you wrong, neither of those two test runs replicate the wrong behavior you're describing, where $ bkupcopy -s source.txt displays no errors. Am I correct? Because the two examples, one with --help and the other with blank arguments, seem to have the correct behavior.
  2. I'm curious to know what the value of result.Tag is in your Main method when you call --help. If it's NotParsed, then you should use that as an indicator to not continue with your program logic. Similarly, you should test that value when only one of the two required parameters are provided.
diversteve commented 9 years ago

Thanks for the follow-up Dan.

To me, this test is an obvious error in the code:

C:\Users\slaforge\Documents\Visual Studio 2013\Projects\bkupcopy\bkupcopy\bin\Debug>bkupcopy -s "c:\inetpub\wwwroot" Please provide all required parameters! <== did not flag that I did not provide the –d parameter!

My Options class (see below) has both –s and –d defined as required. Since I did not specify –d, it should have flagged it as an error and produced the appropriate error messages. In one of the responses yesterday, I may have misunderstood the response, but I read it to say that it was intentional that you can only have 1 required parameter. If that is true, it seems like a severe shortcoming.

As for the result.Tag, I cannot reference it in my project. I changed my program.cs to what is below. I cannot build my project using this code because I get the error:

'CommandLine.ParserResult' does not contain a definition for 'Tag' and no extension method 'Tag' accepting a first argument of type 'CommandLine.ParserResult' could be found (are you missing a using directive or an assembly reference?)

I have a reference to the CommandLine.dll in my project, and using statements for both CommandLine and CommandLine.Text. I installed CommandLine using NuGet by going to Tools > NuGet Package Manager > Manage NuGet Packages for solution… , then searched for ‘Command Line Parser Library’ and selected Command Line Parser Library 2.1.

Thank you!

Program.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; // using CommandLine.Core; // using CommandLine.Infrastructure;

namespace bkupcopy { class Program { static void Main(string[] args) {

                 var result = CommandLine.Parser.Default.ParseArguments(args);
                 // var result = Parser.Default.ParseArguments<Options>(args);
                 switch (result.Tag)
                 {
                       case ParserResultType.Parsed:
                              var parsed = (Parsed<Options>)result;
                              var options = parsed.Value;

                              // do your stuff here
                              string src = options.srcDirectory;
                              string dest = options.destDirectory;
                              Console.WriteLine(string.Format("Source = {0}, Dest = {1}", src, dest));
                              break;
                       case ParserResultType.NotParsed:
                              var notParsed = (NotParsed<Options>)result;
                              var errors = notParsed.Errors;
                              // do your stuff with errors here
                              return;
                              break;
                 }

          }
   }

}

Options.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; using CommandLine.Core; using CommandLine.Infrastructure;

namespace bkupcopy { class Options {

          [Option('s', "src", Required = true,
                 HelpText = "Source directory.")]
          public string srcDirectory { get; set; }

          [Option('d', "dest", Required = true,
                 HelpText = "Destination directory.")]
          public string destDirectory { get; set; }

          //[Option('v', "verbose", DefaultValue = true,
          //     HelpText = "Prints all messages to standard output.")]
          //public bool Verbose { get; set; }

   }

}

Steve LaForge Lead Network Engineer Sodexo Clinical Technology Management 615 844 8828 (Office) 413 383 8362 (Fax) Steve.LaForge@sodexo.commailto:Steve.LaForge@sodexo.com

Sodexo A world leader in food and facilities management services. www.sodexoUSA.comhttp://www.sodexousa.com/ Join the fight against hunger: www.helpstophunger.orghttp://www.helpstophunger.org/

This message is confidential and the property of Sodexo. It may also be privileged and otherwise protected by work product immunity or other legal rules. If you have received it by mistake please let us know by reply and then delete it from your system. You should not copy the message or disclose its contents to anyone.

From: Dan Nemec [mailto:notifications@github.com] Sent: Thursday, July 09, 2015 12:27 AM To: gsscoder/commandline Cc: Laforge, Steve Subject: Re: [commandline] Using 2.1 in C# with Visual Studio (#188)

  1. @diverstevehttps://github.com/diversteve unless I'm understanding you wrong, neither of those two test runs replicate the wrong behavior you're describing, where $ bkupcopy -s source.txt displays no errors. Am I correct? Because the two examples, one with --help and the other with blank arguments, seem to have the correct behavior.
  2. I'm curious to know what the value of result.Tag is in your Main method when you call --help. If it's NotParsed, then you should use that as an indicator to not continue with your program logic. Similarly, you should test that value when only one of the two required parameters are provided.

— Reply to this email directly or view it on GitHubhttps://github.com/gsscoder/commandline/issues/188#issuecomment-119822050.


This e-mail, attachments included, is confidential. It is intended solely for the addressees. If you are not an intended recipient, any use, copy or diffusion, even partial of this message is prohibited. Please delete it and notify the sender immediately. Since the integrity of this message cannot be guaranteed on the Internet, SODEXO cannot therefore be considered liable for its content.

Ce message, pieces jointes incluses, est confidentiel. Il est etabli a l'attention exclusive de ses destinataires. Si vous n'etes pas un destinataire, toute utilisation, copie ou diffusion, meme partielle de ce message est interdite. Merci de le detruire et d'en avertir immediatement l'expediteur. L'integrite de ce message ne pouvant etre garantie sur Internet, SODEXO ne peut etre tenu responsable de son contenu.

gsscoder commented 9 years ago

I'll check with a unit test...

A bug could be present in alpha release, but it obviously not intentional in such case...

gsscoder commented 9 years ago

I'm working on a dev, branch to fix it. When req are > 1 there's a bug. It was re-introduced with a previous fix and lack of test coverage let it to left undiscovered.

@diversteve, I did not understand what you were saying... Now I'have.

I'll tell here when I'll merge the fix to master.

gsscoder commented 9 years ago

@diversteve, thanks for reporting. Now it's fixed. You need to compile from sources or wait 10-20 min, and pick the NuGet release.

diversteve commented 9 years ago

I uninstalled the NuGet package and tried to install it again. It only shows the build that I already had. I don’t see a new distribution. I know you asked me to wait 10-20 minutes, but it has been over 2 hours now.

Also, will the fact that I cannot reference result.Tag be fixed with this new build, or is there something else that I need to do to my project so that I can build my project and use result.Tag to determine the success / failure of the command line parse?

Many thanks! Steve


This e-mail, attachments included, is confidential. It is intended solely for the addressees. If you are not an intended recipient, any use, copy or diffusion, even partial of this message is prohibited. Please delete it and notify the sender immediately. Since the integrity of this message cannot be guaranteed on the Internet, SODEXO cannot therefore be considered liable for its content.

Ce message, pieces jointes incluses, est confidentiel. Il est etabli a l'attention exclusive de ses destinataires. Si vous n'etes pas un destinataire, toute utilisation, copie ou diffusion, meme partielle de ce message est interdite. Merci de le detruire et d'en avertir immediatement l'expediteur. L'integrite de ce message ne pouvant etre garantie sur Internet, SODEXO ne peut etre tenu responsable de son contenu.

gsscoder commented 9 years ago

Try later or go directly to Package Manager Console and type:

Install-Package CommandLineParser -Version 2.0.61-alpha
diversteve commented 9 years ago

Perfect! It works. I have put my working code below (I did not include the Options.cs file again since it has not changed since the last message. Maybe you can use it as sample code for future users.

Thank you, and have a great day!

Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommandLine; using CommandLine.Text;

namespace bkupcopy { class Program { static void Main(string[] args) { bool parseError = false; var result = Parser.Default.ParseArguments(args); switch (result.Tag) { case ParserResultType.Parsed: var parsed = (Parsed)result; var options = parsed.Value;

                              // do your stuff here
                              string src = options.srcDirectory;
                              string dest = options.destDirectory;
                              Console.WriteLine(string.Format("Source = {0}, Dest = {1}", src, dest));
                              break;
                       case ParserResultType.NotParsed:
                              parseError = true;
                              var notParsed = (NotParsed<Options>)result;
                              var errors = notParsed.Errors;
                              // do your stuff with errors here
                              Console.WriteLine("bkupcopy terminated due to errors.");
                              break;
                 }
                 if (parseError) return;
          }
   }

}


This e-mail, attachments included, is confidential. It is intended solely for the addressees. If you are not an intended recipient, any use, copy or diffusion, even partial of this message is prohibited. Please delete it and notify the sender immediately. Since the integrity of this message cannot be guaranteed on the Internet, SODEXO cannot therefore be considered liable for its content.

Ce message, pieces jointes incluses, est confidentiel. Il est etabli a l'attention exclusive de ses destinataires. Si vous n'etes pas un destinataire, toute utilisation, copie ou diffusion, meme partielle de ce message est interdite. Merci de le detruire et d'en avertir immediatement l'expediteur. L'integrite de ce message ne pouvant etre garantie sur Internet, SODEXO ne peut etre tenu responsable de son contenu.

gsscoder commented 9 years ago

@diversteve, I'm happy you can proceed with your work now! :+1: Have nice day!