Install External Library used
go get github.com/aquasecurity/table
Directory structure
main.go
package main
func main() {
todos := Todos{}
storage := NewStorage[Todos]("todos.json")
storage.Load(&todos)
CmdFlags := NewCmdflags()
CmdFlags.Execute(&todos)
storage.Save(todos)
}
- Creating a slice of of structure name Todos
- Create a dynamic memory for the existing todos in the system locally in "todo.json" file that was earlier created with storage.Save
- If not it will load as empty
- Parse and validate the command flags provided by the user
- Execute based on the flags provided
Functionality implementation
package main
import (
"errors"
"fmt"
"os"
"strconv"
"time"
"github.com/aquasecurity/table"
)
type Todo struct {
Title string
Completed bool
CreatedAt time.Time
// this field here is a pointer reference because it can be null
CompletedAt *time.Time
}
// a slice(array) of Todo
type Todos []Todo
// Passing Todo slice here as a reference
// declares a parameter named todos that is a pointer to a Todos slice.
// the function receives a copy of the slice under the name todos
func (todos *Todos) add(title string) {
todo := Todo{
Title: title,
Completed: false,
CompletedAt: nil,
CreatedAt: time.Now(),
}
*todos = append(*todos, todo)
}
func (todos *Todos) validateIndex(index int) error {
if index < 0 || index >= len(*todos) {
err := errors.New("invalid index")
fmt.Println(err)
}
return nil
}
func (todos *Todos) delete(index int) error {
t := *todos
if err := t.validateIndex(index); err != nil {
return err
}
*todos = append(t[:index], t[index+1:]...)
return nil
}
func (todos *Todos) toggle(index int) error {
t := *todos
if err := t.validateIndex(index); err != nil {
return err
}
isCompleted := t[index].Completed
if !isCompleted {
completionTime := time.Now()
t[index].CompletedAt = &completionTime
}
t[index].Completed = !isCompleted
return nil
}
func (todos *Todos) edit(index int, title string) error {
t := *todos
if err := t.validateIndex(index); err != nil {
return err
}
t[index].Title = title
return nil
}
func (todos *Todos) print() {
table := table.New(os.Stdout)
table.SetRowLines(false)
table.SetHeaders("#", "Title", "Status", "Created", "Completed")
for index, t := range *todos {
mark := "❌"
completedAt := ""
if t.Completed {
mark = "✅"
if t.CompletedAt != nil {
completedAt = t.CompletedAt.Format(time.RFC1123)
}
}
table.AddRow(strconv.Itoa(index), t.Title, mark, t.CreatedAt.Format(time.RFC1123), completedAt)
}
table.Render()
}
Storage implementation
package main
import (
"encoding/json"
"os"
)
type Storage[T any] struct {
FileName string
}
func NewStorage[T any](filename string) *Storage[T] {
return &Storage[T]{FileName: filename}
}
func (s *Storage[T]) Save(data T) error {
fileData, err := json.MarshalIndent(data, "", "\t")
if err != nil {
return err
}
return os.WriteFile(s.FileName, fileData, 0644)
}
func (s *Storage[T]) Load(data *T) error {
fileData, err := os.ReadFile(s.FileName)
if err != nil {
return err
}
return json.Unmarshal(fileData, data)
}
Command Line Flags Validation and Execution
package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
)
type CmdFlags struct {
Help bool
Add string
Del int
Edit string
Update int
List bool
}
func NewCmdflags() *CmdFlags {
cf := CmdFlags{}
flag.BoolVar(&cf.Help, "help", false, "List existing commands")
flag.StringVar(&cf.Add, "add", "", "Add a new todo specify title")
flag.StringVar(&cf.Edit, "edit", "", "Edit an existing todo, enter #index and specify a new title. \"id:new title\"")
flag.IntVar(&cf.Del, "del", -1, "Specify a todo by #index to delete")
flag.IntVar(&cf.Update, "update", -1, "Specify a todo #index to update")
flag.BoolVar(&cf.List, "list", false, "List all todos")
for _, arg := range os.Args[1:] {
if strings.HasPrefix(arg, "-") && !isValidFlag(arg) {
fmt.Printf("Unknown flag: %s\n", arg)
fmt.Println("try --help to know more")
os.Exit(0)
}
}
flag.Parse()
return &cf
}
func isValidFlag(flag string) bool {
validFlags := []string{
"-help", "--help",
"-add", "--add",
"-edit", "--edit",
"-del", "--del",
"-update", "--update",
"-list", "--list",
}
if idx := strings.Index(flag, "="); idx != -1 {
flag = flag[:idx]
}
for _, validFlag := range validFlags {
if flag == validFlag {
return true
}
}
return false
}
func (cf *CmdFlags) Execute(todos *Todos) {
switch {
case cf.List:
todos.print()
case cf.Add != "":
todos.add(cf.Add)
case cf.Edit != "":
parts := strings.SplitN(cf.Edit, ":", 2)
if len(parts) != 2 {
fmt.Printf("Error, invalid format for edit.\nCorrect Format: \"id:new title\" ")
os.Exit(1)
}
index, err := strconv.Atoi(parts[0])
if err != nil {
fmt.Printf("Error, Invalid index for edit")
os.Exit(1)
}
todos.edit(index, parts[1])
case cf.Update != -1:
todos.toggle(cf.Update)
case cf.Del != -1:
todos.delete(cf.Del)
case cf.Help:
fmt.Println("usage:")
fmt.Println("--help\t\t| List existing commands")
fmt.Println("--add\t\t| Add new task")
fmt.Println("--del\t\t| Delete an existing task")
fmt.Println("--update\t| Check/Uncheck existing task")
fmt.Println("--edit\t\t| Edit an existing task")
}
}
Github Repo: CLI Todo App
Top comments (0)