cobrax

package module
v0.0.0-...-234b54a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 1, 2022 License: Apache-2.0 Imports: 10 Imported by: 0

README

Cobrax - Helpers for spf13/cobra

This project provides helpers for the commonly used spf13/cobra library. It specifically aims to make commands composable and reusable, as well as to make testing easier.

Installation

go get github.com/zepatrik/cobrax

Usage

Extensive documentation is available on https://pkg.go.dev/github.com/zepatrik/cobrax.

About

This project is based on all the small tweaks and helpers I have accumulated over the years of using cobra while working on Ory projects. This library might be transferred to the Ory organization in the future.

Documentation

Overview

Package cobrax helpers for the commonly used https://github.com/spf13/cobra library. It specifically aims to make commands composable and reusable, as well as to make testing easier.

Example (ExecDependency)

ExampleExecDependency shows how a cobra command can be executed like os/exec.Command, but without the OS overhead.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/spf13/cobra"
	"github.com/zepatrik/cobrax"
)

// NewKetoCmd returns the whole tree of Keto commands, including the server and client commands.
// This would be imported from [github.com/ory/keto/cmd.NewRootCmd].
func NewKetoCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "keto",
		Short: "Global and consistent permission and authorization server by Ory",
	}
}

// ExampleExecDependency shows how a cobra command can be executed like [os/exec.Command], but without the OS overhead.
func main() {
	serverCtx, serverCancel := context.WithCancel(context.Background())
	defer serverCancel()

	eg := cobrax.ExecBackgroundCtx(serverCtx, NewKetoCmd(), nil, nil, nil, "serve", "--config", "keto.yml")
	defer func() {
		fmt.Printf("Keto server exited with error: %v", eg.Wait())
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	cmd := cobrax.CommandExecutor{
		New:            NewKetoCmd,
		Ctx:            ctx,
		PersistentArgs: []string{"--read-remote", "localhost:4466"},
	}

	// Blocks until the command exits.
	statusCtx, statusCancel := context.WithTimeout(ctx, 10*time.Second)
	defer statusCancel()
	_, _, _ = cmd.ExecCtx(statusCtx, nil, "status", "--block")

	stdOut, _, err := cmd.Exec(nil, "check", "relation-tuple", "--namespace", "default", "--object", "article:1", "--relation", "view", "--subject", "user:1")
	if err != nil {
		fmt.Printf("Keto client gave unexpected error: %v", err)
		return
	}
	fmt.Printf("Keto client gave output: %s", stdOut)

	stdOut, _, err = cmd.Exec(nil, "check", "relation-tuple", "--namespace", "default", "--object", "article:1", "--relation", "view", "--subject", "user:2")
	// ...and so on
}
Output:

Example (ExecuteRoot)

ExampleExecuteRoot shows how to use the helpers for the spf13/cobra.Command.RunE and main function.

package main

import (
	"bufio"
	"fmt"
	"runtime/debug"

	"github.com/spf13/cobra"
	"github.com/zepatrik/cobrax"
)

// NewRootCmd creates a new root greetme command. This function takes care of all the flag initialization and registering all subcommands.
func NewRootCmd() *cobra.Command {
	var errorExitCode int

	cmd := &cobra.Command{
		Use:   "greetme",
		Short: "This is a friendly program to greet you.",
		RunE: func(cmd *cobra.Command, args []string) error {
			fmt.Fprintln(cmd.ErrOrStderr(), "Hello, what is your name?")
			name, err := bufio.NewReader(cmd.InOrStdin()).ReadString('\n')
			if err != nil {
				fmt.Fprintf(cmd.ErrOrStderr(), "Error reading name: %v", err)
				return cobrax.WithExitCode(cobrax.FailSilently(cmd), errorExitCode)
			}
			fmt.Fprintf(cmd.ErrOrStderr(), "Hello %s\n", name)
			return nil
		},
	}
	cmd.Flags().IntVar(&errorExitCode, "exit-code", 1, "Exit code to return on error")
	cmd.AddCommand(NewVersionCmd(""))

	return cmd
}

// NewVersionCmd returns a new version subcommand. This could be in a totally different package, and reused by multiple projects.
// The version can be passed as a string. If the version is empty, the version embedded by [runtime/debug.ReadBuildInfo] is used.
func NewVersionCmd(version string) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "version",
		Short: "Prints the version of this program.",
		RunE: func(cmd *cobra.Command, args []string) error {
			if version == "" {
				bi, ok := debug.ReadBuildInfo()
				if !ok {
					fmt.Fprintln(cmd.ErrOrStderr(), "No version information available.")
					return cobrax.FailSilently(cmd)
				}
				version = bi.Main.Version
			}
			fmt.Fprintf(cmd.OutOrStdout(), "Version: %s", version)
			return nil
		},
	}
	cmd.AddCommand(newVersionSubCmd())

	return cmd
}

// NewVersionSubCmd returns a new version subcommand. This would be in the same package as the version command.
// It could depend on the version command as a parent, e.g. to access persistent flags, and would therefore not be exported.
func newVersionSubCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "deeply-nested",
		Short: "This commmand is an example for a deeply nested subcommand.",
		RunE: func(cmd *cobra.Command, args []string) error {
			// Do stuff
			return nil
		},
	}
}

