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

Invoke multiple commands in series #726

Open
vinchauhan opened this issue Aug 10, 2018 · 16 comments
Open

Invoke multiple commands in series #726

vinchauhan opened this issue Aug 10, 2018 · 16 comments
Labels
area/cobra-command Core `cobra.Command` implementations kind/feature A feature request for cobra; new or enhanced behavior lifecycle/needs-proposal For large features/bugs that need a proposal and community buy in triage/needs-info Needs more investigation from maintainers or more info from the issue provider

Comments

@vinchauhan
Copy link

I would like to see if multiple command execution can be triggered by providing the command names on the command line -

I see this issue but it actually triggers two command programmed to be executed from one program provide on the the command line.

#482

What I am trying to do it to execute two command once they are provided on the command line.

like -

mybuild.exe clean compile

It should execute clean command and after that compile command.

May be there is a way to do it ?

@Fjolnir-Dvorak
Copy link

The problem with that is that cobra allows subcommands which gradle or maven doesn't. How should cobra know if compile is a new command or a subcommand from clean?
There is the possibility that clean has the subcommand compile. Should mybuild clean compile be executed or mybuild clean and mybuild compile?
It would not be clear anymore. The easiest way would be to execute the program multiple times. If you want to have it in one line, you would have to use the shell-way of command concatenation. the problem with that is that you have to provide the flags each time, which could be substituted by providing a config file.
Bash: "mybuild clean && mybuild compile"
Powershell: "mybuild.exe clean & mybuild.exe compile"

@ghost
Copy link

ghost commented Dec 23, 2019

This is how I do that:

var cmd = &cobra.Command{
	Use:   "and -- COMMAND1 - COMMAND2",
	Short: "run multiple <my-cli> commands separated by -",
	Run: func(cobraCmd *cobra.Command, args []string) {
		var cmdParts []string
		var cmdList [][]string
		for _, arg := range args {
			if arg == "-" {
				if len(cmdParts) > 0 {
					cmdList = append(cmdList, cmdParts)
					cmdParts = []string{}
				}
			} else {
				cmdParts = append(cmdParts, arg)
			}
		}
		cmdList = append(cmdList, cmdParts)

		for _, cmdParts := range cmdList {
			cmdRoot.SetArgs(cmdParts)
			cmdRoot.Execute()
		}
	},
}
cmd.Flags().SetInterspersed(false)

and to run:

my-cli and command1 - command2 - command3
my-cli and -- command1 - command2 - command3
my-cli --global-options and -- command1 --command1-options command1Arguments - command2 - command3 --command3-options

@github-actions
Copy link

github-actions bot commented Apr 9, 2020

This issue is being marked as stale due to a long period of inactivity

@fkrauthan
Copy link

I would like this feature as well. The biggest Python CLI framework click supports this. It is very useful if you are trying to build build tools where a user might want to decide what they execute. E.g. ./mycommand clean build or ./mycommand build or ./mycommand clean build start. Using the - character as separator would be a very bad hack as it does not conform with any "normal" CLI execution.

@umarcor
Copy link
Contributor

umarcor commented Jul 15, 2020

@fkrauthan, would you mind elaborating on what you mean with "normal CLI execution"?

It feels to me that clean, build and start are clearly arguments of a single command. That's how I would implement it in any CLI (be it bash, Python, go, a makefile...). Hence, I'd say that your feature request can be reworded as allowing cobra subcommands to execute other subcommands, which is precisely what @idetoile solved/explained above. See also #908 and #724.

@fkrauthan
Copy link

@umarcor If you are trying to build a tool like gradle for example the current system will not work. The idea is that I can specify N number of functions that I want to execute. It is not necessarily the code or some command that would decide which sub command (or Main command) should be called. And I've never seen any other CLI tool use a separator character (like -) to separate different commands to be executed.

