summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordemo <demo@antix1>2026-05-29 11:27:00 -0400
committerdemo <demo@antix1>2026-05-29 11:27:00 -0400
commite0e6d43095e93d65833dcebe79ec85778c29eb32 (patch)
treefb6395385a29baa2b041201ed56f8d979b78c8bf
parentdf480b02bceb31039e25c12357404248ba13b8f8 (diff)
wip: implement business logic (rough draft)
There are a ton of visual warts, but the actual logic seems sound so far.
-rw-r--r--cmds.go77
-rw-r--r--go.mod7
-rw-r--r--go.sum10
-rw-r--r--main.go39
4 files changed, 118 insertions, 15 deletions
diff --git a/cmds.go b/cmds.go
index 26cded5..241897c 100644
--- a/cmds.go
+++ b/cmds.go
@@ -1,20 +1,79 @@
package main
import (
+ "encoding/binary"
+ "encoding/json"
"fmt"
+
+ "go.etcd.io/bbolt"
)
-func cmdAdd(task string) error {
- fmt.Println("added task: ", task)
- return nil
+type taskType struct {
+ What string `json:"what"`
+ Done bool `json:"done"`
+ Index uint64 `json:"index"`
+}
+
+// itob returns an 8-byte big endian representation of v.
+func itob(v uint64) []byte {
+ b := make([]byte, 8)
+ binary.BigEndian.PutUint64(b, v)
+ return b
+}
+
+func (ctrl controller) cmdAdd(task string) error {
+ return ctrl.db.Update(func(tx *bbolt.Tx) error {
+ taskBucket := tx.Bucket(ctrl.tasksBucketName)
+
+ // Update call, so ignore the error check. See
+ // pkg.go.dev/go.etcd.io/bbolt#section-readme
+ index, _ := taskBucket.NextSequence()
+ t := taskType{
+ What: task,
+ Done: false,
+ Index: index,
+ }
+
+ buf, err := json.Marshal(t)
+ if err != nil {
+ return fmt.Errorf("can't marshal: %w", err)
+ }
+
+ return taskBucket.Put(itob(index), buf)
+ })
}
-func cmdDo(taskIndex int) error {
- fmt.Println("completed task: ", taskIndex)
- return nil
+func (ctrl controller) cmdDo(taskIndex int) error {
+ return ctrl.db.Update(func(tx *bbolt.Tx) error {
+ taskBucket := tx.Bucket(ctrl.tasksBucketName)
+ ttBytes := taskBucket.Get(itob(uint64(taskIndex)))
+
+ var tt taskType
+ if err := json.Unmarshal(ttBytes, &tt); err != nil {
+ return fmt.Errorf("can't unmarshal: %w", err)
+ }
+
+ tt.Done = true
+
+ buf, err := json.Marshal(tt)
+ if err != nil {
+ return fmt.Errorf("can't marshal: %w", err)
+ }
+
+ return taskBucket.Put(itob(uint64(taskIndex)), buf)
+ })
}
-func cmdList() error {
- fmt.Println("listed pending tasks")
- return nil
+func (ctrl controller) cmdList() error {
+ return ctrl.db.View(func(tx *bbolt.Tx) error {
+ // Assume bucket exists and has keys
+ b := tx.Bucket([]byte(ctrl.tasksBucketName))
+
+ b.ForEach(func(k, v []byte) error {
+ fmt.Printf("%s. %s\n", k, v)
+ return nil
+ })
+
+ return nil
+ })
}
diff --git a/go.mod b/go.mod
index d38e4f1..5d676e7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,8 @@ module git.brandonirizarry.xyz/task
go 1.26.2
require (
- github.com/urfave/cli/v3 v3.9.0 // indirect
- go.etcd.io/bbolt v1.4.3 // indirect
- golang.org/x/sys v0.29.0 // indirect
+ github.com/urfave/cli/v3 v3.9.0
+ go.etcd.io/bbolt v1.4.3
)
+
+require golang.org/x/sys v0.29.0 // indirect
diff --git a/go.sum b/go.sum
index e02bab0..91a09c3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,16 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c=
github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
index 355ac01..85bfa26 100644
--- a/main.go
+++ b/main.go
@@ -8,9 +8,42 @@ import (
"strings"
"github.com/urfave/cli/v3"
+ "go.etcd.io/bbolt"
)
+type controller struct {
+ db *bbolt.DB
+ tasksBucketName []byte
+}
+
+func newController(filename string) (controller, error) {
+ db, err := bbolt.Open(filename, 0644, nil)
+ if err != nil {
+ return controller{}, fmt.Errorf("can't open database file '%s': %w", filename, err)
+ }
+
+ tasksBucketName := []byte("tasks")
+
+ // Create the tasks bucket for the first time. From here on,
+ // all transactions can assume this bucket exists.
+ db.Update(func(tx *bbolt.Tx) error {
+ _, err := tx.CreateBucket(tasksBucketName)
+ if err != nil {
+ return fmt.Errorf("cant' create tasks bucket: %w", err)
+ }
+
+ return nil
+ })
+
+ return controller{db, tasksBucketName}, nil
+}
+
func main() {
+ ctrl, err := newController("tasks.db")
+ if err != nil {
+ log.Fatal(err)
+ }
+
cmd := &cli.Command{
Commands: []*cli.Command{
{
@@ -21,7 +54,7 @@ func main() {
args := cmd.Args().Slice()
task := strings.Join(args, " ")
- return cmdAdd(task)
+ return ctrl.cmdAdd(task)
},
},
{
@@ -45,7 +78,7 @@ func main() {
return fmt.Errorf("invalid 'do' argument: %d", taskIndex)
}
- return cmdDo(taskIndex)
+ return ctrl.cmdDo(taskIndex)
},
},
{
@@ -53,7 +86,7 @@ func main() {
Aliases: []string{"l"},
Usage: "list pending tasks",
Action: func(ctx context.Context, cmd *cli.Command) error {
- return cmdList()
+ return ctrl.cmdList()
},
},
},