Create CLI in Go and Publish it to Homebrew
Elevate Your Go CLI Game: Share Your Tool with the World on Homebrew
Introduction
This easy-to-follow blog post will explain some basic examples of creating a Command Line Interface (CLI) using Golang and the Cobra package. After that, I’ll guide you on preparing your package and publishing it on Homebrew for others to use.
Cobra Lib
Cobra is a popular library in Golang that provides a simple interface for creating command-line interface (CLI) applications. Steve Francia, the same person behind the Hugo static site generator and Viper configuration management library, created it.
Cobra offers a straightforward way to create robust and well-structured CLI applications with support for subcommands, flags, and argument parsing. It also makes generating help documentation and auto-completion scripts for various shells easy.
Some key features of the Cobra library include:
- Command handling: Easily define commands, subcommands, and their individual actions. Cobra provides a hierarchical command structure that allows you to create complex CLI applications with a transparent organization.
- Flags and arguments: Cobra has built-in support for parsing flags (options), and arguments are passed to the commands. It handles both global and local flags, making it easy to manage different options for different commands.
- Configuration management: Cobra integrates seamlessly with the Viper library, allowing you to manage your application’s configuration through various sources like configuration files, environment variables, and command-line flags.
- Auto-generated help and usage messages: Cobra automatically generates help and usage messages for your CLI application based on the commands, flags, and descriptions you define.
- Auto-completion: Cobra supports generating shell auto-completion scripts for Bash, Zsh, Fish, and PowerShell, making it easier for users to interact with your CLI application.
Let’s start
Creating a simple hello world CLI
First, create a new Go module.
go mod init mycliThen, install the Cobra library:
go get -u github.com/spf13/cobra
Now, create a file named main.go And add the following code:
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
func main() {
rootCmd := &cobra.Command{
Use: "greet",
Short: "A CLI to demonstrate Cobra usage",
Long: `This CLI demonstrates how to create a CLI application with Cobra.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Greetings, %s!\n", args[1])
},
}
helloCmd := &cobra.Command{
Use: "hello",
Short: "Prints a 'Hello, world!' message",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello, world!")
},
}
rootCmd.AddCommand(helloCmd)
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}In this code, we create a rootCmd with the greet command and a helloCmd with the greet hello command. We add the helloCmd as a subcommand to the rootCmd using the AddCommand method. Finally, we execute the rootCmd and handle any errors.
To build and run the CLI application, use the following commands:
go build -o mycli ░▒▓ ✔ vidispine_cv
./mycli "Your Name"
./mycli helloThe first command will greet you with “Greetings, Your Name!”. The second command will print, “Hello, world!”.
Building a Simple File and Folder Creator CLI
This application has a single command, create which takes two arguments: the folder name and the file name.
First, ensure you have created a new Go module and installed the Cobra library, as shown in the previous example.
Create a file named main.go and add the following code:
package main
import (
"fmt"
"github.com/spf13/cobra"
"io/ioutil"
"os"
)
func main() {
rootCmd := &cobra.Command{
Use: "create",
Short: "A CLI to create a folder and a text file within it",
Long: `This CLI demonstrates how to create a CLI application with Cobra
that creates a folder and a text file within that folder.`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
folderName := args[0]
fileName := args[1]
err := os.MkdirAll(folderName, 0755)
if err != nil {
fmt.Printf("Error creating folder: %v\n", err)
os.Exit(1)
}
filePath := fmt.Sprintf("%s/%s", folderName, fileName)
err = ioutil.WriteFile(filePath, []byte("Hello, world!\n"), 0644)
if err != nil {
fmt.Printf("Error creating file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Created folder '%s' and file '%s' within it\n", folderName, fileName)
},
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}In this code, we create a rootCmd with the create command. The Run function takes two arguments: the folder name and the file name. It creates the folder using os.MkdirAll and writes a "Hello, world!" message to the specified file using ioutil.WriteFile.
To build and run the CLI application, use the following commands:
go build -o foldercreator
./foldercreator "myfolder" "myfile.txt"This command will create a folder named myfolder and a file named myfile.txt within it containing the message "Hello, world!".
Create a CLI that lists the most memory-consuming services on your machine
To create a CLI with Golang and Cobra that lists the most memory-consuming services, you can use the ps command on Unix-based systems (Linux and macOS) to get the process list and then parse the output to find the services with the highest memory usage.
Please note that this example will only work on Unix-based systems (Linux and macOS) and not Windows.
First, ensure you have created a new Go module and installed the Cobra library, as shown in the previous examples.
Create a file named main.go and add the following code:
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
"os/exec"
"sort"
"strconv"
"strings"
)
type Process struct {
PID int
Comm string
RSS int
}
func main() {
rootCmd := &cobra.Command{
Use: "memtop",
Short: "A CLI to list the most memory-consuming services",
Long: `This CLI demonstrates how to create a CLI application with Cobra
that lists the most memory-consuming services on Unix-based systems.`,
Run: func(cmd *cobra.Command, args []string) {
psCmd := exec.Command("ps", "-eo", "pid,comm,rss")
output, err := psCmd.Output()
if err != nil {
fmt.Printf("Error executing ps command: %v\n", err)
os.Exit(1)
}
processes := parsePsOutput(string(output))
sort.Slice(processes, func(i, j int) bool {
return processes[i].RSS > processes[j].RSS
})
fmt.Printf("%-10s %-30s %s\n", "PID", "Command", "RSS (KB)")
for _, p := range processes {
fmt.Printf("%-10d %-30s %d\n", p.PID, p.Comm, p.RSS)
}
},
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func parsePsOutput(output string) []Process {
lines := strings.Split(output, "\n")
processes := make([]Process, 0, len(lines)-1)
for i, line := range lines {
if i == 0 {
continue // Skip header line
}
fields := strings.Fields(line)
if len(fields) != 3 {
continue
}
pid, _ := strconv.Atoi(fields[0])
rss, _ := strconv.Atoi(fields[2])
processes = append(processes, Process{
PID: pid,
Comm: fields[1],
RSS: rss,
})
}
return processes
}In this code, we create a rootCmd with the memtop command. The Run function executes the ps command with specific flags to get the process list with PID, command, and RSS (Resident Set Size). The output is then parsed and sorted by memory usage in descending order.
To build and run the CLI application, use the following commands:
go build -o memtop ./memtop
This command will display the most memory-consuming services on your Unix-based system.
Remember that this example may require modification to work on Windows, as the ps command is unavailable by default. You could use a third-party package to get the process list or use platform-specific APIs to obtain the information.
Creating a Customizable String Repeater CLI with Flags
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
"strings"
)
func main() {
var count int
var separator string
rootCmd := &cobra.Command{
Use: "repeat",
Short: "A CLI to repeat a string",
Long: `This CLI demonstrates how to create a CLI application with Cobra
that takes a string and repeats it a specified number of times.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
input := args[0]
result := strings.Join(make([]string, count), input)
if count > 0 {
result = input + separator + result
}
fmt.Println(result)
},
}
rootCmd.Flags().IntVarP(&count, "count", "c", 1, "number of times to repeat the string")
rootCmd.Flags().StringVarP(&separator, "separator", "s", "", "separator between repeated strings")
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}In this code, we create a rootCmd with the repeat command. The Run function takes a string and repeats it based on the provided count flag. We also add a separator flag to specify a separator between the repeated strings.
Build and run the CLI application
go build -o repeatcli
./repeatcli --helpThis command will display the help text for the CLI!
./repeatcli "Hello, world!" --count 3 --separator ", "This command will repeat the input string “Hello, world!” three times with a comma and space as the separator, resulting in the following output:
Hello, world!, Hello, world!, Hello, world!Making your CLI available to others
For others to use your CLI, you must take two steps explained here.
- Building
- Distributing
Building CLI application
Building and registering a CLI application involves two main steps: building the binary for your target platforms and distributing your application so that users can easily install and use it. Here’s a step-by-step guide on how to achieve this:
- Building the binary:
You can cross-compile your Go program to build the binary for your target platforms (e.g., macOS, Windows, and Linux). We’ve built the binary for the current platform in the previous examples. To build for other platforms, set the GOOS and GOARCH environment variables before building.
For example, to build for macOS (amd64), Windows (amd64), and Linux (amd64), you can use the following commands:
# macOS (amd64)
GOOS=darwin GOARCH=amd64 go build -o mycli-mac
# Windows (amd64)
GOOS=windows GOARCH=amd64 go build -o mycli-windows.exe
# Linux (amd64)
GOOS=linux GOARCH=amd64 go build -o mycli-linuxThese commands will create three separate macOS, Windows, and Linux binaries.
Distributing the CLI application
To publish a package to Homebrew, you must create a Homebrew formula and submit it to a Homebrew tap. Homebrew taps are repositories containing formulae that users can install via the brew tap command. You can either contribute your formula to the main Homebrew repository or create your own tap.
Here’s a step-by-step guide on how to create and publish a package to Homebrew using your own tap:
- Create a new GitHub repository called
homebrew-tap: Replacetapwith a descriptive name for your tap, such ashomebrew-mytoolsfor a collection of your tools. - Create a new file in the repository: Name the file with the format
your-tool.rb, whereyour-toolis the name of your CLI application. This file is a formula that describes how to install your application using Homebrew. - Add the formula content: Write the formula for your CLI application. Here’s an example template:
class YourTool < Formula
desc "A short description of your CLI application"
homepage "https://github.com/YOUR_USERNAME/your-tool"
url "https://github.com/YOUR_USERNAME/your-tool/releases/download/v1.0.0/your-tool-mac.tar.gz"
sha256 "YOUR_MAC_BINARY_SHA256"
version "1.0.0"
license "MIT"
def install
bin.install "your-tool"
end
test do
system "#{bin}/your-tool", "--version"
end
endReplace YourTool, YOUR_USERNAME, and other placeholders with the appropriate values for your CLI application. Update the url to point to the macOS binary in your release, and replace YOUR_MAC_BINARY_SHA256 it with the SHA256 hash of the macOS binary. You can calculate the hash using the shasum command:
shasum -a 256 your-tool-mac.tar.gz4. Commit and push the changes: Save the formula file and push it to the GitHub repository.
5. Inform users how to install your CLI application: Users can now install your CLI application using Homebrew by running:
brew tap YOUR_USERNAME/tap brew install your-tool
Replace YOUR_USERNAME and your-tool with your GitHub username and your CLI application's name, respectively.
Following these steps, you’ll create a Homebrew tap and publish your package to it, making it easy for users to install and update your CLI application on macOS.
If your package is of general interest to the Homebrew community, you can submit it to the main Homebrew repository by creating a pull request. However, creating and maintaining your tap is recommended for personal projects or less widely-used tools.
