Every now and then, I like to start playing with a new programming language. The one I picked this was go. To start simple, I decided to build a simple command line utility that would do some simple tasks, which I usually do with bash scripts. Reading about how to implement this, I discovered a very cool project called cobra, that would make the task a lot easier. And apparently is used by a lot of big projects.
If my bash scripts are working, why rewrite them? Because with go it is easy to generate an executable. Which means I can distribute them easily in my team, without worrying about their setup.
The development environment
Since I don't want to install go in my machine, and I loved working with VS Code remote containers, I will create one with go. If you don't know what I am talking about, check it out.
I will create a folder called uc (this will be the root of my project), and initialize the container inside it.
Once my container is ready, I need to install cobra.
go get -u github.com/spf13/cobra/cobra
Cobra has a nice utility to help you get started, called cobra generator.
To create the initial structure of the project:
cobra init --pkg-name github.com/kgoedert/uc .
If you want to create your project somewhere else, other than the current directory, just pass it as a parameter.
cobra init --pkg-name github.com/kgoedert/uc /path/to/some/folder
Which will give me an output like this:
Your Cobra applicaton is ready at
/workspaces/uc
You should get an output close to this one:
uc
├── LICENSE
└── src
└── github.com
└── kgoedert
└── uc
├── cmd
│ └── root.go
└── main.go
You already have something you can build and run.
Since I am working inside the container, I can build and install my package with:
Ctrl + Shift + P > Go: Install Current Package
Just remember to execute this with the main.go file open and selected.
A binary will be generated on a bin directory. You can open a terminal and run it with
./uc
You should see an output like this:
root@10b479f71c07:/workspaces/uc/bin# ./uc
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
subcommand is required
Important: Since I am using visual code inside a container, my GOPATH is different, if I am using the terminal than if I am using the plugin commands from the go extension menu. More informations here https://github.com/Microsoft/vscode-go/wiki/GOPATH-in-the-VS-Code-Go-extension
Adding commands
I will add a command called createFolder, that will simply create an empty folder.
To do that, open a terminal go to the directory that has the main.go file in it, in my case it is uc, and type:
cobra add createFolder
This will allow me to have the command uc createFolder in my project. I will also add a parameter to specify the name of the folder I want to create.
Cobra add a file called createFolder.go in my cmd folder. At this point, my project structure, looks like this:
uc
├── bin
│ └── uc
├── LICENSE
└── src
└── github.com
└── kgoedert
└── uc
├── cmd
│ ├── createFolder.go
│ └── root.go
└── main.go
Command implementation
In the createFolder.go, cobra created some templates to get you started. There is a variable to represent your command. What it will do, its name, and some help to your users.
var createFolderCmd = &cobra.Command{
Use: "createFolder",
Short: "Creates a folder",
Long: `Creates a folder with a name as parameter`,
Run: func(cmd *cobra.Command, args []string) {
createFolder(cmd)
},
}
func createFolder(cmd *cobra.Command) error {
name, _ := cmd.Flags().GetString("name")
if name == "" {
return errors.New("Your folder needs a name")
}
err := os.MkdirAll(name, os.ModePerm)
if err != nil {
fmt.Printf("Could not create the directory %v", err)
}
fmt.Println("Folder " + name + " created.")
return nil
}
You can see my implementation is very simple. In the function called init is where you are going to determine to which other command your command is a subcommand to, and add parameters to it.
func init() {
rootCmd.AddCommand(createFolderCmd)
createFolderCmd.Flags().StringP("name", "", false, "Name of the folder you want to create.")
}
My createFolder command will be a subcommand to the root command, and will have one parameter, which will be n, for the folder name. So when called it will look like
./uc createFolder -n newFolder
Now, let's add another simple command. Initializing a git repository inside a given folder.
My code on the gitInit.go file, looks like this:
var gitInitCmd = &cobra.Command{
Use: "gitInit",
Short: "Initializes a git repository on a given path",
Run: func(cmd *cobra.Command, args []string) {
gitInit(cmd)
},
}
func gitInit(cmd *cobra.Command) error {
folder, _ := cmd.Flags().GetString("folder")
if folder == "" {
return errors.New("Your need to inform a path to initialize the git repository")
}
command := exec.Command("git", "init", folder)
err := command.Run()
if err != nil {
fmt.Printf("Could not create the directory %v", err)
}
fmt.Println("Git repository initialized in " + folder)
return nil
}
func init() {
rootCmd.AddCommand(gitInitCmd)
gitInitCmd.Flags().StringP("folder", "f", "", "Path where the git repository will be initialized.")
}
My command will again, be a subcommand of the root command. And will take a -f as a parameter for the name of the folder.
Now, suppose you want to make a command that will aggregate some of the commands you have previously created. One possible solution would be to create a command called for example, all, that would execute the other two. Like this:
var allCmd = &cobra.Command{
Use: "all",
Short: "Executes both commands",
RunE: func(cmd *cobra.Command, args []string) error {
createFolder(cmd)
gitInit(cmd)
return nil
},
}
func init() {
rootCmd.AddCommand(allCmd)
allCmd.Flags().StringP("folder", "f", "", "Path where the git repository will be initialized.")
allCmd.Flags().StringP("name", "n", "", "Name of the folder you want to create.")
}
To use your all command, you would:
./uc all -n newProject -f newProject
This would create a folder and initialize a git repository on it.
Although the commands I used were very simple, I hope I was able to show how you can compose your commands to create something very powerful.
You can find the source code here
Top comments (1)
Thanks a lot, it's a really nice article, but the github repo doesn't have source code 🤔