Skip to content

Commit

Permalink
feat: add autocomplete for project name arguments, fixes ddev#4880
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli committed Jan 27, 2024
1 parent a066c42 commit af7e579
Show file tree
Hide file tree
Showing 21 changed files with 370 additions and 63 deletions.
218 changes: 218 additions & 0 deletions cmd/ddev/cmd/autocompletion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package cmd

import (
"strings"
"testing"

"github.com/ddev/ddev/pkg/exec"
asrt "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestAutocompletionForStopCmd checks autocompletion of project names for ddev stop
func TestAutocompletionForStopCmd(t *testing.T) {
assert := asrt.New(t)

// Skip if we don't have enough tests.
if len(TestSites) < 2 {
t.Skip("Must have at least two test sites to test autocompletion")
}

t.Cleanup(func() {
removeSites()
})

// Make sure we have some sites.
err := addSites()
require.NoError(t, err)

var siteNames []string
for _, site := range TestSites {
siteNames = append(siteNames, site.Name)
}

// ddev stop should show all running sites
out, err := exec.RunHostCommand(DdevBin, "__complete", "stop", "")
assert.NoError(err)
filteredOut := getTestingSitesFromOutput(out)
for _, name := range siteNames {
assert.Contains(filteredOut, name)
}

// Project names shouldn't be repeated
out, err = exec.RunHostCommand(DdevBin, "__complete", "stop", siteNames[0], "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
for i, name := range siteNames {
if i == 0 {
assert.NotContains(filteredOut, name)
} else {
assert.Contains(filteredOut, name)
}
}

// If we've used all the project names, there should be no further suggestions
allArgs := append([]string{"__complete", "stop"}, siteNames...)
allArgs = append(allArgs, "")
out, err = exec.RunHostCommand(DdevBin, allArgs...)
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
assert.Empty(filteredOut)

// If a project is stopped, it shouldn't be suggested for stop anymore
_, err = exec.RunHostCommand(DdevBin, "stop", siteNames[0])
assert.NoError(err)
out, err = exec.RunHostCommand(DdevBin, "__complete", "stop", "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
for i, name := range siteNames {
if i == 0 {
assert.NotContains(filteredOut, name)
} else {
assert.Contains(filteredOut, name)
}
}

// If all projects are stopped (or no projects exist), completion should be empty
allArgs = append([]string{"stop"}, siteNames...)
_, err = exec.RunHostCommand(DdevBin, allArgs...)
assert.NoError(err)
out, err = exec.RunHostCommand(DdevBin, "__complete", "stop", "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
assert.Empty(filteredOut)
}

// TestAutocompletionForStartCmd checks autocompletion of project names for ddev start
func TestAutocompletionForStartCmd(t *testing.T) {
assert := asrt.New(t)

// Skip if we don't have enough tests.
if len(TestSites) < 2 {
t.Skip("Must have at least two test sites to test autocompletion")
}

t.Cleanup(func() {
removeSites()
})

// Make sure we have some sites.
err := addSites()
require.NoError(t, err)

var siteNames []string
for _, site := range TestSites {
siteNames = append(siteNames, site.Name)
}

// If all projects are running, completion should be empty
out, err := exec.RunHostCommand(DdevBin, "__complete", "start", "")
assert.NoError(err)
filteredOut := getTestingSitesFromOutput(out)
assert.Empty(filteredOut)

// All stopped projects should display in completion
_, err = exec.RunHostCommand(DdevBin, "stop", siteNames[0])
assert.NoError(err)
out, err = exec.RunHostCommand(DdevBin, "__complete", "start", "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
for i, name := range siteNames {
if i == 0 {
assert.Contains(filteredOut, name)
} else {
assert.NotContains(filteredOut, name)
}
}

allArgs := append([]string{"stop"}, siteNames...)
_, err = exec.RunHostCommand(DdevBin, allArgs...)
assert.NoError(err)
out, err = exec.RunHostCommand(DdevBin, "__complete", "start", "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
for _, name := range siteNames {
assert.Contains(filteredOut, name)
}

// Project names shouldn't be repeated
out, err = exec.RunHostCommand(DdevBin, "__complete", "start", siteNames[0], "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
for i, name := range siteNames {
if i == 0 {
assert.NotContains(filteredOut, name)
} else {
assert.Contains(filteredOut, name)
}
}

// If we've used all the project names, there should be no further suggestions
allArgs = append([]string{"__complete", "start"}, siteNames...)
allArgs = append(allArgs, "")
out, err = exec.RunHostCommand(DdevBin, allArgs...)
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
assert.Empty(filteredOut)
}

// TestAutocompletionForStartCmd checks autocompletion of project names for ddev describe
func TestAutocompletionForDescribeCmd(t *testing.T) {
assert := asrt.New(t)

// Skip if we don't have enough tests.
if len(TestSites) < 2 {
t.Skip("Must have at least two test sites to test autocompletion")
}

t.Cleanup(func() {
removeSites()
})

// Make sure we have some sites.
err := addSites()
require.NoError(t, err)

var siteNames []string
for _, site := range TestSites {
siteNames = append(siteNames, site.Name)
}

// If all projects are running, completion should show all project names
out, err := exec.RunHostCommand(DdevBin, "__complete", "describe", "")
assert.NoError(err)
filteredOut := getTestingSitesFromOutput(out)
for _, name := range siteNames {
assert.Contains(filteredOut, name)
}

// Even stopped projects should display in completion
_, err = exec.RunHostCommand(DdevBin, "stop", siteNames[0])
assert.NoError(err)
out, err = exec.RunHostCommand(DdevBin, "__complete", "describe", "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
for _, name := range siteNames {
assert.Contains(filteredOut, name)
}

// If there's already an argument, nothing more should be suggested
out, err = exec.RunHostCommand(DdevBin, "__complete", "describe", "anything", "")
assert.NoError(err)
filteredOut = getTestingSitesFromOutput(out)
assert.Empty(filteredOut)
}

// getTestingSitesFromOutput() finds only the ddev list items that
// have names starting with "Test" from a space separated list of project names.
// This is useful when running the tests locally, to filter out projects that
// aren't test-related.
func getTestingSitesFromOutput(output string) []interface{} {
testSites := make([]interface{}, 0)
for _, siteName := range strings.Fields(output) {
if strings.HasPrefix(siteName, "Test") {
testSites = append(testSites, siteName)
}
}
return testSites
}
5 changes: 3 additions & 2 deletions cmd/ddev/cmd/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
)

var CleanCmd = &cobra.Command{
Use: "clean [projectname ...]",
Short: "Removes items DDEV has created",
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 0),
Use: "clean [projectname ...]",
Short: "Removes items DDEV has created",
Long: `Stops all running projects and then removes downloads and snapshots
for the selected projects. Then clean will remove "ddev/ddev-*" images.
Expand Down
8 changes: 5 additions & 3 deletions cmd/ddev/cmd/debug-compose-config.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package cmd

import (
"github.com/ddev/ddev/pkg/fileutil"
"strings"

"github.com/ddev/ddev/pkg/fileutil"

"github.com/ddev/ddev/pkg/ddevapp"
"github.com/ddev/ddev/pkg/output"
"github.com/ddev/ddev/pkg/util"
Expand All @@ -12,8 +13,9 @@ import (

// DebugComposeConfigCmd implements the ddev debug compose-config command
var DebugComposeConfigCmd = &cobra.Command{
Use: "compose-config [project]",
Short: "Prints the docker-compose configuration of the current project",
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1),
Use: "compose-config [project]",
Short: "Prints the docker-compose configuration of the current project",
Run: func(cmd *cobra.Command, args []string) {
projectName := ""

Expand Down
12 changes: 7 additions & 5 deletions cmd/ddev/cmd/debug-config-yaml.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package cmd

import (
"reflect"
"strings"

"github.com/ddev/ddev/pkg/ddevapp"
"github.com/ddev/ddev/pkg/output"
"github.com/ddev/ddev/pkg/util"
"github.com/spf13/cobra"
"reflect"
"strings"
)

// DebugConfigYamlCmd implements the ddev debug configyaml command
var DebugConfigYamlCmd = &cobra.Command{
Use: "configyaml [project]",
Short: "Prints the project config.*.yaml usage",
Example: "ddev debug configyaml, ddev debug configyaml <projectname>",
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1),
Use: "configyaml [project]",
Short: "Prints the project config.*.yaml usage",
Example: "ddev debug configyaml, ddev debug configyaml <projectname>",
Run: func(cmd *cobra.Command, args []string) {
projectName := ""

Expand Down
5 changes: 3 additions & 2 deletions cmd/ddev/cmd/debug-refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (

// DebugRefreshCmd implements the ddev debug refresh command
var DebugRefreshCmd = &cobra.Command{
Use: "refresh",
Short: "Refreshes Docker cache for project",
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1),
Use: "refresh",
Short: "Refreshes Docker cache for project",
Run: func(cmd *cobra.Command, args []string) {
projectName := ""

Expand Down
8 changes: 5 additions & 3 deletions cmd/ddev/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"

"github.com/ddev/ddev/pkg/ddevapp"
"github.com/ddev/ddev/pkg/util"
"github.com/spf13/cobra"
Expand All @@ -15,9 +16,10 @@ var deleteAll bool

// DeleteCmd provides the delete command
var DeleteCmd = &cobra.Command{
Use: "delete [projectname ...]",
Short: "Remove all project information (including database) for an existing project",
Long: `Removes all DDEV project information (including database) for an existing project, but does not touch the project codebase or the codebase's .ddev folder.'.`,
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 0),
Use: "delete [projectname ...]",
Short: "Remove all project information (including database) for an existing project",
Long: `Removes all DDEV project information (including database) for an existing project, but does not touch the project codebase or the codebase's .ddev folder.'.`,
Example: `ddev delete
ddev delete proj1 proj2 proj3
ddev delete --omit-snapshot proj1
Expand Down
7 changes: 4 additions & 3 deletions cmd/ddev/cmd/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import (

// DescribeCommand represents the `ddev config` command
var DescribeCommand = &cobra.Command{
Use: "describe [projectname]",
Aliases: []string{"status", "st", "desc"},
Short: "Get a detailed description of a running DDEV project.",
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1),
Use: "describe [projectname]",
Aliases: []string{"status", "st", "desc"},
Short: "Get a detailed description of a running DDEV project.",
Long: `Get a detailed description of a running DDEV project. Describe provides basic
information about a DDEV project, including its name, location, url, and status.
It also provides details for MySQL connections, and connection information for
Expand Down
7 changes: 4 additions & 3 deletions cmd/ddev/cmd/import-db.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import (
// NewImportDBCmd initializes and returns the `ddev import-db` command.
func NewImportDBCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "import-db [project]",
Args: cobra.RangeArgs(0, 1),
Short: "Import a SQL dump file into the project",
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1),
Use: "import-db [project]",
Args: cobra.RangeArgs(0, 1),
Short: "Import a SQL dump file into the project",
Long: heredoc.Doc(`
Import a SQL dump file into the project.
Expand Down
8 changes: 5 additions & 3 deletions cmd/ddev/cmd/logs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"github.com/ddev/ddev/pkg/ddevapp"
"github.com/ddev/ddev/pkg/util"
"github.com/spf13/cobra"
)
Expand All @@ -13,9 +14,10 @@ var (

// DdevLogsCmd contains the "ddev logs" command
var DdevLogsCmd = &cobra.Command{
Use: "logs [projectname]",
Short: "Get the logs from your running services.",
Long: `Uses 'docker logs' to display stdout from the running services.`,
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1),
Use: "logs [projectname]",
Short: "Get the logs from your running services.",
Long: `Uses 'docker logs' to display stdout from the running services.`,
Example: `ddev logs
ddev logs -f
ddev logs -s db
Expand Down
7 changes: 4 additions & 3 deletions cmd/ddev/cmd/mutagen-monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (

// MutagenMonitorCmd implements the ddev mutagen monitor command
var MutagenMonitorCmd = &cobra.Command{
Use: "monitor",
Short: "Monitor Mutagen status",
Example: `"ddev mutagen monitor", "ddev mutagen monitor <projectname>"`,
ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1),
Use: "monitor",
Short: "Monitor Mutagen status",
Example: `"ddev mutagen monitor", "ddev mutagen monitor <projectname>"`,
Run: func(cmd *cobra.Command, args []string) {
projectName := ""
if len(args) > 1 {
Expand Down

0 comments on commit af7e579

Please sign in to comment.