<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tomfeigin</title>
    <description>The latest articles on DEV Community by tomfeigin (@tomfeigin).</description>
    <link>https://dev.to/tomfeigin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1132231%2Fd6f2ea0c-827b-4b70-bc5f-f3d1d16a3f01.jpeg</url>
      <title>DEV Community: tomfeigin</title>
      <link>https://dev.to/tomfeigin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tomfeigin"/>
    <language>en</language>
    <item>
      <title>Auto-Completion and Cocktail mixing with Golang’s Cobra CLI</title>
      <dc:creator>tomfeigin</dc:creator>
      <pubDate>Mon, 07 Aug 2023 12:47:15 +0000</pubDate>
      <link>https://dev.to/tomfeigin/auto-completion-and-cocktail-mixing-with-golangs-cobra-cli-h26</link>
      <guid>https://dev.to/tomfeigin/auto-completion-and-cocktail-mixing-with-golangs-cobra-cli-h26</guid>
      <description>&lt;p&gt;At &lt;a href="http://raftt.io" rel="noopener noreferrer"&gt;Raftt&lt;/a&gt; we build a dev tool which enables effortless development on Kubernetes envs. Development environments created with Raftt can be interacted with and controlled via a CLI program which we built with the Go programming language. Recently we added the ability to auto-complete the names of Kubernetes resources in the dev env. The auto-completion feature improved our UX and made many users happy that they can leverage the terminal to auto-complete resource names instead of having to type them out.&lt;/p&gt;

&lt;p&gt;In this post we explain how to implement custom auto-completion ability for a CLI tool written in Go and using &lt;a href="https://cobra.dev/" rel="noopener noreferrer"&gt;Cobra&lt;/a&gt;. We start by walking through the creation of a tiny &lt;a href="https://github.com/rafttio/mixologist" rel="noopener noreferrer"&gt;mixologist&lt;/a&gt; app using Cobra. The mixologist app is a CLI tool written in Go using the Cobra framework that can make cocktails from a list of ingredients and uses custom auto-completion to improve the user experience. For reference, the code for the CLI application can be found here: &lt;a href="https://github.com/rafttio/mixologist" rel="noopener noreferrer"&gt;https://github.com/rafttio/mixologist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is an example of auto completion of the &lt;code&gt;mixologist&lt;/code&gt; CLI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfc61cb0740c324e3c27e1_mixologist-example-gif.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfc61cb0740c324e3c27e1_mixologist-example-gif.gif" alt="Auto-completion of the mixologist app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post will then cover how to enable basic auto-completion of a specific subcommand, and finally, how to implement custom auto-completion. The post concludes with a brief discussion of how this feature is used in the Raftt CLI. &lt;strong&gt;‍&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a CLI utility with Cobra
&lt;/h3&gt;

&lt;p&gt;This paragraph discusses how to enable auto-completion in a CLI tool written in Go using the Cobra framework. It walks through the process of creating a small application using Cobra, adding a subcommand, and implementing basic auto-completion for that subcommand. It then explains how to implement custom auto-completion in order to improve the user experience.&lt;/p&gt;

&lt;h4&gt;
  
  
  What is Cobra 🐍
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Cobra is a library for creating powerful modern CLI applications. &lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;https://github.com/spf13/cobra&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You probably already know about Cobra if you ever wrote a CLI tool in Go, but for the few who don't, this article contains a short introduction on how to use it.&lt;/p&gt;

&lt;p&gt;For CLI utilities written in Go, Cobra is the go-to command line wrapper, used by the likes of &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;, &lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;Github CLI&lt;/a&gt;, &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt;, &lt;a href="https://github.com/spf13/cobra/blob/main/site/content/projects_using_cobra.md" rel="noopener noreferrer"&gt;and many more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the next section we will build a small application in Go using the &lt;code&gt;cobra&lt;/code&gt; framework.&lt;/p&gt;

&lt;h4&gt;
  
  
  The mixologist app
&lt;/h4&gt;

&lt;p&gt;In this introduction we will create a &lt;code&gt;mixologist&lt;/code&gt; app. The app will act as a &lt;code&gt;mixologist&lt;/code&gt; which can make a cocktail from a list of ingredients.&lt;/p&gt;

