Skip to content

Commit

Permalink
feat: add aliases for custom commands, art alias for artisan, `dd…
Browse files Browse the repository at this point in the history
…ev aliases` command, fixes ddev#6443 (ddev#6434) [skip ci]
  • Loading branch information
stasadev authored Aug 5, 2024
1 parent c6a56eb commit f9519f0
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 3 deletions.
85 changes: 85 additions & 0 deletions cmd/ddev/cmd/aliases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cmd

import (
"bytes"
"fmt"
"sort"
"strings"

"github.com/ddev/ddev/pkg/styles"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)

// AliasesCmd implements the command to list all command aliases
var AliasesCmd = &cobra.Command{
Use: "aliases",
Short: "Shows all aliases for each command in the current context (global or project).",
Example: `ddev aliases`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
var out bytes.Buffer
t := table.NewWriter()
t.SetOutputMirror(&out)

// Use simple output
t.SetStyle(styles.GetTableStyle("default"))

// Append table header
t.AppendHeader(table.Row{"Command", "Aliases"})

// Collect aliases for all commands
commandAliases := make(map[string][]string)
collectAliases(RootCmd, commandAliases)

// Get a sorted list of command paths
sortedCommands := make([]string, 0, len(commandAliases))
for cmdPath := range commandAliases {
sortedCommands = append(sortedCommands, cmdPath)
}
sort.Strings(sortedCommands)

// Append rows to the table in sorted order
for _, cmdPath := range sortedCommands {
t.AppendRows([]table.Row{
{cmdPath, strings.Join(commandAliases[cmdPath], ", ")},
})
}

// Render the table
t.Render()
fmt.Println(out.String())
},
}

// collectAliases collects aliases for all commands in the app
func collectAliases(cmd *cobra.Command, commandAliases map[string][]string) {
if len(cmd.Aliases) > 0 {
fullAliases := make([]string, len(cmd.Aliases))
for i, alias := range cmd.Aliases {
fullAliases[i] = strings.TrimSpace(buildFullCommandPath(cmd, alias))
}
commandAliases[strings.TrimSpace(trimProgramName(cmd.CommandPath()))] = fullAliases
}
for _, subCmd := range cmd.Commands() {
collectAliases(subCmd, commandAliases)
}
}

// buildFullCommandPath constructs the full command path using the alias, excluding the program name
func buildFullCommandPath(cmd *cobra.Command, alias string) string {
return trimProgramName(cmd.Parent().CommandPath()) + " " + alias
}

// trimProgramName removes the program name from the command path
func trimProgramName(commandPath string) string {
parts := strings.SplitN(commandPath, " ", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
}

func init() {
RootCmd.AddCommand(AliasesCmd)
}
13 changes: 13 additions & 0 deletions cmd/ddev/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ func addCustomCommandsFromDir(rootCmd *cobra.Command, app *ddevapp.DdevApp, serv
example = " " + strings.ReplaceAll(val, `\n`, "\n ")
}

var aliases []string
if val, ok := directives["Aliases"]; ok {
for _, alias := range strings.Split(val, ",") {
alias = strings.TrimSpace(alias)
if foundCmd, _, err := rootCmd.Find([]string{alias}); err != nil {
aliases = append(aliases, alias)
} else {
util.Warning("Command '%s' cannot have alias '%s' that is already in use by command '%s', skipping it", commandName, alias, foundCmd.Name())
}
}
}

autocompleteTerms := []string{}
if val, ok := directives["AutocompleteTerms"]; ok {
if err = json.Unmarshal([]byte(val), &autocompleteTerms); err != nil {
Expand Down Expand Up @@ -263,6 +275,7 @@ func addCustomCommandsFromDir(rootCmd *cobra.Command, app *ddevapp.DdevApp, serv
Use: usage,
Short: description + descSuffix,
Example: example,
Aliases: aliases,
DisableFlagParsing: disableFlags,
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true,
Expand Down
4 changes: 2 additions & 2 deletions cmd/ddev/cmd/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func TestCustomCommands(t *testing.T) {
app.Type = origAppType

// The various CMS commands should not be available here
for _, c := range []string{"artisan", "cake", "drush", "magento", "typo3", "wp"} {
for _, c := range []string{"artisan", "art", "cake", "drush", "magento", "typo3", "wp"} {
_, err = exec.RunHostCommand(DdevBin, c, "-h")
assert.Error(err, "found command %s when it should not have been there (no error) app.Type=%s", c, app.Type)
}
Expand Down Expand Up @@ -241,7 +241,7 @@ func TestCustomCommands(t *testing.T) {
_, _ = exec.RunHostCommand(DdevBin)
err = app.MutagenSyncFlush()
assert.NoError(err)
for _, c := range []string{"artisan", "pint"} {
for _, c := range []string{"artisan", "art", "pint"} {
_, err = exec.RunHostCommand(DdevBin, "help", c)
assert.NoError(err)
}
Expand Down
8 changes: 8 additions & 0 deletions docs/content/users/extend/custom-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ Usage: `## Example: <command-example>`

Example: `## Example: commandname\ncommandname -h`

### `Aliases` Annotation

If your command should have one or more aliases, add the `Aliases` annotation. Multiple aliases are separated by a comma:

Usage: `## Aliases: <list-of-aliases>`

Example: `## Aliases: cacheclear,cache-clear,cache:clear`

### `Flags` Annotation

`Flags` should explain any available flags, including their shorthand when relevant, for the help message. It has to be encoded according the following definition:
Expand Down
11 changes: 11 additions & 0 deletions docs/content/users/usage/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ Two flags are available for every command:

---

## `aliases`

Shows all aliases for each command in the current context (global or project).

```shell
# Print a list of all available command aliases
ddev aliases
```

## `auth`

Authentication commands.
Expand All @@ -81,6 +90,8 @@ Flags:

## `artisan`

*Aliases: `art`.*

Run the `artisan` command; available only in projects of type `laravel`, and only available if `artisan` is in the project root.

```shell
Expand Down
2 changes: 1 addition & 1 deletion pkg/ddevapp/global_dotddev_assets/commands/web/artisan
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
## Description: Run artisan CLI inside the web container
## Usage: artisan [flags] [args]
## Example: "ddev artisan list" or "ddev artisan cache:clear"
## Aliases: art
## ProjectTypes: laravel
## ExecRaw: true

php ./artisan "$@"

0 comments on commit f9519f0

Please sign in to comment.