Intro πͺ
Go is an open source programming language supported by Google. It has gained it’s grounds in the programming world due to it’s simplicity and speed. It is a statically typed language with a syntax similar to C. It is a great language for building command line interfaces (CLI), Cloud/DevOp tools, Networking and Backend server applications. In this article, I will be giving an overview of how I built the gomeasure
tool with Go.
The Journey π²
I’ve always wanted to build a CLI tool but ideas have not been forthcoming until one fateful day at work meeting. There was a need to calculate the weight (files count) of directories in a project.
Hence, I wrote a simple Go script which evolved into the gomeasure
CLI tool.
Why I built with Go? π€
I chose Go because of the following reasons:
I was learning go at the time
I’ve developed the habit of learning at least one new programming language every year. I started learning Go in 2022 and I wanted to build something with it.
Go has a rich standard library and inbuilt tools for building fast elegant CLIs.
The flag
, os
and fmt
packages are enough for building a simple CLI tool. This provides flag parsing out of the box compared to other languages like Java where I would have parsed arguments manually. Anyways, I extended these packages with the github.com/spf13/cobra
package which made building even easier.
Collaboration and community.
I learnt and built the tool with a friend, Ahmed Helali. We both have a passion for open source and we wanted to build something together.
Small binaries
Go binaries are very small in size. The typical size of a Go binary is 2-5 MB. This is a great advantage because the binary has a small memory footprint (we don’t want a memory hog), there is no dependency overhead and binaries can be easily shared.
Cross-platform support (Windows, Linux, Mac)
Go is a compiled language and it compiles to a single binary file which can be run on any platform. This is unlike interpreted languages like Python and JavaScript which require a runtime environment to run. This makes Go a good choice for building CLIs. I built the tool for various platforms in a CI/CD environment and it worked on all platforms without issues.
Structure of a CLI program π
As developers, we use CLI programs every day ranging from git, docker, kubectl, etc. A typical CLI program has the following structure:
<program> [command] [arguments] [flags]
e.g.
gomeasure line /path/to/file -v
I employed the following model for the gomeasure
CLI tool:
- In the docs,
<>
means a required argument and[]
means an optional argument. [command]
is a sub-command of the program. e.g.line
ingomeasure line
is a sub-command ofgomeasure
.- sub-commands have required arguments and optional flags. e.g.
gomeasure line /path/to/file -v
has a required argument/path/to/file
and an optional flag-v
.
Here, it is obvious that the arguments are necessary for the subcommand to carry out the expected action while the flags tells it how to. gomeasure
has ahelp
sub-command which displays the help message for the program. e.g.gomeasure help
displays the help message for the program. The help message is also accessible from the--help
flag. e.g.gomeasure line --help
displays the help message for theline
sub-command.- Flags or options that have a single dash
-
are short flags. e.g.-v
is a short flag and is equivalent to--verbose
. - Short flags can be combined. e.g.
-v -s
is equivalent to-vs
. - Flags or options that have a double dash
--
are long flags. e.g.--verbose
is a long flag.
Introduction to Cobra π
Cobra is a package built by Steve Francia for building powerful modern CLI applications in Go. It provides a simple interface to create powerful modern CLI interfaces and it is used in production grade softwares like Kubernetes, Hugo and Github CLI, to name a few. Cobra provides a generator to create a CLI skeleton which can be customized to suit your needs. I used the generator to create the gomeasure
CLI tool.
Cobra out of the box use the structure described above.
Creating gomeasure with Cobra and Go π
Project structure
The project structure is as follows:
.
βββ LICENSE
βββ README.md
βββ bin
β βββ gomeasure
βββ .goreleaser.yaml
βββ cmd
β βββ file.go
β βββ line.go
β βββ root.go
βββ dist/
βββ go.mod
βββ go.sum
βββ main.go
βββ pkg
β βββ runner.go
β βββ runner_test.go
LICENSE
is the license file.README.md
is the readme file.bin
is the directory where the compiled binary is stored.cmd
is the directory where the CLI commands are stored.dist
is the directory where the compiled binaries for various platforms are stored.go.mod
is the go module file.go.sum
is the go module checksum file.main.go
is the entry point of the program.pkg
is the directory that contains the business logic of the software..goreleaser.yaml
is the goreleaser configuration file.
Cobra and the cmd/ directory
The scope of cobra is limited to the cmd/
directory because this is where each sub-command is defined and flags are parsed.
- The most important class in the
cobra
package is thecobra.Command
class. It is used to define a sub-command and its flags. An example of how we used it to define thefile
sub-command is shown below:
var fileCmd = &cobra.Command{
Use: "file <directory>",
Short: "processes the number of files in a directory",
Long: `gomeasure file processes and returns the number of files in a directory / project.`,
Run: func(cmd *cobra.Command, args []string) {
// ...
},
}
- Cobra also helps to parse flags and it automatically resolves conflicts between flags. An example of how we used it to parse the
--verbose
flag is shown below:
// cmd/root.go
rootCmd.PersistentFlags().BoolVarP(&isVerbose, "verbose", "v", false, "--desc--")
You can check out the cobra documentation at https://github.com/spf13/cobra.
The runner.go file
The runner.go
file contains the business logic of the program. It contains the Runner
struct which contains fields that helps to specify what the program has to do.
This file also contains the Result
struct which is returned from the Run
method of the Runner
struct. The Result
struct contains the result of the program execution.
Deployment and CI/CD with Github Actions
I used Github Actions to build and deploy the program. The workflow is as follows:
- The workflow is triggered when a new release is created.
- The workflow builds the program for various platforms using goreleaser. The `.goreleaser.yaml file contains instructions on how and where to deploy releases.
- The workflow then creates a new release and uploads the compiled binaries to the github releases section and on brew (for macOS).
Roadmap for gomeasure π£
More subcommands π€
I plan to improve the tool by adding more sub-commands and more flags to the existing sub-commands to make it more useful.
Unit Testing omg π₯΅
I plan to add unit tests to the *.go files. It is actually a highly recommended practice to write unit tests in the Go community.
Docker image π³
I plan to create a docker image release for the tool so that it can be used in a docker container.
Conclusion
I’ve made the project open source and I would love to see contributions from the community.
Don’t forget to starπ the repository on Github if you found gomeasure useful.