From 518a0fa4422cff1625cb7b676f40c4bc0a42eed9 Mon Sep 17 00:00:00 2001
From: DeedleFake <deedlefake@users.noreply.github.com>
Date: Wed, 18 Dec 2024 15:31:36 -0500
Subject: [PATCH] feat(golang-rewrite): refactor completion code and move all
 packages except `cmd` into `internal`

* Refactor and move `completion` package to `internal/completions`
* Move `cli` and `repotest` packages to `internal/cli`
* Use passed version information for `--version` flag
* Add simple test for completions package
---
 cmd/asdf/main.go                              |  2 +-
 {cli => internal/cli}/cli.go                  | 51 +++++--------------
 {cli => internal}/completions/asdf.bash       |  0
 .../completions/asdf.elvish                   |  0
 {cli => internal}/completions/asdf.fish       |  0
 .../completions/asdf.nushell                  |  0
 {cli => internal}/completions/asdf.zsh        |  0
 internal/completions/completions.go           | 40 +++++++++++++++
 internal/completions/completions_test.go      | 30 +++++++++++
 internal/execenv/execenv_test.go              |  2 +-
 internal/git/git_test.go                      |  2 +-
 internal/help/help_test.go                    |  2 +-
 internal/installs/installs_test.go            |  2 +-
 internal/pluginindex/pluginindex_test.go      |  2 +-
 internal/plugins/plugins_test.go              |  2 +-
 {repotest => internal/repotest}/repotest.go   |  0
 internal/resolve/resolve_test.go              |  2 +-
 internal/shims/shims_test.go                  |  2 +-
 internal/versions/versions_test.go            |  2 +-
 scripts/lint.bash                             |  6 +--
 20 files changed, 95 insertions(+), 52 deletions(-)
 rename {cli => internal/cli}/cli.go (97%)
 rename {cli => internal}/completions/asdf.bash (100%)
 rename cli/completions/asdf.elv => internal/completions/asdf.elvish (100%)
 rename {cli => internal}/completions/asdf.fish (100%)
 rename cli/completions/asdf.nu => internal/completions/asdf.nushell (100%)
 rename {cli => internal}/completions/asdf.zsh (100%)
 create mode 100644 internal/completions/completions.go
 create mode 100644 internal/completions/completions_test.go
 rename {repotest => internal/repotest}/repotest.go (100%)

diff --git a/cmd/asdf/main.go b/cmd/asdf/main.go
index 85c958e5..997b062f 100644
--- a/cmd/asdf/main.go
+++ b/cmd/asdf/main.go
@@ -1,7 +1,7 @@
 // Main entrypoint for the CLI app
 package main
 
-import "github.com/asdf-vm/asdf/cli"
+import "github.com/asdf-vm/asdf/internal/cli"
 
 // Replaced with the real version during a typical build
 var version = "v-dev"
