There are multiple approaches to implementing a CLI using go. This is the basic structure of the CLI I developed which is mostly influenced by the docker CLI and I have added unit tests as well.
The first thing you need is to have CLI as an interface. This will be inside a package named "cli".
package cli
type Cli interface {
// Have interface functions here
sayHello() error
}
This will be implemented by 2 clis: HelloCli (Our real CLI) and MockCli (used for unit tests)
package cli
type HelloCli struct {
}
func NewHelloCli() *HelloCli {
cli := &HelloCli{
}
return cli
}
Here the HelloCli will implement sayHello function as follows.
package cli
func (cli *HelloCli) SayHello() error {
// Implement here
}
Similarly, there will be a mock cli in a package named test that would implement cli interface and it will also implement the sayHello function.
package test
type MockCli struct {
}
func NewMockCli() *HelloCli {
cli := &MockCli{
}
return cli
}
func (cli *MockCli) SayHello() error {
// Mock implementation here
}
Now I will show how the command is added. First I would have the main package and this is where I would add all the new commands.
package main
func newCliCommand(cli cli.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "foo <command>"
}
cmd.AddCommand(
newHelloCommand(cli),
)
return cmd
}
func main() {
helloCli := cli.NewHelloCli()
cmd := newCliCommand(helloCli)
if err := cmd.Execute(); err != nil {
// Do something here if execution fails
}
}
func newHelloCommand(cli cli.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "hello",
Short: "Prints hello",
Run: func(cmd *cobra.Command, args []string) {
if err := pkg.RunHello(cli, args[0]); err != nil {
// Do something if command fails
}
},
Example: " foo hello",
}
return cmd
}
Here, I have one command called hello. Next, I will have the implementation in a separate package called "pkg".
package pkg
func RunHello(cli cli.Cli) error {
// Do something in this function
cli.SayHello()
return nil
}
The unit tests will also be contained in this package in a file named hello_test.
package pkg
func TestRunHello(t *testing.T) {
mockCli := test.NewMockCli()
tests := []struct {
name string
}{
{
name: "my test 1",
},
{
name: "my test 2"
},
}
for _, tst := range tests {
t.Run(tst.name, func(t *testing.T) {
err := SayHello(mockCli)
if err != nil {
t.Errorf("error in SayHello, %v", err)
}
})
}
}
When you execute foo hello, the HelloCli will be passed to the sayHello() function and when you run unit tests, MockCli will be passed.
initfunction and global variables there at all?