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

Accessing cobra command structure internally (porting applications which use cobra to android) #2106

Open
0pcom opened this issue Feb 4, 2024 · 0 comments

Comments

@0pcom
Copy link

0pcom commented Feb 4, 2024

Apologies if this has been asked or answered before.

I've worked extensively with cobra for creating cli interfaces. One sort of creative approach I've taken on occasion is to re-invoke a program (on linux) with different arguments / flags using os.Args[0]. For example:

import("github.com/bitfield/script")

...

conf, _ := script.Exec(os.Args[0] + " cli config gen -ynN --nofetch").String()
_, err := script.Exec(os.Args[0] + " visor -a '" + conf + "'").Stdout()

This allows accessing subcommands from the code in what I consider to be a highly convenient way.

However, in the context of a mobile / android application, using ...Exec(os.Args[0]... does not work.
(in case you are wondering, the error is gojni executable not found in PATH)

I know that it's possible to do this from the code itself, however I note certain anomalies in attempting to figure out how to do so:

package main

import (
	"fmt"
	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{Use: "app", Short: "A simple command-line app"}

var greeting string

var greetCmd = &cobra.Command{
	Use:   "greet",
	Short: "Prints a greeting message",
	Run: func(cmd *cobra.Command, args []string) {
		name, _ := cmd.Flags().GetString("name")
		fmt.Printf("Hello, %s!\n", name)
	},
}

func init() {
	greetCmd.Flags().StringVarP(&greeting, "name", "n", "World", "Specify the name to greet")
	rootCmd.AddCommand(greetCmd)
}

func main() {
	if err := greetCmd.Execute(); err != nil {
		fmt.Println(err)
		return
	}
}

Now, I would suppose that this program would output as though the greetCmd was run, but this is not the case. It simply prints the main help menu

$ go run main.go 
A simple command-line app

Usage:
  app [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  greet       Prints a greeting message
  help        Help about any command

Flags:
  -h, --help   help for app

Use "app [command] --help" for more information about a command.

perhaps I can just reassign rootCmd to greetCmd like this?:

func init() {
greetCmd.Flags().StringVarP(&greeting, "name", "n", "World", "Specify the name to greet")
rootCmd.AddCommand(greetCmd)
rootCmd = greetCmd
}

or even rootCmd = &cobra.Command{}

But that changes nothing..

However, when I don't add greetCmd to rootCmd, it works more or less as intended:

func init() {
greetCmd.Flags().StringVarP(&greeting, "name", "n", "World", "Specify the name to greet")
//rootCmd.AddCommand(greetCmd)
//rootCmd = greetCmd
}

func main() {
if err := greetCmd.Execute(); err != nil {
fmt.Println(err)
return
}
}

output

$ go run main.go 
Hello, World!

I would think that doing this

rootCmd.AddCommand(greetCmd)
rootCmd = &cobra.Command{}

should effectively reset the rootCmd; however, this isn't the case.

So this presents a challenge in that it's not possible to undo rootCmd.AddCommand() once it's been done, unless there is something I'm missing?

As far as I can tell, by default the command which is not a child of another command but has children of it's own is the one which is invoked.

Is it possible to remove its children?

The larger issue

It's possible to compile a golang application into an .apk for android using gomobile. In order to port an existing application to android which uses cobra, basically the mobile ui can be included as a subcommand of the existing application. And the golang mobile libraries will still work on desktop, so the UI can be tested without an emulator or anything.

But on android, the subcommand containing the mobile UI needs to be more or less the default invocation of the application, because it's not possible to specify any subcommands or flags to the application when it's launched as an android app from an installed .apk.

For the program in question, I need basically to invoke two subcommands, preferably with flags, from the existing code. I hope it's possible to do this without selectively changing the command structure itself depending on operating system. It should be possible to override anything, but it looks more and more like the only alternative is to detect os as android and change the command structure from there. Such a change would present real challenges to testing and may not prove viable.

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

No branches or pull requests

1 participant