Enhance Todo-cli With Interactive Menu Using Go Stdlib
Let's dive into how we can enhance the todo-cli
application by implementing an interactive menu, also known as a REPL (Read-Evaluate-Print Loop), using nothing but the Go standard library. This approach ensures that our tool remains lightweight and dependency-free, aligning with the principles of simplicity and efficiency that Go promotes. Our primary goal is to create a user-friendly interface that guides users through the available commands, making the todo-cli
more accessible and intuitive, especially for those who may not be entirely comfortable with command-line interfaces.
Why an Interactive Menu?
Guys, you might be wondering, why bother with an interactive menu when command-line arguments already work? Well, hereâs the scoop. While command-line arguments are great for quick, specific actions, they require users to remember the exact syntax and available options. An interactive menu, on the other hand, provides a guided experience. It presents users with a list of available commands, prompts them for necessary input, and executes the chosen action. This approach is particularly beneficial for:
- New Users: Those who are new to the
todo-cli
can easily explore its capabilities without needing to memorize commands or consult documentation. - Occasional Users: Users who don't use the
todo-cli
frequently might forget the exact syntax. The menu serves as a helpful reminder. - Complex Commands: For commands that require multiple arguments or options, the menu can guide users through each step, reducing the likelihood of errors.
In essence, an interactive menu makes the todo-cli
more approachable and user-friendly, expanding its appeal to a broader audience.
Implementation Strategy
Our strategy involves several key steps:
- Subcommand: We'll introduce a
menu
subcommand that, when invoked, launches the interactive menu. - Environment Support: The menu should automatically open if no arguments are provided and a specific environment variable (e.g.,
TODO_CLI_INTERACTIVE=true
) is set. This ensures backward compatibility, meaning existing scripts and workflows that rely on command-line arguments will continue to work as before. - Menu Display: The menu will display a list of available commands with clear descriptions.
- User Input: We'll use the
fmt
package to read user input from the command line. - Command Execution: Based on the user's selection, we'll call the appropriate existing command within the
todo-cli
application. - Error Handling: We'll handle invalid selections gracefully, providing informative error messages and prompting the user to try again.
- Exit: The menu will include an option to exit the interactive session.
- README Update: Finally, we'll update the README file to briefly mention the new interactive menu feature.
Step-by-Step Implementation
1. Setting Up the Basic Menu Structure
First, let's create the basic structure for our interactive menu. This involves defining the menu options and a loop to handle user input. This is the basic structure, and more functionalities will be added in the next steps.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Println("\nTodo CLI Menu:")
fmt.Println("1. Add Task")
fmt.Println("2. List Tasks")
fmt.Println("3. Complete Task")
fmt.Println("4. Exit")
fmt.Print("Enter your choice: ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
switch input {
case "1":
fmt.Println("Adding task...")
case "2":
fmt.Println("Listing tasks...")
case "3":
fmt.Println("Completing task...")
case "4":
fmt.Println("Exiting...")
return
default:
fmt.Println("Invalid choice. Please try again.")
}
}
}
2. Integrating with Existing Commands
Now, let's integrate the menu with the existing todo-cli
commands. This involves calling the appropriate functions based on the user's menu selection. For the sake of demonstration, we'll assume that you have functions named addTask
, listTasks
, and completeTask
already defined in your todo-cli
application.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Println("\nTodo CLI Menu:")
fmt.Println("1. Add Task")
fmt.Println("2. List Tasks")
fmt.Println("3. Complete Task")
fmt.Println("4. Exit")
fmt.Print("Enter your choice: ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
switch input {
case "1":
fmt.Println("Adding task...")
// Call the addTask function here
addTask(reader)
case "2":
fmt.Println("Listing tasks...")
// Call the listTasks function here
listTasks()
case "3":
fmt.Println("Completing task...")
// Call the completeTask function here
completeTask(reader)
case "4":
fmt.Println("Exiting...")
return
default:
fmt.Println("Invalid choice. Please try again.")
}
}
}
func addTask(reader *bufio.Reader) {
fmt.Print("Enter task description: ")
description, _ := reader.ReadString('\n')
description = strings.TrimSpace(description)
fmt.Printf("Task added: %s\n", description)
// Here, you would typically add the task to your task list
}
func listTasks() {
fmt.Println("Listing all tasks...")
// Here, you would typically retrieve and display the list of tasks
}
func completeTask(reader *bufio.Reader) {
fmt.Print("Enter task number to complete: ")
taskNumber, _ := reader.ReadString('\n')
taskNumber = strings.TrimSpace(taskNumber)
fmt.Printf("Task %s completed!\n", taskNumber)
// Here, you would typically mark the specified task as complete
}
3. Adding the menu
Subcommand and Environment Support
To make the interactive menu accessible, we'll add a menu
subcommand and support for an environment variable. This involves modifying the main function to check for the menu
subcommand or the presence of the TODO_CLI_INTERACTIVE
environment variable.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
args := os.Args[1:]
// Check for the menu subcommand or the environment variable
if len(args) == 0 && os.Getenv("TODO_CLI_INTERACTIVE") == "true" || len(args) > 0 && args[0] == "menu" {
startMenu()
return
}
// Handle other command-line arguments here (existing functionality)
fmt.Println("Handling command-line arguments...")
// Add your existing command-line argument parsing logic here
}
func startMenu() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Println("\nTodo CLI Menu:")
fmt.Println("1. Add Task")
fmt.Println("2. List Tasks")
fmt.Println("3. Complete Task")
fmt.Println("4. Exit")
fmt.Print("Enter your choice: ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
switch input {
case "1":
fmt.Println("Adding task...")
addTask(reader)
case "2":
fmt.Println("Listing tasks...")
listTasks()
case "3":
fmt.Println("Completing task...")
completeTask(reader)
case "4":
fmt.Println("Exiting...")
return
default:
fmt.Println("Invalid choice. Please try again.")
}
}
}
func addTask(reader *bufio.Reader) {
fmt.Print("Enter task description: ")
description, _ := reader.ReadString('\n')
description = strings.TrimSpace(description)
fmt.Printf("Task added: %s\n", description)
// Here, you would typically add the task to your task list
}
func listTasks() {
fmt.Println("Listing all tasks...")
// Here, you would typically retrieve and display the list of tasks
}
func completeTask(reader *bufio.Reader) {
fmt.Print("Enter task number to complete: ")
TaskNumber, _ := reader.ReadString('\n')
TaskNumber = strings.TrimSpace(TaskNumber)
fmt.Printf("Task %s completed!\n", TaskNumber)
// Here, you would typically mark the specified task as complete
}
4. Error Handling and Input Validation
To make the menu more robust, we'll add error handling and input validation. This involves checking for invalid input and providing informative error messages.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
args := os.Args[1:]
// Check for the menu subcommand or the environment variable
if len(args) == 0 && os.Getenv("TODO_CLI_INTERACTIVE") == "true" || len(args) > 0 && args[0] == "menu" {
startMenu()
return
}
// Handle other command-line arguments here (existing functionality)
fmt.Println("Handling command-line arguments...")
// Add your existing command-line argument parsing logic here
}
func startMenu() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Println("\nTodo CLI Menu:")
fmt.Println("1. Add Task")
fmt.Println("2. List Tasks")
fmt.Println("3. Complete Task")
fmt.Println("4. Exit")
fmt.Print("Enter your choice: ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
switch input {
case "1":
fmt.Println("Adding task...")
addTask(reader)
case "2":
fmt.Println("Listing tasks...")
listTasks()
case "3":
fmt.Println("Completing task...")
completeTask(reader)
case "4":
fmt.Println("Exiting...")
return
default:
fmt.Println("Invalid choice. Please try again.")
}
}
}
func addTask(reader *bufio.Reader) {
fmt.Print("Enter task description: ")
description, _ := reader.ReadString('\n')
description = strings.TrimSpace(description)
fmt.Printf("Task added: %s\n", description)
// Here, you would typically add the task to your task list
}
func listTasks() {
fmt.Println("Listing all tasks...")
// Here, you would typically retrieve and display the list of tasks
}
func completeTask(reader *bufio.Reader) {
fmt.Print("Enter task number to complete: ")
TaskNumber, _ := reader.ReadString('\n')
TaskNumber = strings.TrimSpace(TaskNumber)
// Validate if the input is a number
_, err := strconv.Atoi(TaskNumber)
if err != nil {
fmt.Println("Invalid task number. Please enter a valid number.")
return
}
fmt.Printf("Task %s completed!\n", TaskNumber)
// Here, you would typically mark the specified task as complete
}
5. Updating the README
Finally, remember to update the README file to inform users about the new interactive menu feature. Add a section explaining how to launch the menu using the menu
subcommand or the TODO_CLI_INTERACTIVE
environment variable.
Conclusion
By following these steps, we've successfully implemented an interactive menu for the todo-cli
application using only the Go standard library. This enhancement makes the tool more user-friendly and accessible, while maintaining its lightweight and dependency-free nature. Keep coding, guys! This approach not only improves the user experience but also demonstrates the power and flexibility of the Go standard library. Remember to always prioritize user experience and strive to make your tools as intuitive as possible. That's all! Cheers!