From 33d0c7d07346335c2b07576b4b333ef7e693a4c6 Mon Sep 17 00:00:00 2001 From: rsxri Date: Thu, 15 May 2025 21:03:35 +0100 Subject: [PATCH] feat(cli): add inventory tracking (add/list) with persistent json storage --- src/pantry/core/inventory.go | 44 +++++++++++++++++++ src/pantry/core/storage.go | 39 +++++++++++++++++ src/pantry/main.go | 83 +++++++++++++++++++++++++++++++++++- src/pantry/util/dates.go | 23 ++++++++++ src/pantry/util/paths.go | 22 ++++++++++ 5 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/pantry/core/inventory.go create mode 100644 src/pantry/core/storage.go create mode 100644 src/pantry/util/dates.go create mode 100644 src/pantry/util/paths.go diff --git a/src/pantry/core/inventory.go b/src/pantry/core/inventory.go new file mode 100644 index 0000000..a58f12f --- /dev/null +++ b/src/pantry/core/inventory.go @@ -0,0 +1,44 @@ +package core + +import ( + "fmt" + "time" + + "pantry/util" +) + +type InventoryItem struct { + Name string + Quantity float64 + Unit string + Expiry *time.Time + AddedAt time.Time +} + +type Inventory struct { + Items []InventoryItem +} + +func (p *Inventory) AddItem(item InventoryItem) { + for i, existing := range p.Items { + if existing.Name == item.Name { + p.Items[i].Quantity += item.Quantity + fmt.Printf("Updated %s, new quantity: %.2f %s\n", item.Name, p.Items[i].Quantity, item.Unit) + return + } + } + p.Items = append(p.Items, item) + fmt.Printf("Added %s, quantity: %.2f %s\n", item.Name, item.Quantity, item.Unit) +} + +func (p *Inventory) ListItems() { + if len(p.Items) == 0 { + fmt.Printf("Pantry is empty\n") + return + } + + fmt.Printf("Your pantry:\n") + for _, item := range p.Items { + fmt.Printf("- %s: %.2f %s, (%s)\n", item.Name, item.Quantity, item.Unit, util.DaysUntilExpiry(item.Expiry)) + } +} diff --git a/src/pantry/core/storage.go b/src/pantry/core/storage.go new file mode 100644 index 0000000..48f99d0 --- /dev/null +++ b/src/pantry/core/storage.go @@ -0,0 +1,39 @@ +package core + +import ( + "encoding/json" + "fmt" + "os" +) + +func SaveInventory(filename string, inventory *Inventory) error { + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create file: %s", err) + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + return encoder.Encode(inventory.Items) +} + +func LoadInventory(filename string) (*Inventory, error) { + file, err := os.Open(filename) + if err != nil { + if os.IsNotExist(err) { + return &Inventory{}, nil + } + return nil, fmt.Errorf("failed to open file: %s", err) + } + defer file.Close() + + var items []InventoryItem + decoder := json.NewDecoder(file) + err = decoder.Decode(&items) + if err != nil { + return nil, fmt.Errorf("failed to decode inventory data: %w", err) + } + + return &Inventory{Items: items}, nil +} diff --git a/src/pantry/main.go b/src/pantry/main.go index 969ba5a..106e790 100644 --- a/src/pantry/main.go +++ b/src/pantry/main.go @@ -1 +1,82 @@ -package pantry +package main + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "time" + + "pantry/core" + "pantry/util" +) + +func main() { + dataFile := util.GetDefaultPath() + err := os.MkdirAll(filepath.Dir(dataFile), 0755) + if err != nil { + fmt.Println("Failed to create data directory:", err) + return + } + + inventory, err := core.LoadInventory(dataFile) + if err != nil { + fmt.Println("Failed to load inventory:", err) + return + } + + if len(os.Args) < 2 { + fmt.Println("Usage: inventory [add|list]") + return + } + + command := os.Args[1] + + switch command { + case "add": + if len(os.Args) < 5 { + fmt.Println("Usage: inventory add [name] [quantity] [unit] [expiry:YYYY-MM-DD]") + return + } + + name := os.Args[2] + + quantity, err := strconv.ParseFloat(os.Args[3], 64) + if err != nil { + fmt.Println("Invalid quantity") + return + } + unit := os.Args[4] + + var expiry *time.Time + if len(os.Args) >= 6 { + parsed, err := time.Parse("2006-01-02", os.Args[5]) + if err != nil { + fmt.Println("Invalid expiry date format. Expected YYYY-MM-DD") + return + } + expiry = &parsed + } + + item := core.InventoryItem{ + Name: name, + Quantity: quantity, + Unit: unit, + Expiry: expiry, + AddedAt: time.Now(), + } + + inventory.AddItem(item) + err = core.SaveInventory(dataFile, inventory) + if err != nil { + fmt.Println("Failed to save inventory:", err) + return + } + + case "list": + inventory.ListItems() + + default: + fmt.Println("Unknown command:", command) + } +} diff --git a/src/pantry/util/dates.go b/src/pantry/util/dates.go new file mode 100644 index 0000000..0cbf8f7 --- /dev/null +++ b/src/pantry/util/dates.go @@ -0,0 +1,23 @@ +package util + +import ( + "fmt" + "time" +) + +// DaysUntilExpiry - takes expiry time and returns as a string. +func DaysUntilExpiry(expiry *time.Time) string { + if expiry == nil { + return "no expiry date" + } + days := int(expiry.Sub(time.Now()).Hours() / 24) + if days < 0 { + return "expired" + } else if days == 0 { + return "expires today" + } else if days == 1 { + return "in 1 day" + } else { + return fmt.Sprintf("in %d days", days) + } +} diff --git a/src/pantry/util/paths.go b/src/pantry/util/paths.go new file mode 100644 index 0000000..7c9bdea --- /dev/null +++ b/src/pantry/util/paths.go @@ -0,0 +1,22 @@ +package util + +import ( + "os" + "path/filepath" + "runtime" +) + +func GetDefaultPath() string { + var base string + + if runtime.GOOS == "windows" { + base = os.Getenv("APPDATA") + } else { + base = os.Getenv("XDG_CONFIG_HOME") + if base == "" { + base = filepath.Join(os.Getenv("HOME"), ".config") + } + } + + return filepath.Join(base, "pantry", "inventory.json") +}