feat(golang-rewrite): create asdf where command

* Move `versions.ParseString` function to the toolversions package
* Create `toolversions.FormatForFS` function
* use new `toolversions.FormatForFS` function
* Create `asdf where` command
* Enable BATS tests for `asdf where` command
This commit is contained in:
Trevor Brown 2024-10-05 17:49:28 -04:00
parent ec8985af8f
commit 09d06ff125
9 changed files with 151 additions and 53 deletions

View File

@ -19,6 +19,7 @@ import (
"asdf/internal/plugins"
"asdf/internal/resolve"
"asdf/internal/shims"
"asdf/internal/toolversions"
"asdf/internal/versions"
"github.com/urfave/cli/v2"
@ -175,6 +176,15 @@ func Execute(version string) {
return reshimCommand(logger, args.Get(0), args.Get(1))
},
},
{
Name: "where",
Action: func(cCtx *cli.Context) error {
tool := cCtx.Args().Get(0)
version := cCtx.Args().Get(1)
return whereCommand(logger, tool, version)
},
},
{
Name: "which",
Action: func(cCtx *cli.Context) error {
@ -265,7 +275,7 @@ func getVersionInfo(conf config.Config, plugin plugins.Plugin, currentDir string
installed := false
if found {
firstVersion := toolversion.Versions[0]
versionType, version := versions.ParseString(firstVersion)
versionType, version := toolversions.Parse(firstVersion)
installed = installs.IsInstalled(conf, plugin, versionType, version)
}
return toolversion, found, installed
@ -663,8 +673,68 @@ func whichCommand(logger *log.Logger, command string) error {
return nil
}
func whereCommand(logger *log.Logger, tool, version string) error {
conf, err := config.LoadConfig()
if err != nil {
logger.Printf("error loading config: %s", err)
return err
}
currentDir, err := os.Getwd()
if err != nil {
logger.Printf("unable to get current directory: %s", err)
return err
}
plugin := plugins.New(conf, tool)
err = plugin.Exists()
if err != nil {
if _, ok := err.(plugins.PluginMissing); ok {
logger.Printf("No such plugin: %s", tool)
}
return err
}
versionType, parsedVersion := toolversions.Parse(version)
if version == "" {
// resolve version
toolversions, found, err := resolve.Version(conf, plugin, currentDir)
if err != nil {
fmt.Printf("err %#+v\n", err)
return err
}
if found && len(toolversions.Versions) > 0 && installs.IsInstalled(conf, plugin, "version", toolversions.Versions[0]) {
installPath := installs.InstallPath(conf, plugin, "version", toolversions.Versions[0])
logger.Printf("%s", installPath)
return nil
}
// not found
msg := fmt.Sprintf("No version is set for %s; please run `asdf <global | shell | local> %s <version>`", tool, tool)
logger.Print(msg)
return errors.New(msg)
}
if version == "system" {
logger.Printf("System version is selected")
return errors.New("System version is selected")
}
if !installs.IsInstalled(conf, plugin, versionType, parsedVersion) {
logger.Printf("Version not installed")
return errors.New("Version not installed")
}
installPath := installs.InstallPath(conf, plugin, versionType, parsedVersion)
logger.Printf("%s", installPath)
return nil
}
func reshimToolVersion(conf config.Config, tool, version string, out io.Writer, errOut io.Writer) error {
versionType, version := versions.ParseString(version)
versionType, version := toolversions.Parse(version)
return shims.GenerateForVersion(conf, plugins.New(conf, tool), versionType, version, out, errOut)
}

View File

@ -10,7 +10,7 @@ import (
"asdf/internal/config"
"asdf/internal/plugins"
"asdf/internal/versions"
"asdf/internal/toolversions"
)
//go:embed help.txt
@ -80,7 +80,7 @@ func writePluginHelp(conf config.Config, toolName, toolVersion string, writer io
}
if toolVersion != "" {
versionType, version := versions.ParseString(toolVersion)
versionType, version := toolversions.Parse(toolVersion)
env["ASDF_INSTALL_VERSION"] = version
env["ASDF_INSTALL_TYPE"] = versionType
}

View File

@ -10,6 +10,7 @@ import (
"asdf/internal/config"
"asdf/internal/plugins"
"asdf/internal/toolversions"
)
const (
@ -45,7 +46,8 @@ func InstallPath(conf config.Config, plugin plugins.Plugin, versionType, version
if versionType == "path" {
return version
}
return filepath.Join(pluginInstallPath(conf, plugin), version)
return filepath.Join(pluginInstallPath(conf, plugin), toolversions.FormatForFS(versionType, version))
}
// DownloadPath returns the download path for a particular plugin and version
@ -53,7 +55,8 @@ func DownloadPath(conf config.Config, plugin plugins.Plugin, versionType, versio
if versionType == "path" {
return ""
}
return filepath.Join(conf.DataDir, dataDirDownloads, plugin.Name, version)
return filepath.Join(conf.DataDir, dataDirDownloads, plugin.Name, toolversions.FormatForFS(versionType, version))
}
// IsInstalled checks if a specific version of a tool is installed

View File

@ -1,8 +1,10 @@
// Package toolversions handles reading and writing tools and versions from
// asdf's .tool-versions files
// asdf's .tool-versions files. It also handles parsing version strings from
// .tool-versions files and command line arguments.
package toolversions
import (
"fmt"
"os"
"slices"
"strings"
@ -81,6 +83,39 @@ func Unique(versions []ToolVersions) (uniques []ToolVersions) {
return uniques
}
// Parse parses a version string into versionType and version components
func Parse(version string) (string, string) {
segments := strings.Split(version, ":")
if len(segments) >= 1 {
remainder := strings.Join(segments[1:], ":")
switch segments[0] {
case "ref":
return "ref", remainder
case "path":
// This is for people who have the local source already compiled
// Like those who work on the language, etc
// We'll allow specifying path:/foo/bar/project in .tool-versions
// And then use the binaries there
return "path", remainder
default:
return "version", version
}
}
return "version", version
}
// FormatForFS takes a versionType and version strings and generate a version
// string suitable for the file system
func FormatForFS(versionType, version string) string {
switch versionType {
case "ref":
return fmt.Sprintf("ref-%s", version)
default:
return version
}
}
// readLines reads all the lines in a given file
// removing spaces and comments which are marked by '#'
func readLines(content string) (lines []string) {

View File

@ -157,3 +157,33 @@ func TestgetAllToolsAndVersionsInContent(t *testing.T) {
})
}
}
func TestParse(t *testing.T) {
t.Run("returns 'version', and unmodified version when passed semantic version", func(t *testing.T) {
versionType, version := Parse("1.2.3")
assert.Equal(t, versionType, "version")
assert.Equal(t, version, "1.2.3")
})
t.Run("returns 'ref' and reference version when passed a ref version", func(t *testing.T) {
versionType, version := Parse("ref:abc123")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "abc123")
})
t.Run("returns 'ref' and empty string when passed 'ref:'", func(t *testing.T) {
versionType, version := Parse("ref:")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "")
})
}
func TestFormatForFS(t *testing.T) {
t.Run("returns version when version type is not ref", func(t *testing.T) {
assert.Equal(t, FormatForFS("version", "foobar"), "foobar")
})
t.Run("returns version prefixed with 'ref-' when version type is ref", func(t *testing.T) {
assert.Equal(t, FormatForFS("ref", "foobar"), "ref-foobar")
})
}

View File

@ -16,6 +16,7 @@ import (
"asdf/internal/plugins"
"asdf/internal/resolve"
"asdf/internal/shims"
"asdf/internal/toolversions"
)
const (
@ -131,7 +132,7 @@ func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string
return UninstallableVersionError{versionType: "system"}
}
versionType, version := ParseString(version)
versionType, version := toolversions.Parse(version)
if versionType == "path" {
return UninstallableVersionError{versionType: "path"}
@ -306,25 +307,3 @@ func parseVersions(rawVersions string) []string {
}
return versions
}
// ParseString parses a version string into versionType and version components
func ParseString(version string) (string, string) {
segments := strings.Split(version, ":")
if len(segments) >= 1 {
remainder := strings.Join(segments[1:], ":")
switch segments[0] {
case "ref":
return "ref", remainder
case "path":
// This is for people who have the local source already compiled
// Like those who work on the language, etc
// We'll allow specifying path:/foo/bar/project in .tool-versions
// And then use the binaries there
return "path", remainder
default:
return "version", version
}
}
return "version", version
}

View File

@ -308,26 +308,6 @@ func TestLatest(t *testing.T) {
})
}
func TestParseString(t *testing.T) {
t.Run("returns 'version', and unmodified version when passed semantic version", func(t *testing.T) {
versionType, version := ParseString("1.2.3")
assert.Equal(t, versionType, "version")
assert.Equal(t, version, "1.2.3")
})
t.Run("returns 'ref' and reference version when passed a ref version", func(t *testing.T) {
versionType, version := ParseString("ref:abc123")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "abc123")
})
t.Run("returns 'ref' and empty string when passed 'ref:'", func(t *testing.T) {
versionType, version := ParseString("ref:")
assert.Equal(t, versionType, "ref")
assert.Equal(t, version, "")
})
}
func TestAllVersions(t *testing.T) {
pluginName := "list-all-test"
conf, _ := generateConfig(t)

View File

@ -95,9 +95,9 @@ func TestBatsTests(t *testing.T) {
// runBatsFile(t, dir, "version_commands.bats")
//})
//t.Run("where_command", func(t *testing.T) {
// runBatsFile(t, dir, "where_command.bats")
//})
t.Run("where_command", func(t *testing.T) {
runBatsFile(t, dir, "where_command.bats")
})
t.Run("which_command", func(t *testing.T) {
runBatsFile(t, dir, "which_command.bats")

View File

@ -8,6 +8,7 @@ setup() {
install_dummy_version 1.0
install_dummy_version 2.1
install_dummy_version ref-master
cd "$HOME" || exit
}
teardown() {