From fcf0cb3653c77072768952091f13de4a30b46613 Mon Sep 17 00:00:00 2001 From: amantahiliani Date: Fri, 23 May 2025 00:00:37 -0400 Subject: [PATCH] Update --- .gitignore | 6 +- cmd/todo/main.go | 227 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 10 ++- go.sum | 10 +++ 4 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 cmd/todo/main.go diff --git a/.gitignore b/.gitignore index b946cb8..fd6474f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,5 @@ # Binaries -todo -todo.exe -todo-mac -todo-linux -main + # OS files .DS_Store diff --git a/cmd/todo/main.go b/cmd/todo/main.go new file mode 100644 index 0000000..f301855 --- /dev/null +++ b/cmd/todo/main.go @@ -0,0 +1,227 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "os" + "path/filepath" + + _ "github.com/mattn/go-sqlite3" + "github.com/spf13/cobra" +) + +type Todo struct { + Id int `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Completed bool `json:"completed"` +} + +var ( + dbPath string + rootCmd = &cobra.Command{ + Use: "todo", + Short: "A simple CLI todo application", + Long: `Todo is a CLI application for managing your tasks. + It provides simple commands to add, list, complete, and remove todos. + All data is stored locally in a SQLite database.`, + } + + addCmd = &cobra.Command{ + Use: "add", + Short: "Add a new todo", + Run: handleAdd, + } + + listCmd = &cobra.Command{ + Use: "list", + Short: "List all todos", + Run: handleList, + } + + completeCmd = &cobra.Command{ + Use: "complete", + Short: "Mark a todo as completed", + Run: handleComplete, + } + + removeCmd = &cobra.Command{ + Use: "remove", + Short: "Remove a todo", + Run: handleRemove, + } + + title string + description string + id int +) + +func init() { + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + + // Create .todo directory if it doesn't exist + todoDir := filepath.Join(homeDir, ".todo") + if err := os.MkdirAll(todoDir, 0755); err != nil { + log.Fatal(err) + } + + // Set database path + dbPath = filepath.Join(todoDir, "todos.db") + + // Add commands + rootCmd.AddCommand(addCmd, listCmd, completeCmd, removeCmd) + + // Add flags + addCmd.Flags().StringVarP(&title, "title", "t", "", "Title of the todo") + addCmd.Flags().StringVarP(&description, "description", "d", "", "Description of the todo") + addCmd.MarkFlagRequired("title") + addCmd.MarkFlagRequired("description") + + completeCmd.Flags().IntVarP(&id, "id", "i", 0, "ID of the todo to complete") + completeCmd.MarkFlagRequired("id") + + removeCmd.Flags().IntVarP(&id, "id", "i", 0, "ID of the todo to remove") + removeCmd.MarkFlagRequired("id") +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func getDB() *sql.DB { + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + log.Fatal(err) + } + + // Create table if it doesn't exist + sqlStmt := `CREATE TABLE IF NOT EXISTS todos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + completed BOOLEAN DEFAULT 0 + );` + + _, err = db.Exec(sqlStmt) + if err != nil { + log.Fatal(err) + } + + return db +} + +func handleAdd(_ *cobra.Command, _ []string) { + db := getDB() + defer db.Close() + + _, err := db.Exec("INSERT INTO todos (title, description) VALUES (?, ?)", title, description) + if err != nil { + log.Fatal(err) + } + fmt.Println("Todo added successfully!") +} + +func handleList(_ *cobra.Command, _ []string) { + db := getDB() + defer db.Close() + + rows, err := db.Query("SELECT id, title, description, completed FROM todos ORDER BY completed, id") + if err != nil { + log.Fatal(err) + } + defer rows.Close() + + fmt.Println("\nPending Todos:") + fmt.Println("-------------") + pendingFound := false + + for rows.Next() { + var todo Todo + if err := rows.Scan(&todo.Id, &todo.Title, &todo.Description, &todo.Completed); err != nil { + log.Fatal(err) + } + if !todo.Completed { + pendingFound = true + fmt.Printf("[%d] %s\n %s\n\n", todo.Id, todo.Title, todo.Description) + } + } + + if !pendingFound { + fmt.Println("No pending todos!\n") + } + + // Reset the rows for completed todos + rows, err = db.Query("SELECT id, title, description, completed FROM todos WHERE completed = 1 ORDER BY id") + if err != nil { + log.Fatal(err) + } + defer rows.Close() + + fmt.Println("Completed Todos:") + fmt.Println("---------------") + completedFound := false + + for rows.Next() { + var todo Todo + if err := rows.Scan(&todo.Id, &todo.Title, &todo.Description, &todo.Completed); err != nil { + log.Fatal(err) + } + completedFound = true + fmt.Printf("[%d] %s\n %s\n\n", todo.Id, todo.Title, todo.Description) + } + + if !completedFound { + fmt.Println("No completed todos!") + } +} + +func handleComplete(_ *cobra.Command, _ []string) { + db := getDB() + defer db.Close() + + result, err := db.Exec("UPDATE todos SET completed = 1 WHERE id = ?", id) + if err != nil { + log.Fatal(err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + log.Fatal(err) + } + + if rowsAffected == 0 { + fmt.Printf("No todo found with ID %d\n", id) + return + } + + fmt.Printf("Todo %d marked as completed!\n", id) +} + +func handleRemove(_ *cobra.Command, _ []string) { + db := getDB() + defer db.Close() + + result, err := db.Exec("DELETE FROM todos WHERE id = ?", id) + if err != nil { + log.Fatal(err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + log.Fatal(err) + } + + if rowsAffected == 0 { + fmt.Printf("No todo found with ID %d\n", id) + return + } + + fmt.Printf("Todo %d removed successfully!\n", id) +} diff --git a/go.mod b/go.mod index 8b75e24..3a39d03 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,12 @@ module github.com/AmanTahiliani/todo go 1.24.0 -require github.com/mattn/go-sqlite3 v1.14.28 +require ( + github.com/mattn/go-sqlite3 v1.14.28 + github.com/spf13/cobra v1.9.1 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect +) diff --git a/go.sum b/go.sum index 42e5bac..3089b82 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=