Skip to content

Commit

Permalink
Merge pull request #70 from asdf-vm/tb/asdf-help-command
Browse files Browse the repository at this point in the history
feat(golang-rewrite): implement `asdf help` command
  • Loading branch information
Stratus3D authored Sep 26, 2024
2 parents b18e3fe + a634d7f commit 44651dd
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 6 deletions.
39 changes: 39 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"asdf/internal/config"
"asdf/internal/exec"
"asdf/internal/help"
"asdf/internal/info"
"asdf/internal/installs"
"asdf/internal/plugins"
Expand Down Expand Up @@ -65,6 +66,14 @@ func Execute(version string) {
return execCommand(logger, command, args)
},
},
{
Name: "help",
Action: func(cCtx *cli.Context) error {
toolName := cCtx.Args().Get(0)
toolVersion := cCtx.Args().Get(1)
return helpCommand(logger, version, toolName, toolVersion)
},
},
{
Name: "info",
Action: func(_ *cli.Context) error {
Expand Down Expand Up @@ -408,6 +417,36 @@ func infoCommand(conf config.Config, version string) error {
return info.Print(conf, version)
}

func helpCommand(logger *log.Logger, asdfVersion, tool, version string) error {
conf, err := config.LoadConfig()
if err != nil {
logger.Printf("error loading config: %s", err)
return err
}

if tool != "" {
if version != "" {
err := help.PrintToolVersion(conf, tool, version)
if err != nil {
os.Exit(1)
}
return err
}

err := help.PrintTool(conf, tool)
if err != nil {
os.Exit(1)
}
return err
}

err = help.Print(asdfVersion)
if err != nil {
os.Exit(1)
}
return err
}

func pluginUpdateCommand(cCtx *cli.Context, logger *log.Logger, pluginName, ref string) error {
updateAll := cCtx.Bool("all")
if !updateAll && pluginName == "" {
Expand Down
120 changes: 120 additions & 0 deletions internal/help/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Package help contains functions responsible for generating help output for
// asdf and asdf plugins.
package help

import (
_ "embed"
"fmt"
"io"
"os"

"asdf/internal/config"
"asdf/internal/plugins"
"asdf/internal/versions"
)

//go:embed help.txt
var helpText string

const quote = "\"Late but latest\"\n-- Rajinikanth"

// Print help output to STDOUT
func Print(asdfVersion string) error {
return Write(asdfVersion, os.Stdout)
}

// PrintTool write tool help output to STDOUT
func PrintTool(conf config.Config, toolName string) error {
return WriteToolHelp(conf, toolName, os.Stdout, os.Stderr)
}

// PrintToolVersion write help for specific tool version to STDOUT
func PrintToolVersion(conf config.Config, toolName, toolVersion string) error {
return WriteToolVersionHelp(conf, toolName, toolVersion, os.Stdout, os.Stderr)
}

// Write help output to an io.Writer
func Write(asdfVersion string, writer io.Writer) error {
_, err := writer.Write([]byte(fmt.Sprintf("version: %s", asdfVersion)))
if err != nil {
return err
}

_, err = writer.Write([]byte(helpText))
if err != nil {
return err
}

_, err = writer.Write([]byte("\n"))
if err != nil {
return err
}

_, err = writer.Write([]byte(quote))
if err != nil {
return err
}

_, err = writer.Write([]byte("\n"))
if err != nil {
return err
}

return nil
}

// WriteToolHelp output to an io.Writer
func WriteToolHelp(conf config.Config, toolName string, writer io.Writer, errWriter io.Writer) error {
return writePluginHelp(conf, toolName, "", writer, errWriter)
}

// WriteToolVersionHelp output to an io.Writer
func WriteToolVersionHelp(conf config.Config, toolName, toolVersion string, writer io.Writer, errWriter io.Writer) error {
return writePluginHelp(conf, toolName, toolVersion, writer, errWriter)
}

func writePluginHelp(conf config.Config, toolName, toolVersion string, writer io.Writer, errWriter io.Writer) error {
plugin := plugins.New(conf, toolName)
env := map[string]string{
"ASDF_INSTALL_PATH": plugin.Dir,
}

if toolVersion != "" {
versionType, version := versions.ParseString(toolVersion)
env["ASDF_INSTALL_VERSION"] = version
env["ASDF_INSTALL_TYPE"] = versionType
}

if err := plugin.Exists(); err != nil {
errWriter.Write([]byte(fmt.Sprintf("No plugin named %s\n", plugin.Name)))
return err
}

err := plugin.RunCallback("help.overview", []string{}, env, writer, errWriter)
if _, ok := err.(plugins.NoCallbackError); ok {
// No such callback, print err msg
errWriter.Write([]byte(fmt.Sprintf("No documentation for plugin %s\n", plugin.Name)))
return err
}

if err != nil {
return err
}

err = plugin.RunCallback("help.deps", []string{}, env, writer, errWriter)
if _, ok := err.(plugins.NoCallbackError); !ok {
return err
}

err = plugin.RunCallback("help.config", []string{}, env, writer, errWriter)
if _, ok := err.(plugins.NoCallbackError); !ok {
return err
}

err = plugin.RunCallback("help.links", []string{}, env, writer, errWriter)
if _, ok := err.(plugins.NoCallbackError); !ok {
return err
}

return nil
}
66 changes: 66 additions & 0 deletions internal/help/help.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
MANAGE PLUGINS
asdf plugin add <name> [<git-url>] Add a plugin from the plugin repo OR,
add a Git repo as a plugin by
specifying the name and repo url
asdf plugin list [--urls] [--refs] List installed plugins. Optionally show
git urls and git-ref
asdf plugin list all List plugins registered on asdf-plugins
repository with URLs
asdf plugin remove <name> Remove plugin and package versions
asdf plugin update <name> [<git-ref>] Update a plugin to latest commit on
default branch or a particular git-ref
asdf plugin update --all Update all plugins to latest commit on
default branch


MANAGE TOOLS
asdf current Display current version set or being
used for all packages
asdf current <name> Display current version set or being
used for package
asdf global <name> <version> Set the package global version
asdf global <name> latest[:<version>] Set the package global version to the
latest provided version
asdf help <name> [<version>] Output documentation for plugin and tool
asdf install Install all the package versions listed
in the .tool-versions file
asdf install <name> Install one tool at the version
specified in the .tool-versions file
asdf install <name> <version> Install a specific version of a package
asdf install <name> latest[:<version>] Install the latest stable version of a
package, or with optional version,
install the latest stable version that
begins with the given string
asdf latest <name> [<version>] Show latest stable version of a package
asdf latest --all Show latest stable version of all the
packages and if they are installed
asdf list <name> [version] List installed versions of a package and
optionally filter the versions
asdf list all <name> [<version>] List all versions of a package and
optionally filter the returned versions
asdf local <name> <version> Set the package local version
asdf local <name> latest[:<version>] Set the package local version to the
latest provided version
asdf shell <name> <version> Set the package version to
`ASDF_${LANG}_VERSION` in the current shell
asdf uninstall <name> <version> Remove a specific version of a package
asdf where <name> [<version>] Display install path for an installed
or current version
asdf which <command> Display the path to an executable


UTILS
asdf exec <command> [args...] Executes the command shim for current version
asdf env <command> [util] Runs util (default: `env`) inside the
environment used for command shim execution.
asdf info Print OS, Shell and ASDF debug information.
asdf version Print the currently installed version of ASDF
asdf reshim <name> <version> Recreate shims for version of a package
asdf shim-versions <command> List the plugins and versions that
provide a command
asdf update Update asdf to the latest stable release
asdf update --head Update asdf to the latest on the master branch

RESOURCES
GitHub: https://github.com/asdf-vm/asdf
Docs: https://asdf-vm.com
133 changes: 133 additions & 0 deletions internal/help/help_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package help

import (
"os"
"path/filepath"
"strings"
"testing"

"asdf/internal/config"
"asdf/internal/plugins"
"asdf/repotest"

"github.com/stretchr/testify/assert"
)

const (
version = "0.15.0"
testPluginName = "lua"
)

func TestWrite(t *testing.T) {
testDataDir := t.TempDir()
err := os.MkdirAll(filepath.Join(testDataDir, "plugins"), 0o777)
assert.Nil(t, err)

var stdout strings.Builder

err = Write(version, &stdout)
assert.Nil(t, err)
output := stdout.String()

// Simple format assertions
assert.Contains(t, output, "version: ")
assert.Contains(t, output, "MANAGE PLUGINS\n")
assert.Contains(t, output, "MANAGE TOOLS\n")
assert.Contains(t, output, "UTILS\n")
assert.Contains(t, output, "RESOURCES\n")
}

func TestWriteToolHelp(t *testing.T) {
conf, plugin := generateConfig(t)

t.Run("when plugin implements all help callbacks", func(t *testing.T) {
var stdout strings.Builder
var stderr strings.Builder

err := WriteToolHelp(conf, plugin.Name, &stdout, &stderr)

assert.Nil(t, err)
assert.Empty(t, stderr.String())
expected := "Dummy plugin documentation\n\nDummy plugin is a plugin only used for unit tests\n"
assert.Equal(t, stdout.String(), expected)
})

t.Run("when plugin does not have help.overview callback", func(t *testing.T) {
var stdout strings.Builder
var stderr strings.Builder
plugin := installPlugin(t, conf, "dummy_legacy_plugin", "legacy-plugin")

err := WriteToolHelp(conf, plugin.Name, &stdout, &stderr)

assert.EqualError(t, err, "Plugin named legacy-plugin does not have a callback named help.overview")
assert.Empty(t, stdout.String())
assert.Equal(t, stderr.String(), "No documentation for plugin legacy-plugin\n")
})

t.Run("when plugin does not exist", func(t *testing.T) {
var stdout strings.Builder
var stderr strings.Builder

err := WriteToolHelp(conf, "non-existent", &stdout, &stderr)

assert.EqualError(t, err, "Plugin named non-existent not installed")
assert.Empty(t, stdout.String())
assert.Equal(t, stderr.String(), "No plugin named non-existent\n")
})
}

func TestWriteToolVersionHelp(t *testing.T) {
conf, plugin := generateConfig(t)

t.Run("when plugin implements all help callbacks", func(t *testing.T) {
var stdout strings.Builder
var stderr strings.Builder

err := WriteToolVersionHelp(conf, plugin.Name, "1.2.3", &stdout, &stderr)

assert.Nil(t, err)
assert.Empty(t, stderr.String())
expected := "Dummy plugin documentation\n\nDummy plugin is a plugin only used for unit tests\n\nDetails specific for version 1.2.3\n"
assert.Equal(t, stdout.String(), expected)
})

t.Run("when plugin does not have help.overview callback", func(t *testing.T) {
var stdout strings.Builder
var stderr strings.Builder
plugin := installPlugin(t, conf, "dummy_legacy_plugin", "legacy-plugin")

err := WriteToolVersionHelp(conf, plugin.Name, "1.2.3", &stdout, &stderr)

assert.EqualError(t, err, "Plugin named legacy-plugin does not have a callback named help.overview")
assert.Empty(t, stdout.String())
assert.Equal(t, stderr.String(), "No documentation for plugin legacy-plugin\n")
})

t.Run("when plugin does not exist", func(t *testing.T) {
var stdout strings.Builder
var stderr strings.Builder

err := WriteToolVersionHelp(conf, "non-existent", "1.2.3", &stdout, &stderr)

assert.EqualError(t, err, "Plugin named non-existent not installed")
assert.Empty(t, stdout.String())
assert.Equal(t, stderr.String(), "No plugin named non-existent\n")
})
}

func generateConfig(t *testing.T) (config.Config, plugins.Plugin) {
t.Helper()
testDataDir := t.TempDir()
conf, err := config.LoadConfig()
assert.Nil(t, err)
conf.DataDir = testDataDir

return conf, installPlugin(t, conf, "dummy_plugin", testPluginName)
}

func installPlugin(t *testing.T, conf config.Config, fixture, pluginName string) plugins.Plugin {
_, err := repotest.InstallPlugin(fixture, conf.DataDir, pluginName)
assert.Nil(t, err)

return plugins.New(conf, pluginName)
}
Loading

0 comments on commit 44651dd

Please sign in to comment.