diff --git a/cli/cli.go b/cli/cli.go index 44504de44ed..927ff4ed323 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -24,6 +24,7 @@ import ( "github.com/arduino/arduino-cli/cli/board" "github.com/arduino/arduino-cli/cli/cache" "github.com/arduino/arduino-cli/cli/compile" + "github.com/arduino/arduino-cli/cli/completion" "github.com/arduino/arduino-cli/cli/config" "github.com/arduino/arduino-cli/cli/core" "github.com/arduino/arduino-cli/cli/daemon" @@ -77,6 +78,7 @@ func createCliCommandTree(cmd *cobra.Command) { cmd.AddCommand(board.NewCommand()) cmd.AddCommand(cache.NewCommand()) cmd.AddCommand(compile.NewCommand()) + cmd.AddCommand(completion.NewCommand()) cmd.AddCommand(config.NewCommand()) cmd.AddCommand(core.NewCommand()) cmd.AddCommand(daemon.NewCommand()) diff --git a/cli/completion/completion.go b/cli/completion/completion.go new file mode 100644 index 00000000000..ffe5472728e --- /dev/null +++ b/cli/completion/completion.go @@ -0,0 +1,76 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package completion + +import ( + "bytes" + "os" + "strings" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/spf13/cobra" +) + +var ( + completionNoDesc bool //Disable completion description for shells that support it +) + +// NewCommand created a new `version` command +func NewCommand() *cobra.Command { + command := &cobra.Command{ + Use: "completion [bash|zsh|fish] [--no-descriptions]", + ValidArgs: []string{"bash", "zsh", "fish"}, + Args: cobra.ExactArgs(1), + Short: "Generates completion scripts", + Long: "Generates completion scripts for various shells", + Example: " " + os.Args[0] + " completion bash > completion.sh\n" + + " " + "source completion.sh", + Run: run, + } + command.Flags().BoolVar(&completionNoDesc, "no-descriptions", false, "Disable completion description for shells that support it") + + return command +} + +func run(cmd *cobra.Command, args []string) { + if completionNoDesc && (args[0] == "bash" || args[0] == "zsh") { + feedback.Errorf("Error: command description is not supported by %v", args[0]) + os.Exit(errorcodes.ErrGeneric) + } + switch args[0] { + case "bash": + cmd.Root().GenBashCompletion(os.Stdout) + break + case "zsh": + buf := new(bytes.Buffer) + cmd.Root().GenZshCompletion(buf) + // Next 3 lines are Hack, we'll wait new version of cobra with ZshV2Completion https://github.com/spf13/cobra/pull/1070 + //insert escaping before [ and ] + s := strings.ReplaceAll(buf.String(), "[", "\\[") + s = strings.ReplaceAll(s, "]", "\\]") + s = strings.ReplaceAll(s, "\\[1\\]", "[1]") // revert the case + os.Stdout.WriteString(s) + break + case "fish": + buf := new(bytes.Buffer) + cmd.Root().GenFishCompletion(buf, !completionNoDesc) + // Next 2 lines are Hack, fixed here https://github.com/spf13/cobra/pull/1122 + s := strings.ReplaceAll(buf.String(), "arduino-cli_comp", "arduino_cli_comp") //required because fish does not support env variables with "-" in the name + os.Stdout.WriteString(s) + break + } +} diff --git a/docs/command-line-completion.md b/docs/command-line-completion.md new file mode 100644 index 00000000000..35455e7c90e --- /dev/null +++ b/docs/command-line-completion.md @@ -0,0 +1,45 @@ +`arduino-cli` supports command-line completion (also known as *tab completion*) for basic commands. +Currently only `bash`, `zsh`, `fish` shells are supported + +### Before you start +In order to generate the file required to make the completion work you have to [install](installation.md) Arduino CLI first. + +### Generate the completion file +To generate the completion file you can use `arduino-cli completion [bash|zsh|fish] [--no-descriptions]`. +By default this command will print on the standard output (the shell window) the content of the completion file. To save to an actual file use the `>` redirect symbol. + +### Bash +Use `arduino-cli completion bash > arduino-cli.sh` to generate the completion file. +At this point you can move that file in `/etc/bash_completion.d/` (root access is required) with `sudo mv arduino-cli.sh /etc/bash_completion.d/`. + +A not recommended alternative is to source the completion file in `.bashrc`. + +Remember to open a new shell to test the functionality + +### Zsh +Use `arduino-cli completion zsh > _arduino-cli` to generate the completion file. +At this point you can place the file in a directory listed in your `fpath` if you have already created a directory to store your completion. + +Or if you want you can create a directory, add it to your `fpath` and copy the file in it: + +1. `mkdir ~/completion_zsh` +2. add `fpath=($HOME/completion_zsh $fpath)` at the beginning of your `.zshrc` file +3. `mv _arduino-cli ~/completion_zsh/` + +Remember to open a new shell to test the functionality + +*N.B.* +The Zsh completion is working with [Oh-My-Zsh](https://ohmyz.sh/) but not with [Prezto](https://github.com/sorin-ionescu/prezto) (the zsh completion system is working in a different way than classic zsh). But hopefully it will be fixed in the future + +### Fish +Use `arduino-cli completion fish > arduino-cli.fish` to generate the completion file. +At this point you can place the file in `~/.config/fish/completions` as stated in the [official documentation](http://fishshell.com/docs/current/index.html#where-to-put-completions). +Remember to create the directory if it's not already there `mkdir -p ~/.config/fish/completions/` and then place the completion file in there with `mv arduino-cli.fish ~/.config/fish/completions/` + +Remember to open a new shell to test the functionality + +#### Disabling command and flag descriptions +By default fish completion has command and flag description enabled by default. If you want to disable this behaviour you can simply pass the `--no-descriptions` flag when calling `completion` command and the generated file will not have descriptions + +*N.B.* +This flag is not compatible with bash or zsh diff --git a/docsgen/go.mod b/docsgen/go.mod index 6ef7b4955a5..331cf3cb951 100644 --- a/docsgen/go.mod +++ b/docsgen/go.mod @@ -6,5 +6,5 @@ replace github.com/arduino/arduino-cli => ../ require ( github.com/arduino/arduino-cli v0.0.0 - github.com/spf13/cobra v0.0.6 + github.com/spf13/cobra v1.0.0 ) diff --git a/go.mod b/go.mod index fa398bf170e..e1b824f9251 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/schollz/closestmatch v2.1.0+incompatible github.com/segmentio/stats/v4 v4.5.3 github.com/sirupsen/logrus v1.4.2 - github.com/spf13/cobra v0.0.5 + github.com/spf13/cobra v1.0.0 github.com/spf13/jwalterweatherman v1.0.0 github.com/spf13/viper v1.6.2 github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 52b9bdc331c..95aa17bc879 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY= @@ -171,6 +173,8 @@ github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5H github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ= @@ -179,6 +183,8 @@ github.com/segmentio/objconv v1.0.1 h1:QjfLzwriJj40JibCV3MGSEiAoXixbp4ybhwfTB8RX github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= github.com/segmentio/stats/v4 v4.5.3 h1:Y/DSUWZ4c8ICgqJ9rQohzKvGqGWbLPWad5zmxVoKN+Y= github.com/segmentio/stats/v4 v4.5.3/go.mod h1:LsaahUJR7iiSs8mnkvQvdQ/RLHAS5adGLxuntg0ydGo= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -194,12 +200,15 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/mkdocs.yml b/mkdocs.yml index af169843596..0e2e2375a7c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -56,6 +56,7 @@ nav: - Documentation Home: index.md - installation.md - getting-started.md + - command-line-completion.md - CONTRIBUTING.md - FAQ.md - Command reference: @@ -68,6 +69,7 @@ nav: - cache: commands/arduino-cli_cache.md - cache clean: commands/arduino-cli_cache_clean.md - compile: commands/arduino-cli_compile.md + - completion: commands/arduino-cli_completion.md - config: commands/arduino-cli_config.md - config dump: commands/arduino-cli_config_dump.md - config init: commands/arduino-cli_config_init.md diff --git a/test/test_completion.py b/test/test_completion.py new file mode 100644 index 00000000000..3f6bc303534 --- /dev/null +++ b/test/test_completion.py @@ -0,0 +1,63 @@ +# This file is part of arduino-cli. +# +# Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +# +# This software is released under the GNU General Public License version 3, +# which covers the main part of arduino-cli. +# The terms of this license can be found at: +# https://www.gnu.org/licenses/gpl-3.0.en.html +# +# You can be released from the requirements of the above licenses by purchasing +# a commercial license. Buying such a license is mandatory if you want to modify or +# otherwise use the software for commercial activities involving the Arduino +# software without disclosing the source code of your own applications. To purchase +# a commercial license, send an email to license@arduino.cc. + +import pytest + +def test_completion_no_args(run_command): + result = run_command("completion") + assert not result.ok + assert "Error: accepts 1 arg(s), received 0" in result.stderr + assert result.stdout == "" + +def test_completion_bash(run_command): + result = run_command("completion bash") + assert result.ok + assert result.stderr == "" + assert "_arduino-cli_root_command()" in result.stdout + assert "__start_arduino-cli()" in result.stdout + +def test_completion_zsh(run_command): + result = run_command("completion zsh") + assert result.ok + assert result.stderr == "" + assert "#compdef _arduino-cli arduino-cli" in result.stdout + assert "function _arduino-cli" in result.stdout + +def test_completion_fish(run_command): + result = run_command("completion fish") + assert result.ok + assert result.stderr == "" + assert "# fish completion for arduino-cli" in result.stdout + assert "function __arduino-cli_perform_completion" in result.stdout + +def test_completion_bash_no_desc(run_command): + result = run_command("completion bash --no-descriptions") + assert not result.ok + assert result.stdout == "" + assert "Error: command description is not supported by bash" in result.stderr + +def test_completion_zsh_no_desc(run_command): + result = run_command("completion zsh --no-descriptions") + assert not result.ok + assert result.stdout == "" + assert "Error: command description is not supported by zsh" in result.stderr + +def test_completion_fish_no_desc(run_command): + result = run_command("completion fish --no-descriptions") + assert result.ok + assert result.stderr == "" + assert "# fish completion for arduino-cli" in result.stdout + assert "function __arduino-cli_perform_completion" in result.stdout + assert "__completeNoDesc" in result.stdout \ No newline at end of file