diff --git a/cli/cli.go b/internal/cli/cli.go
similarity index 97%
rename from cli/cli.go
rename to internal/cli/cli.go
index 0a2ccb91..afb9d976 100644
--- a/cli/cli.go
+++ b/internal/cli/cli.go
@@ -2,7 +2,6 @@
 package cli
 
 import (
-	_ "embed"
 	"errors"
 	"fmt"
 	"io"
@@ -14,6 +13,7 @@ import (
 	"strings"
 	"text/tabwriter"
 
+	"github.com/asdf-vm/asdf/internal/completions"
 	"github.com/asdf-vm/asdf/internal/config"
 	"github.com/asdf-vm/asdf/internal/exec"
 	"github.com/asdf-vm/asdf/internal/execenv"
@@ -52,7 +52,7 @@ func Execute(version string) {
 
 	app := &cli.App{
 		Name:    "asdf",
-		Version: "0.1.0",
+		Version: version,
 		// Not really sure what I should put here, but all the new Golang code will
 		// likely be written by me.
 		Copyright: "(c) 2024 Trevor Brown",
@@ -315,45 +315,18 @@ func Execute(version string) {
 	}
 }
 
-//go:embed completions/asdf.bash
-var bashCompletions string
-
-//go:embed completions/asdf.zsh
-var zshCompletions string
-
-//go:embed completions/asdf.fish
-var fishCompletions string
-
-//go:embed completions/asdf.nu
-var nuCompletions string
-
-//go:embed completions/asdf.elv
-var elvishCompletions string
-
 func completionCommand(l *log.Logger, shell string) error {
-	switch shell {
-	case "bash":
-		fmt.Print(bashCompletions)
-		return nil
-	case "zsh":
-		fmt.Print(zshCompletions)
-		return nil
-	case "fish":
-		fmt.Print(fishCompletions)
-		return nil
-	case "nushell":
-		fmt.Print(nuCompletions)
-		return nil
-	case "elvish":
-		fmt.Print(elvishCompletions)
-		return nil
-	default:
-		fmtString := `No completions available for shell with name %s
-Completions are available for: bash, zsh, fish, nushell, elvish`
-		msg := fmt.Sprintf(fmtString, shell)
-		l.Print(msg)
-		return errors.New(msg)
+	file, ok := completions.Get(shell)
+	if !ok {
+		l.Printf(`No completions available for shell with name %q
+Completions are available for: %v`, shell, strings.Join(completions.Names(), ", "))
+		return errors.New("bad shell name")
 	}
+	defer file.Close()
+
+	io.Copy(os.Stdout, file)
+
+	return nil
 }
 
 // This function is a whole mess and needs to be refactored
diff --git a/cli/completions/asdf.bash b/internal/completions/asdf.bash
similarity index 100%
rename from cli/completions/asdf.bash
rename to internal/completions/asdf.bash
diff --git a/cli/completions/asdf.elv b/internal/completions/asdf.elvish
similarity index 100%
rename from cli/completions/asdf.elv
rename to internal/completions/asdf.elvish
diff --git a/cli/completions/asdf.fish b/internal/completions/asdf.fish
similarity index 100%
rename from cli/completions/asdf.fish
rename to internal/completions/asdf.fish
diff --git a/cli/completions/asdf.nu b/internal/completions/asdf.nushell
similarity index 100%
rename from cli/completions/asdf.nu
rename to internal/completions/asdf.nushell
diff --git a/cli/completions/asdf.zsh b/internal/completions/asdf.zsh
similarity index 100%
rename from cli/completions/asdf.zsh
rename to internal/completions/asdf.zsh
diff --git a/internal/completions/completions.go b/internal/completions/completions.go
new file mode 100644
index 00000000..37338550
--- /dev/null
+++ b/internal/completions/completions.go
@@ -0,0 +1,40 @@
+// Package completions handles shell completion files.
+//
+// To add completion support for a shell, simply add a file named
+// "asdf.<shell>" to this directory, replacing "<shell>" with the name
+// of the shell.
+package completions
+
+import (
+	"embed"
+	"errors"
+	"io/fs"
+	"slices"
+	"strings"
+)
+
+//go:embed asdf.*
+var completions embed.FS
+
+// Get returns a file containing completion code for the given shell if it is
+// found.
+func Get(name string) (fs.File, bool) {
+	file, err := completions.Open("asdf." + name)
+	if err != nil {
+		if errors.Is(err, fs.ErrNotExist) {
+			return nil, false
+		}
+		panic(err) // This should never happen.
+	}
+	return file, true
+}
+
+// Names returns a slice of shell names that completion is available for.
+func Names() []string {
+	files, _ := fs.Glob(completions, "asdf.*")
+	for i, file := range files {
+		files[i] = strings.TrimPrefix(file, "asdf.")
+	}
+	slices.Sort(files)
+	return files
+}
diff --git a/internal/completions/completions_test.go b/internal/completions/completions_test.go
new file mode 100644
index 00000000..a001fd5c
--- /dev/null
+++ b/internal/completions/completions_test.go
@@ -0,0 +1,30 @@
+package completions
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGet(t *testing.T) {
+	t.Run("returns file when completion file found with matching name", func(t *testing.T) {
+		file, found := Get("bash")
+
+		info, err := file.Stat()
+		assert.Nil(t, err)
+		assert.Equal(t, "asdf.bash", info.Name())
+
+		assert.True(t, found)
+	})
+
+	t.Run("returns false when completion file not found", func(t *testing.T) {
+		_, found := Get("non-existent")
+		assert.False(t, found)
+	})
+}
+
+func TestNames(t *testing.T) {
+	t.Run("returns slice of shell names for which completion is available", func(t *testing.T) {
+		assert.Equal(t, []string{"bash", "elvish", "fish", "nushell", "zsh"}, Names())
+	})
+}
diff --git a/internal/execenv/execenv_test.go b/internal/execenv/execenv_test.go
index 28c6f7b1..b340a25c 100644
--- a/internal/execenv/execenv_test.go
+++ b/internal/execenv/execenv_test.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/asdf-vm/asdf/internal/config"
 	"github.com/asdf-vm/asdf/internal/plugins"
-	"github.com/asdf-vm/asdf/repotest"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/internal/git/git_test.go b/internal/git/git_test.go
index 11b21bea..0db56b5f 100644
--- a/internal/git/git_test.go
+++ b/internal/git/git_test.go
@@ -5,7 +5,7 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/asdf-vm/asdf/repotest"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/stretchr/testify/assert"
diff --git a/internal/help/help_test.go b/internal/help/help_test.go
index 9a8874b1..c8ae86bb 100644
--- a/internal/help/help_test.go
+++ b/internal/help/help_test.go
@@ -9,7 +9,7 @@ import (
 
 	"github.com/asdf-vm/asdf/internal/config"
 	"github.com/asdf-vm/asdf/internal/plugins"
-	"github.com/asdf-vm/asdf/repotest"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/internal/installs/installs_test.go b/internal/installs/installs_test.go
index 8f093955..f8e7f22c 100644
--- a/internal/installs/installs_test.go
+++ b/internal/installs/installs_test.go
@@ -8,8 +8,8 @@ import (
 	"github.com/asdf-vm/asdf/internal/config"
 	"github.com/asdf-vm/asdf/internal/installtest"
 	"github.com/asdf-vm/asdf/internal/plugins"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/asdf-vm/asdf/internal/toolversions"
-	"github.com/asdf-vm/asdf/repotest"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/internal/pluginindex/pluginindex_test.go b/internal/pluginindex/pluginindex_test.go
index 6a766f98..e618d594 100644
--- a/internal/pluginindex/pluginindex_test.go
+++ b/internal/pluginindex/pluginindex_test.go
@@ -9,7 +9,7 @@ import (
 	"time"
 
 	"github.com/asdf-vm/asdf/internal/git"
-	"github.com/asdf-vm/asdf/repotest"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/internal/plugins/plugins_test.go b/internal/plugins/plugins_test.go
index beb80911..af2372a9 100644
--- a/internal/plugins/plugins_test.go
+++ b/internal/plugins/plugins_test.go
@@ -9,7 +9,7 @@ import (
 
 	"github.com/asdf-vm/asdf/internal/config"
 	"github.com/asdf-vm/asdf/internal/data"
-	"github.com/asdf-vm/asdf/repotest"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/repotest/repotest.go b/internal/repotest/repotest.go
similarity index 100%
rename from repotest/repotest.go
rename to internal/repotest/repotest.go
diff --git a/internal/resolve/resolve_test.go b/internal/resolve/resolve_test.go
index 76aa0aca..92536526 100644
--- a/internal/resolve/resolve_test.go
+++ b/internal/resolve/resolve_test.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/asdf-vm/asdf/internal/config"
 	"github.com/asdf-vm/asdf/internal/plugins"
-	"github.com/asdf-vm/asdf/repotest"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/internal/shims/shims_test.go b/internal/shims/shims_test.go
index 8e38bb09..dfd84e51 100644
--- a/internal/shims/shims_test.go
+++ b/internal/shims/shims_test.go
@@ -12,8 +12,8 @@ import (
 	"github.com/asdf-vm/asdf/internal/installs"
 	"github.com/asdf-vm/asdf/internal/installtest"
 	"github.com/asdf-vm/asdf/internal/plugins"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/asdf-vm/asdf/internal/toolversions"
-	"github.com/asdf-vm/asdf/repotest"
 	"github.com/stretchr/testify/assert"
 	"golang.org/x/sys/unix"
 )
diff --git a/internal/versions/versions_test.go b/internal/versions/versions_test.go
index f955b2cd..6250f57b 100644
--- a/internal/versions/versions_test.go
+++ b/internal/versions/versions_test.go
@@ -9,8 +9,8 @@ import (
 
 	"github.com/asdf-vm/asdf/internal/config"
 	"github.com/asdf-vm/asdf/internal/plugins"
+	"github.com/asdf-vm/asdf/internal/repotest"
 	"github.com/asdf-vm/asdf/internal/toolversions"
-	"github.com/asdf-vm/asdf/repotest"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/scripts/lint.bash b/scripts/lint.bash
index 904fbcda..48b464c3 100755
--- a/scripts/lint.bash
+++ b/scripts/lint.bash
@@ -31,7 +31,7 @@ run_shfmt_stylecheck() {
 
   print.info "Checking .bash with shfmt"
   shfmt --language-dialect bash --indent 2 "${shfmt_flag}" \
-    cli/completions/*.bash \
+    internal/completions/*.bash \
     bin/asdf \
     bin/private/asdf-exec \
     lib/utils.bash \
@@ -55,7 +55,7 @@ run_shellcheck_linter() {
 
   print.info "Checking .bash files with Shellcheck"
   shellcheck --shell bash --external-sources \
-    cli/completions/*.bash \
+    internal/completions/*.bash \
     bin/asdf \
     bin/private/asdf-exec \
     lib/utils.bash \
@@ -123,7 +123,7 @@ run_fish_linter() {
     printf "%s\n" "[WARNING] fish_indent not found. Skipping .fish files."
   else
     print.info "Checking .fish files with fish_indent"
-    fish_indent "${flag}" ./cli/completions/asdf.fish
+    fish_indent "${flag}" ./completions/asdf.fish
   fi
 }