diff options
| author | demo <demo@antix1> | 2026-05-29 11:27:00 -0400 |
|---|---|---|
| committer | demo <demo@antix1> | 2026-05-29 11:27:00 -0400 |
| commit | e0e6d43095e93d65833dcebe79ec85778c29eb32 (patch) | |
| tree | fb6395385a29baa2b041201ed56f8d979b78c8bf | |
| parent | df480b02bceb31039e25c12357404248ba13b8f8 (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.go | 77 | ||||
| -rw-r--r-- | go.mod | 7 | ||||
| -rw-r--r-- | go.sum | 10 | ||||
| -rw-r--r-- | main.go | 39 |
4 files changed, 118 insertions, 15 deletions
@@ -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 + }) } @@ -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 @@ -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= @@ -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() }, }, }, |
