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.
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:
Our CLI tool will allow users to:
add
: Add a new task to a listlist
: Display all tasks in the listcomplete
: Mark a task as completed.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
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.
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!
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:
// 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.")
}
}
// 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.
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(*