Skip to content

Commit

Permalink
cli: add helpers for cwd and execpath (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo authored Jul 19, 2023
1 parent 4df9585 commit 06e2531
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 0 deletions.
46 changes: 46 additions & 0 deletions cli/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"unicode"
Expand Down Expand Up @@ -91,6 +92,15 @@ type Command interface {
// command, and returns them. This is most useful for testing where callers
// want to simulate inputs or assert certain command outputs.
Pipe() (stdin, stdout, stderr *bytes.Buffer)

// WorkingDir returns the absolute path of current working directory from
// where the command was started. All symlinks are resolved to their real
// paths.
WorkingDir() (string, error)

// ExecutablePath returns the absolute path of the CLI executable binary. All
// symlinks are resolved to their real values.
ExecutablePath() (string, error)
}

// ArgPredictor is an optional interface that [Command] can implement to declare
Expand Down Expand Up @@ -367,6 +377,42 @@ func (c *BaseCommand) Pipe() (stdin, stdout, stderr *bytes.Buffer) {
return
}

// WorkingDir returns the working directory. See [Command] for more information.
func (c *BaseCommand) WorkingDir() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get current working directory: %w", err)
}

symCwd, err := filepath.EvalSymlinks(cwd)
if err != nil {
return "", fmt.Errorf("failed to resolve symlinks for current working directory: %w", err)
}

abs, err := filepath.Abs(symCwd)
if err != nil {
return "", fmt.Errorf("failed to compute absolute path for current working directory: %w", err)
}

return abs, nil
}

// ExecutablePath returns the executable's path. See [Command] for more
// information.
func (c *BaseCommand) ExecutablePath() (string, error) {
pth, err := os.Executable()
if err != nil {
return "", fmt.Errorf("failed to get executable path: %w", err)
}

sym, err := filepath.EvalSymlinks(pth)
if err != nil {
return "", fmt.Errorf("failed to evaluate executable path symlink: %w", err)
}

return sym, nil
}

// buildCompleteCommands maps a [Command] to its flag and argument completion. If
// the given command is a [RootCommand], it recursively builds the entire
// complete tree.
Expand Down
26 changes: 26 additions & 0 deletions cli/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,32 @@ func TestBaseCommand_Prompt_Cancels(t *testing.T) {
}
}

func TestBaseCommand_WorkingDir(t *testing.T) {
t.Parallel()

var cmd RootCommand
dir, err := cmd.WorkingDir()
if err != nil {
t.Fatal(err)
}
if dir == "" {
t.Errorf("expected working dir to be defined")
}
}

func TestBaseCommand_ExecutablePath(t *testing.T) {
t.Parallel()

var cmd RootCommand
pth, err := cmd.ExecutablePath()
if err != nil {
t.Fatal(err)
}
if pth == "" {
t.Errorf("expected executable path to be defined")
}
}

type TestCommand struct {
BaseCommand

Expand Down

0 comments on commit 06e2531

Please sign in to comment.