Building a CLI Application

Hey! If you love Go and building Go apps as much as I do, let's connect on Twitter or LinkedIn. I talk about this stuff all the time!

Want to learn how to build better Go applications faster and easier? You can.

Check out my course on the Go Standard Library. You can check it out now for free.


Build Powerful and Efficient CLI Tools in Go

A CLI application is a type of program that runs in your terminal and interacts with the user through text commands. Think of it like interacting with your computer using code instead of clicking on menus. You’ll see this type of interaction in many tools, such as ls for listing files or grep for searching within them.

Why it Matters:

CLI applications are powerful tools for a variety of tasks:

  • Automation: CLIs are great for automating repetitive actions on your computer.

  • Efficiency: CLIs can be faster and more flexible than graphical interfaces, especially for tasks that involve text processing.

  • Integration: They easily integrate with other command-line tools and scripts.

Building a Task Manager with Go

Let’s imagine we want to build a simple CLI task manager in Go. Here’s a step-by-step guide:

  1. Define the Functionality:

Our CLI tool will allow users to:

  • add: Add a new task to a list
  • list: Display all tasks in the list
  • complete: Mark a task as completed.
  1. Choose Tools and Data Structures:

We’ll use Go’s built-in fmt package for printing output and input/output. To store the tasks, we can use a simple slice of strings:

// Tasks is a slice to hold all our tasks
var Tasks []string 
  1. Create the CLI:

This code snippet demonstrates a basic example of adding functionality to “add” tasks to a program.

package main

import (
  "fmt"
)

func main() {
  // Add some initial commands
  tasks := []string{"task1", "task2"}

  // Define the function to add a task
  func addTask(task string) {
    Tasks = append(tasks, task) 
  }

Why use these functions?

Using simple, clear text-based commands in Go makes the tool easy to understand and interact with.

  • Tasks: A slice is a good choice for storing strings because it’s a dynamic data structure. This means we can easily add new tasks to our list without having to worry about exceeding a fixed size.
  • Efficiency: CLIs are efficient for tasks like:
    • Managing Lists: Simple operations like appending and deleting items are fast and require minimal code.
  1. Use flag package for parsing arguments:

Go’s flag package simplifies handling CLI arguments. It allows users to easily understand how to interact with the program, making it easier to use.

import "flag"

// ... (previous code snippet)

// Example of a simple task list:

// Initialize a slice to store tasks
var Tasks []string 

func addTask(task string) {
  *Tasks = append(*Tasks, task)
}

// Define the "task" string slice as a variable to be modified

// ... (rest of your code)

// Example usage:
// Run addTask("buy groceries")

Best Practices:

  • Clear and Concise Names: Choose clear names for your commands and arguments.

  • Handle Input/Output: Use the fmt package to print out information about the tasks (e.g., “Added task: " + task).

  • Error Handling: Always include error handling!

    • What if the user enters a non-string argument?
    • What if there’s an error adding the task to the list?

The addTask function in the code snippet above is just an example.

You can use this as a starting point and build upon it by:

  • Adding a task:
// Add a task to the Tasks slice
func addTask(task string) {
  // Assuming Tasks is a global variable (you'll need to make sure it's initialized outside of functions).

  // Append the task to the Tasks list
  fmt.Println("Added task: " + task)

  *Tasks = append(*Tasks, task) 
}
  // This code will add tasks to the `tasks` slice.

func main() {
    // Define a global variable to store our task list
    var Tasks []string
    
    // Initialize the task list with command-line flags
    taskName := flag.String("add", "", "Task name to add (use -add= to specify)")
    
    // Add tasks to the `Tasks` slice and parse
    flag.Parse()

    // Create a new slice for the completed tasks
    completedTasks := []string{}
    completedTasks = append(*taskName)

Why are these best practices important?

  • They make your code more robust: Using clear names and handling errors clearly makes it easier to understand, debug, and maintain.

  • Clear and concise error messages:
    Let’s say you have a function markAsComplete(task string) that takes the task name as input.

func main() {
  // ... (previous code)

  // Add logic to handle "addTask" command
  var tasks []string

  if err != nil {
    // Handle error during parsing
    fmt.Println("Error: ", err)
    os.Exit(1) 
  }

  // Define a function to mark tasks as complete

  func markAsComplete(taskName string) {
    for i, task in range tasks {
      if task == taskName {
        // This is where the "completed" task logic goes
        fmt.Println("Task", task, "marked as complete.")

        // Mark task as completed and add it to a new slice
        *tasks = append(tasks[:i], "completed") 

    }
  }

  func main() {
    // ... (previous code)

    if completedTasks[0] == "" {
      fmt.Println("Completed task:", taskName)
    } else {
      fmt.Println("Task", taskName, "is already in the list.")
    }
  } 
  • Error Handling:
// Example: Implement a function to mark tasks as complete
func addTask(taskName string) {
  if len(task) == 0 {
      fmt.Println("Please enter a task name.")
      return
  }

  for i, t := range *tasks { // Add the task to the list
      t = "task"
      *tasks = append(*Tasks, t)
  }
  // Remove the completed task and mark it in the global slice
  fmt.Println("Added task:", taskName)

Why use a function for completedTasks:
The addTask function is designed to handle the logic of adding tasks and providing feedback.

  • Simplicity: By using the flag package, we can easily define and access variables from the global scope.

This example demonstrates how you can create a simple command-line interface.

Let’s say I want to implement “complete” functionality in a program:

func complete(taskName string) {
  // ... (previous code)

  for i := range *Tasks {
      if taskName == "" {
          *Tasks[i] = "*"+string(string(*


Stay up to date on the latest in Coding for AI and Data Science

Intuit Mailchimp