commandlineparser / commandline

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

Parse Single dash option as dashdash option #685

Open Ducatel opened 4 years ago

Ducatel commented 4 years ago

Hi everyone,

I'm trying to parse a command line (imposed, no way to change it) which are like

myexe.exe  -Url=https://blabla.com/ --AppName=val1 -e testCase  -monoapp Open -Ctx "default ctx" 

As you can see, there is a lot of different case:

And I wasn't able to parse it because it seems that when there is just a dash option, it mustn't be a long name. ( I have a lot of UnknownOptionError which seem to come from that)

image

So I have a very simple code to test

[Verb("Open")]
public class CommandLineOptions
{
   [Option("Url", Separator = '=')]
   public string Url { get; set; }
   [Option("AppName", Separator = '=')]
   public string AppName { get; set; }
   [Option('e',"env", Separator = ' ')]
   public string Env { get; set; }
   [Option("monoapp")]
   public bool Monoapp { get; set; }
   [Option("Ctx", Separator = ' ')]
   public string Ctx { get; set; }
}

....... In another file but in the  same  assembly

static void Main(string[] args) 
{
        var commandLineOptions = new CommandLineOptionsOpen();
        var parser = new Parser(with =>
        {
            with.EnableDashDash = false; // tested as true, same result
            with.IgnoreUnknownArguments = false;
        });
        var parseResult = parser.ParseArguments<CommandLineOptionsOpen>(args).WithParsed<CommandLineOptionsOpen>(result => commandLineOptions = result);

        System.Console.WriteLine(parseResult.Tag); // NotParsed
        System.Console.WriteLine(commandLineOptions.Url); // Null like all others values

}

So I did something wrong ? There is a way to parse this command line ?

Thanks in advance for your help

rmunn commented 4 years ago

The EnableDashDash option has nothing to do with allowing -Url to be treated as --Url. What it enables is the standard handling of a bare -- arg (that is, an argument that's just two dashes and nothing else) as it's normally handled by the GNU getopt_long() function, where a bare -- argument means "everything after this point is a value, not an option, even if it starts with a hyphen".

As for treating a single - like a double -- as an option prefix, that's something this library will not do, because it's designed to mimic the behavior of GNU getopt, which requires a single - before single-charcter options, and a double -- before double-character options. And single-charcter options can be mixed, so that someprog -a -v -x can also be written as someprog -avx and that will have the exact same effect. This is a longstanding GNU getopt feature that is built into the behavior of CommandLineProcessor, and there's no way to change it. This is why -Url doesn't work: it's being treated as the combination of -U, -r and -l single-character options, none of which exist in your options class.

The only way to do what you're wanting to do is to pre-process your incoming args before handing them to the Parser, so that all the single-hyphen args that don't fit the getopt style are turned into double hyphens. I'd suggest something like this:

var processedArgs = args.Select(arg => arg.Replace("-Url", "--Url").Replace("-monoapp", "--monoapp").Replace("-Ctx", "--Ctx"));

Or better yet, use regular expressions and put a ^ in front of each of those patterns to ensure that it will only match at the start of each arg string. That way you won't accidentally turn a URL like "http://some.server/cool-monoapp-example" into "http://some.server/cool--monoapp-example" and get a 404 later on.

Then you'll be able to hand the processedArgs array to Parser and it should work for you.

BTW, the Separator property of Option doesn't do what you think it does. It's only for specifying a separator character on list-like arguments, e.g. if you want "--files=file1,file2,file3" to be automatically turned into a list you'd let your Files property be of type IEnumerable and set Separator=',' in the Options attribute. CommandLineParser will automatically understand either equals or space as being the separator for long-style arguments, so --arg=value and --arg value will both work on the commandline.

Ducatel commented 4 years ago

Arf ok... So the only way for me to use commandlineparser is to preprocess the command line. The thing is, I have something like 200 different options (and will continue to grow). Then Add regex for each seems to not be a maintainable solution :sweat_smile:

So do you know and alternative library which can help me ?

Ps: thanks for all of those explanation :wink:

moh-hassan commented 4 years ago

@Ducatel

I'm trying to parse a command line (imposed, no way to change it)

What is the OS you are using this commandline options? Do you pass these args to a 3rd party tool/engine?

moh-hassan commented 4 years ago

@rmunn We can plan to extend the library to support other standard commandline options beside GNU standard like:

Ducatel commented 4 years ago

I tried FluentCommandLineParser and PowerArgs and cannotable to parse the command line also.

@moh-hassan The main target are windows 10 x64 but can also have some Windows server. I'm acting as a middleware, I received some commands and I organize call to external software/API.

moh-hassan commented 4 years ago

@Ducatel

I'm acting as a middleware, I received some commands and I organize call to external software/API.

As you are acting as a middleware, you have a control to get your args as Gnu standard and then converting these options to match the external software/API.

Ducatel commented 4 years ago

@moh-hassan

As you are acting as a middleware, you have a control to get your args as Gnu standard and then converting these options to match the external software/API.

Of course I can manage the conversion of options, this is my work :D But I have no control on all input option. I don't understand why you thinks it's possible for me ?

I will try to find a generic way to convert the command line

Edit:

I find a quite simple generic way seem to work well. Do you see any possible problem with this ?

args = args.Select(arg => Regex.IsMatch(arg, "^-\\w{2,}") ? "-" + arg : arg ).ToArray();
tushev commented 3 years ago

@rmunn We can plan to extend the library to support other standard commandline options beside GNU standard like:

  • Using single dash only as Posix standard/ Powershell and Unity Engine.
  • Using forward slash to support legacy windows standard.

+1. It would be nice to support other standards.

ericnewton76 commented 3 years ago

For windows, its always been a problem that windows used / as the argument discriminator when used in POSIX situations. How does the library differentiate between creating a new directory /Url or downloading for the /Url specified?

Additionally, when you start introducing single dash options for long names, you create ambiguities that are unable to be resolved.

For example, if I have three boolean options, U for underline, r for display in red color, and l for justify the text left, these short options can be combined into -url If you also have an option called "Url" which would grab a url and print it, using the other -Url options, then you have ambiguity... what is the parser to do?

Supporting different standards is beyond the scope of this project. The CommandLineParser library is entirely designed to support the GNU getopt standard of argument parsing. If somebody decides to fork the project and continue to utilize the core parsing, property resolution and value placements, thats fine.

The GNU getopt standard is tried and true, has existed for over two decades, and it pretty reliable, even by current standards. I personally do not want to begin to support the myriad of ambiguous scenarios that arise from mixing and matching different standards.

...Just my 2cents...

MichaeIDietrich commented 3 years ago

I published a PR that brings the option to define the parsing behavior for command line options, whether to use single or double dashes: #767

Maybe you could consider this PR to raise the satisfaction for some of your library consumers.

KhloyannareK commented 1 year ago

I am also writing a parser and have exactly the same issue. I tried to process command line to change single dash to double dashes, but occasionally I find a new test case for which change is not correct :) also I was thinking of a way to disable flag merging to not have ambiguity like -long <=> -l -o -n -g.

Carsten-MaD commented 1 year ago

@rmunn We can plan to extend the library to support other standard commandline options beside GNU standard like:

  • Using single dash only as Posix standard/ Powershell and Unity Engine.
  • Using forward slash to support legacy windows standard.

I would like to comment in support of this planning. In my case, I am trying to use CommandLine for a Unity project, where a single dash in front of parameter names is unfortunately what is used. For now, I seem to be fine with the regex based conversion that is mentioned here: https://github.com/commandlineparser/commandline/issues/685#issuecomment-676275503