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

Global options & always parsing options as 'multiple' #58

Closed
stefanjarina opened this issue Jun 4, 2020 · 8 comments
Closed

Global options & always parsing options as 'multiple' #58

stefanjarina opened this issue Jun 4, 2020 · 8 comments
Labels
question Further information is requested

Comments

@stefanjarina
Copy link

stefanjarina commented Jun 4, 2020

Hello,

first of all, I quite like this library, it is really easy to grasp, which is rare in .net world.

There are 2 features though which are a blocker for me to consider using it, so just want to ask if I missed something or not.

Problem 1

One feature which I think is missing is global or persistent options, or did I missed it?
Specified on parent command, accessible to child command (maybe dependency injection can be used here?)

some useful examples

./cmd --log-level=debug subcommand --local-option  # set loggin level for all subcommands
./ginit --repo gitlab init --project-type rust ./my_project # set that all subcommands will be run against gitlab
./cmd -q subcommand # no output for all subcommands, only return codes
./cmd --json fetch 34 --include-images # return a JSON string for all subcommands

One way to have global options might be to enable them at the end (before arguments). like e.g. 'npm' handles it

npm -g ls --depth 1   # is an equivalent to
npm ls --depth 1 -g

Problem 2

This library is always parsing options as multiple values, which I think is really unusual (and essentially blocks global options).
I can't remember used it like that in case cli supported commands and subcommands.

How I think this feature is mostly implemented:

./cmd -i test.txt -i test.txt  # used e.g. in docker cli
# or
./cmd -i test.txt,test.txt     # used in a lot of cli apps + it is native to how powershell handles it
# or
./cmd -i=test.txt,tests.txt   # used e.g. in git cli

Just wondering what was the reasoning for this implementation, because going through some modern big CLIs like (kubectl, docker, dotnet, hugo, git, github, az, npm, yarn, cargo, pip, choco, scoop, etc.) it was hard to find similar behaviour. At least for me it is simply not intuitive and more or less unexpected.

Thank you for answers,
Stefan

@domn1995
Copy link
Contributor

domn1995 commented Jun 5, 2020

I would solve Problem 1 by using inheritance. Essentially, put all the "global" options in an abstract base class and extend that base class for all your commands.

@stefanjarina
Copy link
Author

stefanjarina commented Jun 5, 2020

Hello, thank you a lot for answer, abstract class works and it solves Problem 1 that I can use it at the end of command without implementing it in every command.

Works:

-> dotnet run -- config get myKey -r Gitlab

Passed Global Option Repo: 'Gitlab'

Problem 2 however blocks in using it in the middle
Doesn't Work (and it is strange)

-> dotnet run -- config -r Github get myKey

Option --repo|-r expects a single value, but provided with multiple:
"Github" "get" "myKey"
Usage
  dotnet ginit.dll config [command] [options]

Options
  -r|--repo         Specify repository Valid values: "Github", "Gitlab", "Azure", "Bitbucket". Default: "Github".
  -h|--help         Shows help text.

Commands
  get               Get value of a configuration key

You can run `dotnet ginit.dll config [command] --help` to show help on a specific command.

I think it can be solved 2 ways:

1. Allow single

[CommandOption("name", "n", IsSingle = true, ....)] or [CommandOptionSingle("name", "n", ....)]

2. Allow to specify the # of arguments belonging to the option

[CommandOption("name", "n", ArgSize = 2)] or NArgs, NumberOfArguments NumberOfArgs, etc...

example:
python click library have it implemented in this way:

Sometimes, you have options that take more than one argument. For options, only a fixed number of arguments is supported. This can be configured by the nargs parameter. The values are then stored as a tuple.

@click.command()
@click.option('--pos', nargs=2, type=float)
def findme(pos):
    click.echo('%s / %s' % pos)

And on the command line:

$ findme --pos 2.0 3.0
2.0 / 3.0

I think, here might be problematic to pass value to subcommands, so for me it is enough that I can use globals at the end of the commands, however it is strange and people would expect that globals work anywhere in the command.

Once again, thanks.

@Tyrrrz
Copy link
Owner

Tyrrrz commented Jun 5, 2020

Regarding "Problem 1" I would also suggest default interface members feature introduced in C#8 which essentially allows multiple inheritance. You can use that to compose options/parameters in your commands.

Regarding "Problem 2" it's explained a little bit in readme and, to summarize, the answer is readability. With dotnet run -- config -r Github get myKey it's very unclear whether get and myKey are also part of -r or not, and you can only be sure if you know how the tool is implemented. With dotnet run -- config get myKey -r Gitlab it's clear and there's no ambiguity.

Most CLIs that I know are not picky about order and accept options mixed with parameters, so both approaches work. With CliFx the order is enforced for consistency.

Also, dotnet is an example of a CLI where order matters as well:

image

@Tyrrrz Tyrrrz added the question Further information is requested label Jun 5, 2020
@stefanjarina
Copy link
Author

Hi, thanks a lot, Problem 1 pretty much solved.
As for the Problem 2 I guess, that I will just make it absolutely clear in help that it have to be specified at the end of the command and that's it.
Thanks for your help.

@Tyrrrz
Copy link
Owner

Tyrrrz commented Jun 6, 2020

You're welcome!

@alirezanet
Copy link
Contributor

Regarding "Problem 1" I would also suggest default interface members feature introduced in C#8 which essentially allows multiple inheritance. You can use that to compose options/parameters in your commands.

this is a little bit confusing because clifx doesn't recognize default interface members in the commands! what did I miss!? can you explain a little bit?

@Tyrrrz Tyrrrz changed the title Global Options & always parsing options as 'multiple' Global options & always parsing options as 'multiple' Jan 4, 2022
@Tyrrrz
Copy link
Owner

Tyrrrz commented Jan 4, 2022

Regarding "Problem 1" I would also suggest default interface members feature introduced in C#8 which essentially allows multiple inheritance. You can use that to compose options/parameters in your commands.

this is a little bit confusing because clifx doesn't recognize default interface members in the commands! what did I miss!? can you explain a little bit?

I haven't tried that in a while, what happens exactly? Do default members function differently from inherited members in terms of reflection?

@alirezanet
Copy link
Contributor

alirezanet commented Jan 4, 2022

Yes, if you mean something like this

public interface IBaseOptions : ICommand
{
   [CommandOption("no-color", 'c', Description = "Disable color output")]
   public bool NoColor
   {
      get => !Logger.Colors;
      set => Logger.Colors = !value;
   }

   [CommandOption("quite", 'q', Description = "Disable console output")]
   public bool Quite
   {
      get => Logger.Quiet;
      set => Logger.Quiet = value;
   }

   [CommandOption("verbose", 'v', Description = "Enable verbose output")]
   public bool Verbose
   {
      get => Logger.Verbose;
      set => Logger.Verbose = value;
   }
}

public class SomeCommand: IBaseOptions {}

it doesn't work by default. you need explicit type casting first, these props belong to the IBaseOptions type, not SomeCommand
(this could be a great feature though :) )

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

No branches or pull requests

4 participants