The type of feature I would love to see would follow the same (or similar implementation) to how for example Python Click does it (https://click.palletsprojects.com/en/7.x/commands/#multi-command-chaining). Which is a common way development CLI tools work (like above mentioned gradle or python's setup.py).

@umarcor
Copy link
Contributor

umarcor commented Jul 15, 2020

If you are trying to build a tool like gradle for example the current system will not work. The idea is that I can specify N number of functions that I want to execute.

Sure it will. See dbhi/run.

It is not necessarily the code or some command that would decide which sub command (or Main command) should be called.

It is the user who decides which subcommands (better named tasks/targets/procedures) to execute, by providing them as a list of args. The (sub)command which handles it is only reponsible for taking the input (strings) and converting them to function calls.

And I've never seen any other CLI tool use a separator character (like -) to separate different commands to be executed.

I believe that is because @idetoile wanted to preserve the possibility for each command to have args. It seems you don't need that because you are using arguments as makefile targets (i.e. standalone symbols/tokens). Then, you should focus on the following lines:

cmdRoot.SetArgs(cmdParts)
cmdRoot.Execute()

Note it is very similar to #724 (comment).

However, if your subcommands are not going to accept arguments (because the syntax you want doesn't allow so), I would suggest you to reconsider using subcommands in the first place. It feels that all the benefit you get from having subcommands is separate description messages. Apart from that, all the plumbing feels overkill. Instead, I would write a single command that accepts a list of strings (task names) and a map that translates those strings to a struct that contains a function and a description. With such an approach, you get the benefits of cobra and viper for easily using configuration files, generating a help message, etc. but you keep the complexity constrained to your needs.

The type of feature I would love to see would follow the same (or similar implementation) to how for example Python Click does it (click.palletsprojects.com/en/7.x/commands/#multi-command-chaining). Which is a common way development CLI tools work (like above mentioned gradle or python's setup.py).

That is exactly what you get with .Execute(), as shown by @idetoile and @marckhouzam.

@fkrauthan
Copy link

@umarcor No I explicitly want the capability of args. A real example of an application written (at the moment in Python) that I was going to port to Go.

./mytool --services=service1,service2 build start logs --services=service1

which would build and startup service1 and service2 but only listen to logs of service1

./mytool --services=service1,service2 build --with-ui start

which would build service1 and service2 including UI components and then start both services.

In all of this cases I would like to be able to specify the sub commands to run AND have the capability of providing arguments to each subcommand without having to use a workaround/hack of a separator sign.

@umarcor
Copy link
Contributor

umarcor commented Jul 15, 2020

Can you guarantee that all your subcommands have no additional subcommands? I.e. you only have the root command and a single level of subcommands in the hierarchy?

@fkrauthan
Copy link

In my particular case yes. I think the API could easily say if you enable this multi subcommand feature you can not have nested commands or something along that lines (I believe click does it that way).

@umarcor
Copy link
Contributor

umarcor commented Jul 15, 2020

So, if you guarantee that:

  • None of the subcommands has a subcommand.
  • None of the (sub)commands can accept "regular" positional arguments. Technically, you need to allow it for the root command, but in practice that's the list of strings that you are going to manipulate.
  • All labels must be provided using --key=val and NOT --key VAL.

Then, you can modify @idetoile's example to "break" commands when you get an arg with the first character being != '-' (instead of breaking on isolated -).

NOTE

What you are calling "args" is named "flags" in the context of cobra. Args are positional while flags are prepended with --.

@fkrauthan
Copy link

But that is exactly my point. I have to pretty much write a light command processor on-top of cobra. While I think it is a valid use-case to be provided out of the box by cobra itself (maybe even with an elegant solution for supporting subcommands in a chaining scenario). It's ok if you don't think its worth implementing I was just commenting that I would support a feature implement like this and provided some samples where in the real world CLI interfaces that would require this are used.

@umarcor
Copy link
Contributor

umarcor commented Jul 15, 2020

I see your point. However, I beg to differ. The "light command processor on-top of cobra" is, in fact, ~10 lines of code to split a single list of strings into multiple lists of strings. It's a single ordered for loop (two were used above, but that's not required) with a very light evaluation (always the first char). I can hardly imagine how it can be done easier... Just configuring whichever API that might be built-in (e.g. to use an explicit separator or not), would involve almost the same complexity. Moreover, given all the constraints, it would feel weird if it was a first class option in the root command, compared to the generally broad focus of the project. So, I'm not opposed to it, I just think it is not worth the effort because you can already do it easily.

I was just commenting that I would support a feature implement like this and provided some samples where in the real world CLI interfaces that would require this are used.

Nevertheless, I think that this and other code examples should be easier to find. Cobra is lacking any "Documentation site" apart from the API. Info is scattered in markdown files and in some issues. I believe the project would benefit from a site hosted at GitHub Pages which might contain snippets for users to reuse. See #985.

@fkrauthan
Copy link

@umarcor just one quick question. After looking at @idetoile solution it seems like a sub command called and was created to accomplish this. Is it possible to have this run logic as part of the root command or would that screw up the help generation? If that is not possible then I think that would be a reason for why it should be implemented in the core framework. If the logic however this can be moved to the root command itself then I agree that this might be a valid workaround for the time being.

@umarcor
Copy link
Contributor

umarcor commented Jul 16, 2020

My guess is that you can use it as part of the root command, but you need some "key" to skip the regular parsing. IIRC, the logic is as follows:

  • If the first argument is a flag of the root command, it is processed.
  • If the first argument is a subcommand, it is executed, and the remaining args/flags are passed to it.
  • If the first argument is neither a flag nor a subcommand, it and all the remaining args/flags are considered positional arguments. I.e., cobra does not support cmd --flag posarg --anotherflag.

So, you might need to have a first arg which is NOT a subcommand. Otherwise, when you write ./mytool --services=service1,service2 build start logs --services=service1, it will pass start logs --services=service1 as arguments of build, and that's not what you want.

I think that an easy solution would be to use e.g. ./mytool --services=service1,service2 -- build --someargforbuild=avalue start --startempty logs --services=service1. If that works (using -- without having to write any further logic), I think it is quite idiomatic.

Note that ./mytool --services=service1,service2 build and ./mytool --services=service1,service2 start will still work because root's own function is never executed when a subcommand is found. So, in a sense -- might be the API that you were looking for. However, take it with a grain of salt. I have not tried it.

EDIT

Of course, it that does not work, I agree with you: it'd be a legit request to have something done so it is possible.

Also note that -- should ALWAYS be the first argument you receive in the root command (if exists), because it's own flags are stripped before executing it's own function.

@fkrauthan
Copy link

Hmm yeah I've noticed that. I don't think that is a very elegant solution and would require people to use this tool differently then before (something I would like to prevent and instead have a drop in solution). I guess for now I will be stuck with the Python version unless someone gets to implement this feature in cobra.

@johnSchnake johnSchnake changed the title New Feature Invoke multiple commands in series Mar 8, 2022
@johnSchnake johnSchnake added area/cobra-command Core `cobra.Command` implementations kind/feature A feature request for cobra; new or enhanced behavior lifecycle/needs-proposal For large features/bugs that need a proposal and community buy in triage/needs-info Needs more investigation from maintainers or more info from the issue provider and removed kind/stale labels Mar 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/cobra-command Core `cobra.Command` implementations kind/feature A feature request for cobra; new or enhanced behavior lifecycle/needs-proposal For large features/bugs that need a proposal and community buy in triage/needs-info Needs more investigation from maintainers or more info from the issue provider
Projects
None yet
Development

No branches or pull requests

5 participants