&lt;p&gt;First create the root command for our app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// cmd/root.govar&lt;/span&gt;
&lt;span class="n"&gt;rootCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"mixologist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Mixologist is your personal bartender."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`Mixologist acts as a bartender who specializes in cocktail making.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RunE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Help&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rootCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rootCmd&lt;/code&gt; is the entry-point of our &lt;code&gt;mixologist&lt;/code&gt; app, it prints the help and exits. As mentioned before, we want our &lt;code&gt;mixologist&lt;/code&gt; to mix cocktails, to do that we will add the sub command &lt;code&gt;mix&lt;/code&gt;. The mix subcommand will receive at least 2 arguments, controlled by the &lt;code&gt;Args&lt;/code&gt; field of the &lt;code&gt;cobra.Command&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// cmd/mix.go&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/spf13/cobra"&lt;/span&gt;
    &lt;span class="s"&gt;"golang.org/x/exp/slices"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mixCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"mix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"make a cocktail."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;`Mix some ingredients together to make a cocktail`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MinimumNArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;RunE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Psuedo code&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IndexFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Vodka"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IndexFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Orange Juice"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You can make a screwdriver cocktail!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"i can't make a cocktail from %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rootCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mixCmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now need to tie it all together in our &lt;code&gt;main.go&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;
    &lt;span class="err"&gt;‍&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"{pathToYourApp}/cmd"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By executing the &lt;code&gt;mixologist&lt;/code&gt; with the correct ingredients, it can make us a Vodka Screwdriver! But if we get the ingredients wrong we will get an error.&lt;/p&gt;

&lt;p&gt;In the next paragraph we will demonstrate how we can leverage &lt;code&gt;cobra&lt;/code&gt; to have auto-completion in our terminal and make it easier for our users to enjoy our app.&lt;/p&gt;

&lt;h4&gt;
  
  
  Basic Auto-completion
&lt;/h4&gt;

&lt;p&gt;Users of the &lt;code&gt;mixologist&lt;/code&gt; app may find it difficult to guess the available ingredients. We should make it easier to use the app by making our CLI utility auto complete an ingredient if it is available in our bar.&lt;/p&gt;

&lt;p&gt;To implement basic auto completion we will set the &lt;code&gt;ValidArgs&lt;/code&gt; field of the command like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;availableIngredients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"Vodka"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Gin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Orange Juice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Triple Sec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Tequila"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"simple syrup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"white rum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Kahlua coffee liqueur"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mixCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"mix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"make a cocktail."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ValidArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;availableIngredients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to install the auto-completion in our shell, cobra can generate auto completion for &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;zsh&lt;/code&gt; and &lt;code&gt;fish&lt;/code&gt; shells out of the box. After choosing the shell we can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./mixologist completion zsh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/completion
&lt;span class="nb"&gt;source&lt;/span&gt; /tmp/completion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then when we type &lt;code&gt;mixologist [tab][tab]&lt;/code&gt; we will get the list of ingredients auto completed!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfc8f7772b2c24b04534b2_mixologist-basic-autocomplete.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfc8f7772b2c24b04534b2_mixologist-basic-autocomplete.gif" alt="Auto-completion of ingredients for the mix subcommand"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is really cool but it still isn't optimal, the command can auto-complete ingredients which when mixed together doesn't make any cocktail. We want to auto-complete the next ingredient only if it can actually combine with all previously mentioned ingredients. To achieve that optimal user experience we need to implement custom auto-completion, we will see how to do that in the next paragraph.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Auto completion
&lt;/h3&gt;

&lt;p&gt;Now our &lt;code&gt;mixologist&lt;/code&gt; app auto-completes the ingredient list in when pressing tab on the &lt;code&gt;mix&lt;/code&gt; subcommand, but we want to make it smarter.&lt;/p&gt;

&lt;p&gt;Our opinionated bartender believes that the only thing that can be paired with Vodka is Orange Juice, so we want our auto-completion to suggest only Orange Juice if the previously mentioned ingredient was Vodka. We also don't want to repeat the same ingredient twice so we won't end up mixing Kahlua with Kahlua 4 times.&lt;/p&gt;

&lt;p&gt;To achieve that we need to edit the &lt;code&gt;mix&lt;/code&gt; subcommand and use the awesome &lt;a href="https://github.com/samber/lo" rel="noopener noreferrer"&gt;&lt;code&gt;lo&lt;/code&gt;&lt;/a&gt; package for some help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/samber/lo"&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;availableIngredients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mixCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"mix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"make a cocktail."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ValidArgsFunction&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toComplete&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShellCompDirective&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Vodka"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Orange Juice"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShellCompDirectiveNoFileComp&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unusedIngredients&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;availableIngredients&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unusedIngredients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShellCompDirectiveNoFileComp&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we use the auto-completion of the &lt;code&gt;mix&lt;/code&gt; subcommand by pressing &lt;code&gt;tab&lt;/code&gt; twice, the terminal will show:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfc9fefacc755c5e72e0e8_mixologist-custom.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfc9fefacc755c5e72e0e8_mixologist-custom.gif" alt="custom-auto-completing-igredients-gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that selected ingredients do not appear as an option for subsequent auto-completions anymore.&lt;/p&gt;

&lt;p&gt;If we give &lt;code&gt;Vokda&lt;/code&gt; as the first argument and then press &lt;code&gt;tab&lt;/code&gt; twice, the terminal will auto complete &lt;code&gt;Orange Juice&lt;/code&gt; immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfca25fa988372a146d730_mixologist-custom-2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F627bf36ecef36c976239c7b6%2F64bfca25fa988372a146d730_mixologist-custom-2.gif" alt="custom-auto-comleting-ingredients-gif-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new auto-completion improved the UX of our &lt;code&gt;mixologist&lt;/code&gt; app and made our users much happier 🙂.&lt;/p&gt;

&lt;p&gt;Now that we know how to implement custom auto-completion using the &lt;code&gt;cobra&lt;/code&gt; framework, we will discuss in short how we use this feature in the &lt;code&gt;raftt&lt;/code&gt; CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto completion in raftt ⛵
&lt;/h3&gt;

&lt;p&gt;In the Raftt CLI, we have implemented custom auto-completion using the Cobra framework. This feature allows users to easily and quickly input valid Kubernetes resource names when interacting with resources in the Raftt dev environment. With custom auto-completion, users can select only valid resource names, improving the overall user experience and preventing frustrating typos.&lt;/p&gt;

&lt;p&gt;If you are following along our tutorial &lt;a href="https://docs.raftt.io/basics/tutorials/connect_mode" rel="noopener noreferrer"&gt;here&lt;/a&gt;, you can try out our auto-complete for yourself when you convert the &lt;code&gt;frontend&lt;/code&gt; or &lt;code&gt;recommendations&lt;/code&gt; services to dev mode. Write &lt;code&gt;raftt dev [tab][tab]&lt;/code&gt; and see it list the options 🙂. The tightly integrated auto completion provided us with blazing fast and effortless UX without having to worry about typos.&lt;/p&gt;

&lt;p&gt;The step where we installed the completion by running the &lt;code&gt;source&lt;/code&gt; command above is performed for you if you have installed Raftt using &lt;code&gt;brew&lt;/code&gt; or &lt;code&gt;snap&lt;/code&gt;. Otherwise, add &lt;code&gt;eval $(raftt completion &amp;lt;SHELL&amp;gt;)&lt;/code&gt; to your shell's &lt;code&gt;rc&lt;/code&gt; file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Performance
&lt;/h4&gt;

&lt;p&gt;Lastly we should briefly touch the efficiency of the auto-completion feature, if the auto-completion logic is complicated or depends on remote services, consecutive completions will be slow and result in a bad user experience. For example when auto-completing a long list of Kubernetes resources by repeatedly pressing the &lt;code&gt;[tab][tab]&lt;/code&gt; combination. To mitigate this issue, we can cache the results of the complicated auto-completion logic.&lt;/p&gt;

&lt;p&gt;Caching auto-complete results introduces two new problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Where are the results stored? The CLI itself only runs for a second to generate the completion, and does not stick around. We could potentially put them on disk, but that means dealing with multiple accessors at once, locking, etc. Raftt has a daemon that it starts up, and it was natural to cache the results there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Out of date information. If our cache is too long, we might be serving incorrect information. For example, if the Kubernetes resources have since been deleted. We settled on 3 seconds as a reasonable duration.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In conclusion, implementing custom auto-completion for a CLI tool using Cobra can greatly improve the user experience and make the tool more efficient to use. By following the steps outlined in this post, you can add this feature to your own Go-based CLI tool. With auto-completion, users can more easily navigate and interact with your tool, reducing errors and improving the overall experience. Thank you for auto-completing this post 🙂&lt;/p&gt;

&lt;p&gt;The code for the mixologist app can be found here: &lt;a href="https://github.com/rafttio/mixologist" rel="noopener noreferrer"&gt;https://github.com/rafttio/mixologist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are interested in how Raftt can help you be develop effectively on local or remote Kubernetes clusters check out our tutorials &lt;a href="https://docs.raftt.io/basics/tutorials" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>cli</category>
      <category>ux</category>
    </item>
  </channel>
</rss>
