From 0a58b32142ca49c386ffef1f7df1df4497b290e0 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Sat, 5 Oct 2024 17:49:28 -0400 Subject: [PATCH] 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 --- cmd/cmd.go | 74 +++++++++++++++++++++- internal/help/help.go | 4 +- internal/installs/installs.go | 7 +- internal/toolversions/toolversions.go | 37 ++++++++++- internal/toolversions/toolversions_test.go | 30 +++++++++ internal/versions/versions.go | 25 +------- internal/versions/versions_test.go | 20 ------ main_test.go | 6 +- test/where_command.bats | 1 + 9 files changed, 151 insertions(+), 53 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 423b5ea2..34165f35 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -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 %s `", 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) } diff --git a/internal/help/help.go b/internal/help/help.go index 0bf48057..27076e1a 100644 --- a/internal/help/help.go +++ b/internal/help/help.go @@ -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 } diff --git a/internal/installs/installs.go b/internal/installs/installs.go index ae0299b0..112f2bcf 100644 --- a/internal/installs/installs.go +++ b/internal/installs/installs.go @@ -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 diff --git a/internal/toolversions/toolversions.go b/internal/toolversions/toolversions.go index 1ae81964..959f4fca 100644 --- a/internal/toolversions/toolversions.go +++ b/internal/toolversions/toolversions.go @@ -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) { diff --git a/internal/toolversions/toolversions_test.go b/internal/toolversions/toolversions_test.go index dc2b4623..c52bb8e0 100644 --- a/internal/toolversions/toolversions_test.go +++ b/internal/toolversions/toolversions_test.go @@ -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") + }) +} diff --git a/internal/versions/versions.go b/internal/versions/versions.go index b58f5350..afd4a68c 100644 --- a/internal/versions/versions.go +++ b/internal/versions/versions.go @@ -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 -} diff --git a/internal/versions/versions_test.go b/internal/versions/versions_test.go index 23132ec6..a6212599 100644 --- a/internal/versions/versions_test.go +++ b/internal/versions/versions_test.go @@ -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) diff --git a/main_test.go b/main_test.go index 6a698546..c42fa93b 100644 --- a/main_test.go +++ b/main_test.go @@ -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") diff --git a/test/where_command.bats b/test/where_command.bats index 491f6c2d..5157edba 100644 --- a/test/where_command.bats +++ b/test/where_command.bats @@ -8,6 +8,7 @@ setup() { install_dummy_version 1.0 install_dummy_version 2.1 install_dummy_version ref-master + cd "$HOME" || exit } teardown() {