feat(golang-rewrite): implement asdf plugin list all command

* Enable `plugin_list_all_command.bats` tests
* Create `PluginIndex.Get` method
* Create `asdf plugin list all` subcommand
* Extract plugin index repo URL into config package
* Fix failing tests
This commit is contained in:
Trevor Brown 2024-12-07 14:48:31 -05:00
parent 5e542da7b5
commit e7df5ff325
8 changed files with 137 additions and 13 deletions

View File

@ -20,6 +20,7 @@ import (
"asdf/internal/hook"
"asdf/internal/info"
"asdf/internal/installs"
"asdf/internal/pluginindex"
"asdf/internal/plugins"
"asdf/internal/resolve"
"asdf/internal/shims"
@ -173,6 +174,14 @@ func Execute(version string) {
Action: func(cCtx *cli.Context) error {
return pluginListCommand(cCtx, logger)
},
Subcommands: []*cli.Command{
{
Name: "all",
Action: func(_ *cli.Context) error {
return pluginListAllCommand(logger)
},
},
},
},
{
Name: "remove",
@ -628,6 +637,68 @@ func pluginListCommand(cCtx *cli.Context, logger *log.Logger) error {
return nil
}
func pluginListAllCommand(logger *log.Logger) error {
conf, err := config.LoadConfig()
if err != nil {
logger.Printf("error loading config: %s", err)
return err
}
disableRepo, err := conf.DisablePluginShortNameRepository()
if err != nil {
logger.Printf("unable to check config")
return err
}
if disableRepo {
logger.Printf("Short-name plugin repository is disabled")
os.Exit(1)
return nil
}
lastCheckDuration := 0
// We don't care about errors here as we can use the default value
checkDuration, _ := conf.PluginRepositoryLastCheckDuration()
if !checkDuration.Never {
lastCheckDuration = checkDuration.Every
}
index := pluginindex.Build(conf.DataDir, conf.PluginIndexURL, false, lastCheckDuration)
availablePlugins, err := index.Get()
if err != nil {
logger.Printf("error loading plugin index: %s", err)
return err
}
installedPlugins, err := plugins.List(conf, true, false)
if err != nil {
logger.Printf("error loading plugin list: %s", err)
return err
}
w := tabwriter.NewWriter(os.Stdout, 15, 0, 1, ' ', 0)
for _, availablePlugin := range availablePlugins {
if pluginInstalled(availablePlugin, installedPlugins) {
fmt.Fprintf(w, "%s\t\t*%s\n", availablePlugin.Name, availablePlugin.URL)
} else {
fmt.Fprintf(w, "%s\t\t%s\n", availablePlugin.Name, availablePlugin.URL)
}
}
w.Flush()
return nil
}
func pluginInstalled(plugin pluginindex.Plugin, installedPlugins []plugins.Plugin) bool {
for _, installedPlugin := range installedPlugins {
if installedPlugin.Name == plugin.Name && installedPlugin.URL == plugin.URL {
return true
}
}
return false
}
func infoCommand(conf config.Config, version string) error {
return info.Print(conf, version)
}

View File

@ -18,6 +18,7 @@ const (
dataDirDefault = "~/.asdf"
configFileDefault = "~/.asdfrc"
defaultToolVersionsFilenameDefault = ".tool-versions"
defaultPluginIndexURL = "https://github.com/asdf-vm/asdf-plugins.git"
)
/* PluginRepoCheckDuration represents the remote plugin repo check duration
@ -40,7 +41,8 @@ type Config struct {
DataDir string `env:"ASDF_DATA_DIR, overwrite"`
ForcePrepend bool `env:"ASDF_FORCE_PREPEND, overwrite"`
// Field that stores the settings struct if it is loaded
Settings Settings
Settings Settings
PluginIndexURL string
}
// Settings is a struct that stores config values from the asdfrc file
@ -62,6 +64,7 @@ func defaultConfig(dataDir, configFile string) *Config {
DataDir: dataDir,
ConfigFile: configFile,
DefaultToolVersionsFilename: defaultToolVersionsFilenameDefault,
PluginIndexURL: defaultPluginIndexURL,
}
}

View File

@ -4,6 +4,7 @@ package pluginindex
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"time"
@ -28,6 +29,12 @@ type PluginIndex struct {
updateDurationMinutes int
}
// Plugin represents a plugin listed on a plugin index.
type Plugin struct {
Name string
URL string
}
// Build returns a complete PluginIndex struct with default values set
func Build(dataDir string, URL string, disableUpdate bool, updateDurationMinutes int) PluginIndex {
directory := filepath.Join(dataDir, pluginIndexDir)
@ -45,6 +52,16 @@ func New(directory, url string, disableUpdate bool, updateDurationMinutes int, r
}
}
// Get returns a slice of all available plugins
func (p PluginIndex) Get() (plugins []Plugin, err error) {
_, err = p.Refresh()
if err != nil {
return plugins, err
}
return getPlugins(p.directory)
}
// Refresh may update the plugin repo if it hasn't been updated in longer
// than updateDurationMinutes. If the plugin repo needs to be updated the
// repo will be invoked to perform the actual Git pull.
@ -145,3 +162,23 @@ func readPlugin(dir, name string) (string, error) {
return pluginInfo.Section("").Key("repository").String(), nil
}
func getPlugins(dir string) (plugins []Plugin, err error) {
files, err := os.ReadDir(filepath.Join(dir, "plugins"))
if _, ok := err.(*fs.PathError); ok {
return plugins, nil
}
for _, file := range files {
if !file.IsDir() {
url, err := readPlugin(dir, file.Name())
if err != nil {
return plugins, err
}
plugins = append(plugins, Plugin{Name: file.Name(), URL: url})
}
}
return plugins, err
}

View File

@ -83,6 +83,17 @@ func writeMockPluginFile(dir, pluginName, pluginURL string) error {
return nil
}
func TestGet(t *testing.T) {
t.Run("returns populated slice of plugins when plugins exist in directory", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, mockIndexURL, true, 0, &MockIndex{Directory: dir})
plugins, err := pluginIndex.Get()
assert.Nil(t, err)
assert.Equal(t, plugins, []Plugin{{Name: "elixir", URL: "https://github.com/asdf-vm/asdf-elixir.git"}})
})
}
func TestGetPluginSourceURL(t *testing.T) {
t.Run("with Git returns a plugin url when provided name of existing plugin", func(t *testing.T) {
dir := t.TempDir()

View File

@ -272,7 +272,7 @@ func Add(config config.Config, pluginName, pluginURL string) error {
lastCheckDuration = checkDuration.Every
}
index := pluginindex.Build(config.DataDir, "https://github.com/asdf-vm/asdf-plugins.git", false, lastCheckDuration)
index := pluginindex.Build(config.DataDir, config.PluginIndexURL, false, lastCheckDuration)
var err error
pluginURL, err = index.GetPluginSourceURL(pluginName)
if err != nil {

View File

@ -51,9 +51,9 @@ func TestBatsTests(t *testing.T) {
// runBatsFile(t, dir, "plugin_extension_command.bats")
//})
//t.Run("plugin_list_all_command", func(t *testing.T) {
// runBatsFile(t, dir, "plugin_list_all_command.bats")
//})
t.Run("plugin_list_all_command", func(t *testing.T) {
runBatsFile(t, dir, "plugin_list_all_command.bats")
})
t.Run("plugin_remove_command", func(t *testing.T) {
runBatsFile(t, dir, "plugin_remove_command.bats")

View File

@ -26,15 +26,13 @@ teardown() {
@test "plugin_list_all should sync repo when check_duration set to 0" {
export ASDF_CONFIG_DEFAULT_FILE="$HOME/.asdfrc"
echo 'plugin_repository_last_check_duration = 0' >"$ASDF_CONFIG_DEFAULT_FILE"
local expected_plugin_repo_sync="updating plugin repository..."
local expected_plugins_list="\
bar http://example.com/bar
dummy *http://example.com/dummy
dummy http://example.com/dummy
foo http://example.com/foo"
run asdf plugin list all
[ "$status" -eq 0 ]
[[ "$output" == *"$expected_plugin_repo_sync"* ]]
[[ "$output" == *"$expected_plugins_list"* ]]
}
@ -43,7 +41,7 @@ foo http://example.com/foo"
echo 'plugin_repository_last_check_duration = 10' >"$ASDF_CONFIG_DEFAULT_FILE"
local expected="\
bar http://example.com/bar
dummy *http://example.com/dummy
dummy http://example.com/dummy
foo http://example.com/foo"
run asdf plugin list all
@ -56,7 +54,7 @@ foo http://example.com/foo"
echo 'plugin_repository_last_check_duration = never' >"$ASDF_CONFIG_DEFAULT_FILE"
local expected="\
bar http://example.com/bar
dummy *http://example.com/dummy
dummy http://example.com/dummy
foo http://example.com/foo"
run asdf plugin list all
@ -67,7 +65,7 @@ foo http://example.com/foo"
@test "plugin_list_all list all plugins in the repository" {
local expected="\
bar http://example.com/bar
dummy *http://example.com/dummy
dummy http://example.com/dummy
foo http://example.com/foo"
run asdf plugin list all

View File

@ -70,12 +70,13 @@ install_mock_plugin_repo() {
init_git_repo() {
location="$1"
remote="${2:-"https://asdf-vm.com/fake-repo"}"
git -C "${location}" init -q
git -C "${location}" config user.name "Test"
git -C "${location}" config user.email "test@example.com"
git -C "${location}" add -A
git -C "${location}" commit -q -m "asdf ${plugin_name} plugin"
git -C "${location}" remote add origin "https://asdf-vm.com/fake-repo"
git -C "${location}" remote add origin "$remote"
}
install_mock_plugin_version() {
@ -127,6 +128,9 @@ clean_asdf_dir() {
}
setup_repo() {
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugins_repo" "$ASDF_DIR/repository"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugins_repo" "$ASDF_DIR/plugin-index"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugins_repo" "$ASDF_DIR/plugin-index-2"
init_git_repo "$ASDF_DIR/plugin-index-2"
init_git_repo "$ASDF_DIR/plugin-index" "$ASDF_DIR/plugin-index-2"
touch "$(asdf_dir)/tmp/repo-updated"
}