diff --git a/cmd/ddev/cmd/cd.go b/cmd/ddev/cmd/cd.go new file mode 100644 index 00000000000..a3948a2e4d0 --- /dev/null +++ b/cmd/ddev/cmd/cd.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "fmt" + "github.com/ddev/ddev/pkg/ddevapp" + "github.com/ddev/ddev/pkg/fileutil" + "github.com/ddev/ddev/pkg/globalconfig" + "github.com/ddev/ddev/pkg/heredoc" + "github.com/ddev/ddev/pkg/output" + "github.com/ddev/ddev/pkg/util" + "github.com/spf13/cobra" + "path/filepath" +) + +var ( + bashFile = filepath.Join(globalconfig.GetGlobalDdevDir(), "commands/host/shells/ddev.sh") + fishFile = filepath.Join(globalconfig.GetGlobalDdevDir(), "commands/host/shells/ddev.fish") +) + +// CdCmd is the top-level "ddev cd" command +var CdCmd = &cobra.Command{ + Use: "cd [project-name]", + Short: "Uses shell built-in 'cd' to change to a project directory", + Long: heredoc.Doc(fmt.Sprintf(` + To enable the 'ddev cd' command, source the ddev.sh script from your rc-script. + + For bash: + + printf '\n[ -f "%s" ] && source "%s"\n' >> ~/.bashrc + + For zsh: + + printf '\n[ -f "%s" ] && source "%s"\n' >> ~/.zshrc + + For fish: + + printf '\n[ -f "%s" ] && source "%s"\n' >> ~/.config/fish/config.fish + + Restart your shell, and use 'ddev cd project-name'. + `, bashFile, bashFile, bashFile, bashFile, fishFile, fishFile)), + ValidArgsFunction: ddevapp.GetProjectNamesFunc("all", 1), + Example: `ddev cd project-name`, + Run: func(cmd *cobra.Command, args []string) { + for _, file := range []string{bashFile, fishFile} { + if !fileutil.FileExists(file) { + util.Failed("Unable to find file: %s", file) + } + + } + if len(args) != 1 { + util.Failed("This command only takes one argument: project-name") + } + projectName := args[0] + app, err := ddevapp.GetActiveApp(projectName) + if err != nil { + util.Failed("Failed to find path for project: %v", err) + } + if cmd.Flags().Changed("get-approot") { + output.UserOut.Println(app.AppRoot) + } else { + output.UserOut.Println(cmd.Long) + } + }, +} + +func init() { + CdCmd.Flags().BoolP("get-approot", "", false, "Get the full path to the project root directory") + _ = CdCmd.Flags().MarkHidden("get-approot") + RootCmd.AddCommand(CdCmd) +} diff --git a/cmd/ddev/cmd/cd_test.go b/cmd/ddev/cmd/cd_test.go new file mode 100644 index 00000000000..72d98843920 --- /dev/null +++ b/cmd/ddev/cmd/cd_test.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "github.com/ddev/ddev/pkg/exec" + "github.com/ddev/ddev/pkg/globalconfig" + "github.com/ddev/ddev/pkg/util" + asrt "github.com/stretchr/testify/assert" + "path/filepath" + "strings" + "testing" +) + +// TestCdCmd runs `ddev cd` to see if it works. +func TestCdCmd(t *testing.T) { + assert := asrt.New(t) + // Shows help + out, err := exec.RunHostCommand(DdevBin, "cd", TestSites[0].Name) + assert.NoError(err) + assert.Contains(out, filepath.Join(globalconfig.GetGlobalDdevDir(), "commands/host/shells/ddev.sh")) + assert.Contains(out, filepath.Join(globalconfig.GetGlobalDdevDir(), "commands/host/shells/ddev.fish")) + // Returns the path to the project + out, err = exec.RunHostCommand(DdevBin, "cd", TestSites[0].Name, "--get-approot") + assert.NoError(err) + assert.Equal(strings.TrimRight(out, "\n"), TestSites[0].Dir) + // Shows error + out, err = exec.RunHostCommand(DdevBin, "cd", "does-not-exist-"+util.RandString(4)) + assert.Error(err) + assert.Contains(out, "Failed to find path for project") + // Shows error + out, err = exec.RunHostCommand(DdevBin, "cd") + assert.Error(err) + assert.Contains(out, "This command only takes one argument: project-name") +} diff --git a/docs/content/users/usage/commands.md b/docs/content/users/usage/commands.md index d3c27e1ed81..b96e7586037 100644 --- a/docs/content/users/usage/commands.md +++ b/docs/content/users/usage/commands.md @@ -227,6 +227,17 @@ Run the `cake` command; available only in projects of type `cakephp`, and only a ddev cake ``` +## `cd` + +Uses shell built-in `cd` to change to a project directory. For example, `ddev cd some-project` will change directories to the project root of the project named `some-project`. + +Note that this command can't work until you make a small addition to your `.bashrc`, `.zshrc`, or `config.fish`. To see the explanation of what you need to do: + +```shell +# Where some-project is a project from the `ddev list` +ddev cd some-project +``` + ## `clean` Removes items DDEV has created. (See [Uninstalling DDEV](../usage/uninstall.md).) diff --git a/pkg/ddevapp/global_dotddev_assets/commands/host/shells/ddev.fish b/pkg/ddevapp/global_dotddev_assets/commands/host/shells/ddev.fish new file mode 100644 index 00000000000..d36f43dfb06 --- /dev/null +++ b/pkg/ddevapp/global_dotddev_assets/commands/host/shells/ddev.fish @@ -0,0 +1,20 @@ +#ddev-generated +# This script should be sourced in the context of your shell like so: +# source $HOME/.ddev/commands/host/shells/ddev.fish +# Alternatively, it can be installed into one of the directories +# that fish uses to autoload functions (e.g ~/.config/fish/functions) +# Once the ddev() function is defined, you can type +# "ddev cd project-name" to cd into the project directory. + +function ddev + if test (count $argv) -eq 2 -a "$argv[1]" = "cd" + switch "$argv[2]" + case '-*' + command ddev $argv + case '*' + cd (DDEV_VERBOSE=false command ddev cd "$argv[2]" --get-approot) + end + else + command ddev $argv + end +end diff --git a/pkg/ddevapp/global_dotddev_assets/commands/host/shells/ddev.sh b/pkg/ddevapp/global_dotddev_assets/commands/host/shells/ddev.sh new file mode 100644 index 00000000000..b01098fcf54 --- /dev/null +++ b/pkg/ddevapp/global_dotddev_assets/commands/host/shells/ddev.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh +#ddev-generated +# This script should be sourced in the context of your shell like so: +# source $HOME/.ddev/commands/host/shells/ddev.sh +# Once the ddev() function is defined, you can type +# "ddev cd project-name" to cd into the project directory. + +ddev() { + if [ "$#" -eq 2 ] && [ "$1" = "cd" ]; then + case "$2" in + -*) command ddev "$@" ;; + *) cd "$(DDEV_VERBOSE=false command ddev cd "$2" --get-approot)" ;; + esac + else + command ddev "$@" + fi +}