// ExampleExecuteRoot shows how to use the helpers for the [spf13/cobra.Command.RunE] and main function.
func main() {
	cobrax.ExecuteRootCommand(NewRootCmd())
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoPrintButFail = errors.New("this error should never be printed")

ErrNoPrintButFail is returned to detect a failure state that was already reported to the user in some way

Functions

func AddUsageTemplateFunc

func AddUsageTemplateFunc(name string, f interface{})

AddUsageTemplateFunc adds a template function to the usage template.

func AssertUsageTemplates

func AssertUsageTemplates(t TestingT, cmd *cobra.Command)

AssertUsageTemplates asserts that the usage string of the commands are properly templated.

func DisableUsageTemplating

func DisableUsageTemplating(cmds ...*cobra.Command)

DisableUsageTemplating resets the commands usage template to the default. This can be used to undo the effects of EnableUsageTemplating, specifically for a subcommand.

func EnableUsageTemplating

func EnableUsageTemplating(cmds ...*cobra.Command)

EnableUsageTemplating enables gotemplates for usage strings, i.e. cmd.Short, cmd.Long, and cmd.Example. The data for the template is the command itself. Especially useful are `.Root.Name` and `.CommandPath`. This will be inherited by all subcommands, so enabling it on the root command is sufficient.

func Exec

func Exec(cmd *cobra.Command, stdIn io.Reader, args ...string) (stdOut string, stdErr string, err error)

Exec runs the provided cobra command with the given reader as STD_IN and the given args. This function can also be used outside of tests.

func ExecBackgroundCtx

func ExecBackgroundCtx(ctx context.Context, cmd *cobra.Command, stdIn io.Reader, stdOut, stdErr io.Writer, args ...string) *errgroup.Group

ExecBackgroundCtx runs the cobra command in the background. This function can also be used outside of tests. Pass nil for stdIn, stdOur, or stdErr to use os.Std*.

func ExecCtx

func ExecCtx(ctx context.Context, cmd *cobra.Command, stdIn io.Reader, args ...string) (stdOut string, stdErr string, err error)

ExecCtx is the same as Exec but with a user-supplied context. This function can also be used outside of tests.

func ExecExpectedErr

func ExecExpectedErr(t TestingT, cmd *cobra.Command, args ...string) (stdErr string)

ExecExpectedErr is a test helper that assumes a failing run from Exec returning ErrNoPrintButFail.

func ExecExpectedErrCtx

func ExecExpectedErrCtx(ctx context.Context, t TestingT, cmd *cobra.Command, args ...string) (stdErr string)

ExecExpectedErrCtx is the same as ExecExpectedErr but with a user-supplied context.

func ExecNoErr

func ExecNoErr(t TestingT, cmd *cobra.Command, args ...string) (stdOut string)

ExecNoErr is a test helper that assumes a successful run from Exec.

func ExecNoErrCtx

func ExecNoErrCtx(ctx context.Context, t TestingT, cmd *cobra.Command, args ...string) string

ExecNoErrCtx is the same as ExecNoErr but with a user-supplied context.

func ExecuteRootCommand

func ExecuteRootCommand(cmd *cobra.Command)

ExecuteRootCommand executes the given command (usually the root command) and exits the process with a non-zero exit code if an error occurs. The error is only printed if it is not ErrNoPrintButFail, because in that case we assume the error was already printed. To customize the status code you can add an error to the error chain that implements the ExitCodeCarrier interface.

func ExecuteRootCommandContext

func ExecuteRootCommandContext(ctx context.Context, cmd *cobra.Command)

ExecuteRootCommandContext is the same as ExecuteRootCommand but with a user-supplied context.

func FailSilently

func FailSilently(cmd *cobra.Command) error

FailSilently is supposed to be used within a commands RunE function. It silences cobras default error handling and returns the ErrNoPrintButFail error.

func WithExitCode

func WithExitCode(err error, exitCode int) error

Types

type CommandExecutor

type CommandExecutor struct {
	New            func() *cobra.Command
	Ctx            context.Context
	PersistentArgs []string
}

CommandExecutor is a struct that can be used to execute a cobra command multiple times.

func (*CommandExecutor) Exec

func (c *CommandExecutor) Exec(stdin io.Reader, args ...string) (stdOut string, stdErr string, err error)

Exec runs the cobra command with the given reader as STD_IN and the given args appended to the persistent args.

func (*CommandExecutor) ExecBackground

func (c *CommandExecutor) ExecBackground(stdin io.Reader, stdOut, stdErr io.Writer, args ...string) *errgroup.Group

ExecBackground runs the cobra command in the background with the given reader as STD_IN and the given args appended to the persistent args.

func (*CommandExecutor) ExecBackgroundCtx

func (c *CommandExecutor) ExecBackgroundCtx(ctx context.Context, stdin io.Reader, stdOut, stdErr io.Writer, args ...string) *errgroup.Group

ExecBackgroundCtx is the same as [ExecBackground] but with a user-supplied context.

func (*CommandExecutor) ExecCtx

func (c *CommandExecutor) ExecCtx(ctx context.Context, stdin io.Reader, args ...string) (stdOut string, stdErr string, err error)

ExecCtx is the same as Exec but with a user-supplied context.

func (*CommandExecutor) ExecExpectedErr

func (c *CommandExecutor) ExecExpectedErr(t TestingT, args ...string) string

ExecExpectedErr is a test helper that assumes a failing run returning ErrNoPrintButFail. The args are appended to the persistent args.

func (*CommandExecutor) ExecNoErr

func (c *CommandExecutor) ExecNoErr(t TestingT, args ...string) string

ExecNoErr is a test helper that assumes a successful run. The args are appended to the persistent args.

type ExitCodeCarrier

type ExitCodeCarrier interface {
	error
	ExitCode() int
}

type TestingT

type TestingT interface {
	Errorf(format string, args ...interface{})
	FailNow()
	Helper()
}

TestingT is an interface excerpt of testing.TB.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL