Enhance Todo-cli With Interactive Menu Using Go Stdlib

by ADMIN 55 views

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:

  1. New Users: Those who are new to the todo-cli can easily explore its capabilities without needing to memorize commands or consult documentation.
  2. Occasional Users: Users who don't use the todo-cli frequently might forget the exact syntax. The menu serves as a helpful reminder.
  3. 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:

  1. Subcommand: We'll introduce a menu subcommand that, when invoked, launches the interactive menu.
  2. 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.
  3. Menu Display: The menu will display a list of available commands with clear descriptions.
  4. User Input: We'll use the fmt package to read user input from the command line.
  5. Command Execution: Based on the user's selection, we'll call the appropriate existing command within the todo-cli application.
  6. Error Handling: We'll handle invalid selections gracefully, providing informative error messages and prompting the user to try again.
  7. Exit: The menu will include an option to exit the interactive session.
  8. 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!