feat(cli): add inventory tracking (add/list) with persistent json storage
This commit is contained in:
parent
0de0686125
commit
33d0c7d073
44
src/pantry/core/inventory.go
Normal file
44
src/pantry/core/inventory.go
Normal file
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/pantry/core/storage.go
Normal file
39
src/pantry/core/storage.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
23
src/pantry/util/dates.go
Normal file
23
src/pantry/util/dates.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/pantry/util/paths.go
Normal file
22
src/pantry/util/paths.go
Normal file
|
|
@ -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")
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue