Compare commits
7 Commits
7aa3760711
...
402bcd35a9
Author | SHA1 | Date | |
---|---|---|---|
402bcd35a9 | |||
796075d9f2 | |||
1874a875f5 | |||
0ca262c2ea | |||
de0a3b3e85 | |||
9b0597e680 | |||
3ae5ea780e |
18
cmd/cli/main.go
Normal file
18
cmd/cli/main.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"ketotrack/config"
|
||||||
|
"ketotrack/data"
|
||||||
|
"ketotrack/ui/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
appConfig := config.Load()
|
||||||
|
appCtx, err := data.NewContext(appConfig.DataPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error initializing application context: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cli.Run(&appCtx)
|
||||||
|
}
|
20
config/config.go
Normal file
20
config/config.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
DataPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() AppConfig {
|
||||||
|
// TODO: Handle os.MkdirAll error gracefully
|
||||||
|
homeDir, _ := os.UserHomeDir()
|
||||||
|
ketotrackDir := filepath.Join(homeDir, ".ketotrack")
|
||||||
|
os.MkdirAll(ketotrackDir, 0770) // handle error appropriately
|
||||||
|
return AppConfig{
|
||||||
|
DataPath: filepath.Join(ketotrackDir, "records.json"),
|
||||||
|
}
|
||||||
|
}
|
50
data/data.go
Normal file
50
data/data.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"ketotrack/model"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppContext is the application data store that holds Records
|
||||||
|
type AppContext struct {
|
||||||
|
Records []model.Record
|
||||||
|
dataPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContext(dataPath string) (AppContext, error) {
|
||||||
|
ctx := AppContext{dataPath: dataPath}
|
||||||
|
err := ctx.LoadRecords()
|
||||||
|
if err != nil {
|
||||||
|
return AppContext{}, err
|
||||||
|
}
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadRecords will load records from file
|
||||||
|
func (ctx *AppContext) LoadRecords() error {
|
||||||
|
data, err := os.ReadFile(ctx.dataPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &ctx.Records)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveRecords will save records to a file
|
||||||
|
func (ctx *AppContext) SaveRecords() error {
|
||||||
|
jsonData, err := json.Marshal(ctx.Records)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(ctx.dataPath, jsonData, 0660)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReading will add a Reading to the AppContext
|
||||||
|
func (ctx *AppContext) AddReading(reading model.Reading) {
|
||||||
|
ctx.Records = append(ctx.Records, model.Record{Reading: &reading})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNote will a add a Note to the AppContext
|
||||||
|
func (ctx *AppContext) AddNote(note model.Note) {
|
||||||
|
ctx.Records = append(ctx.Records, model.Record{Note: ¬e})
|
||||||
|
}
|
242
main.go
242
main.go
@ -1,242 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var recordsFilename string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Get user's homeDir directory
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error fetching user's home directory:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the path to the application data directory
|
|
||||||
ketotrackDir := filepath.Join(homeDir, ".ketotrack")
|
|
||||||
|
|
||||||
// Create the directory if it does not exist
|
|
||||||
err = os.MkdirAll(ketotrackDir, 0770)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error creating .ketotrack directory:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set data file path for readings
|
|
||||||
recordsFilename = filepath.Join(ketotrackDir, "records.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record holds a pointer to a Reading or Note.
|
|
||||||
type Record struct {
|
|
||||||
Reading *Reading `json:"reading,omitempty"`
|
|
||||||
Note *Note `json:"note,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note holds a text note along with the time the note was taken.
|
|
||||||
type Note struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNote returns a new Note
|
|
||||||
func NewNote(text string) Note {
|
|
||||||
return Note{
|
|
||||||
Time: time.Now(),
|
|
||||||
Text: text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading holds the glucose and ketone level measurements along with the time the measurements were taken.
|
|
||||||
type Reading struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Glucose float64 `json:"glucose"`
|
|
||||||
Ketone float64 `json:"ketone"`
|
|
||||||
GKI float64 `json:"GKI"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReading creates and returns a new Reading instance with the provided glucose and ketone levels, and records
|
|
||||||
// the current time. The glucose value should be provided in mg/dL, while the ketone level should be in mmol/L.
|
|
||||||
func NewReading(glucose, ketone float64) Reading {
|
|
||||||
var gki float64
|
|
||||||
if ketone == 0 {
|
|
||||||
gki = 0
|
|
||||||
} else {
|
|
||||||
gki = (glucose / 18) / ketone
|
|
||||||
}
|
|
||||||
|
|
||||||
return Reading{
|
|
||||||
Time: time.Now(),
|
|
||||||
Glucose: glucose,
|
|
||||||
Ketone: ketone,
|
|
||||||
GKI: gki,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppContext is the application data store that holds Records
|
|
||||||
type AppContext struct {
|
|
||||||
Records []Record
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadRecords will load records from file
|
|
||||||
func (ctx *AppContext) LoadRecords(filename string) error {
|
|
||||||
data, err := os.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return json.Unmarshal(data, &ctx.Records)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveRecords will save records to a file
|
|
||||||
func (ctx *AppContext) SaveRecords(filename string) error {
|
|
||||||
jsonData, err := json.Marshal(ctx.Records)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(filename, jsonData, 0660)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddReading will add a Reading to the AppContext
|
|
||||||
func (ctx *AppContext) AddReading(reading Reading) {
|
|
||||||
ctx.Records = append(ctx.Records, Record{Reading: &reading})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddNote will a add a Note to the AppContext
|
|
||||||
func (ctx *AppContext) AddNote(note Note) {
|
|
||||||
ctx.Records = append(ctx.Records, Record{Note: ¬e})
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// TODO: Write option handlers (handleNewNote, handleNewReading)
|
|
||||||
|
|
||||||
var appCtx AppContext
|
|
||||||
|
|
||||||
if err := appCtx.LoadRecords(recordsFilename); err != nil {
|
|
||||||
fmt.Printf("Error loading records from file: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
choice, err := getUserInput("1. Enter new reading\n2. Enter new note\n3. Exit\nYour choice")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error reading choice: %s\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if choice == "3" {
|
|
||||||
fmt.Println("Exiting...")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch choice {
|
|
||||||
case "1":
|
|
||||||
// Get a new Reading from the user
|
|
||||||
reading, err := getReading()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error getting reading: %s\n", err)
|
|
||||||
} else {
|
|
||||||
appCtx.AddReading(reading)
|
|
||||||
}
|
|
||||||
case "2":
|
|
||||||
// Get a new Note from the user
|
|
||||||
note, err := getNote()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error getting note: %s\n", err)
|
|
||||||
} else {
|
|
||||||
appCtx.AddNote(note)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Printf("Invalid choice, please try again.\n\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save Records before exiting
|
|
||||||
if err := appCtx.SaveRecords(recordsFilename); err != nil {
|
|
||||||
fmt.Printf("Error saving records to file: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNote() (Note, error) {
|
|
||||||
text, err := getUserInput("Enter note text")
|
|
||||||
if err != nil {
|
|
||||||
return Note{}, err
|
|
||||||
}
|
|
||||||
return NewNote(text), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReading() (Reading, error) {
|
|
||||||
// Get glucose reading
|
|
||||||
glucose, err := getUserFloat("Enter glucose reading (mg/dL)")
|
|
||||||
if err != nil {
|
|
||||||
return Reading{}, fmt.Errorf("error getting glucose reading: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate glucose reading
|
|
||||||
if glucose <= 0 {
|
|
||||||
return Reading{}, errors.New("glucose reading cannot be less than or equal to 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get ketone reading
|
|
||||||
ketone, err := getUserFloat("Enter ketone reading (mmol/L)")
|
|
||||||
if err != nil {
|
|
||||||
return Reading{}, fmt.Errorf("error getting ketone reading: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate ketone reading
|
|
||||||
if ketone < 0 {
|
|
||||||
fmt.Println("")
|
|
||||||
return Reading{}, errors.New("ketone reading cannot be less than 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
reading := NewReading(glucose, ketone)
|
|
||||||
|
|
||||||
// Display GKI and level of ketosis
|
|
||||||
fmt.Printf("\nYour GKI is: %0.2f\n", reading.GKI)
|
|
||||||
switch {
|
|
||||||
case reading.GKI <= 1:
|
|
||||||
fmt.Println("You're in the highest level of ketosis.")
|
|
||||||
case reading.GKI < 3:
|
|
||||||
fmt.Println("You're in a high therapeutic level of ketosis.")
|
|
||||||
case reading.GKI < 6:
|
|
||||||
fmt.Println("You're in a moderate level of ketosis.")
|
|
||||||
case reading.GKI <= 9:
|
|
||||||
fmt.Println("You're in a low level of ketosis.")
|
|
||||||
default:
|
|
||||||
fmt.Println("You are not in ketosis.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return reading, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUserInput(prompt string) (string, error) {
|
|
||||||
fmt.Printf("%s: ", prompt)
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
input, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(input), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUserFloat(prompt string) (float64, error) {
|
|
||||||
// Get input as a string
|
|
||||||
s, err := getUserInput(prompt)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert string to float
|
|
||||||
f, err := strconv.ParseFloat(s, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
49
model/types.go
Normal file
49
model/types.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Note holds a text note along with the time the note was taken.
|
||||||
|
type Note struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNote returns a new Note
|
||||||
|
func NewNote(text string) Note {
|
||||||
|
return Note{
|
||||||
|
Time: time.Now(),
|
||||||
|
Text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading holds the glucose and ketone level measurements along with the time the measurements were taken.
|
||||||
|
type Reading struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Glucose float64 `json:"glucose"`
|
||||||
|
Ketone float64 `json:"ketone"`
|
||||||
|
GKI float64 `json:"GKI"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReading creates and returns a new Reading instance with the provided glucose and ketone levels, and records
|
||||||
|
// the current time. The glucose value should be provided in mg/dL, while the ketone level should be in mmol/L.
|
||||||
|
func NewReading(glucose, ketone float64) Reading {
|
||||||
|
var gki float64
|
||||||
|
if ketone == 0 {
|
||||||
|
gki = 0
|
||||||
|
} else {
|
||||||
|
gki = (glucose / 18) / ketone
|
||||||
|
}
|
||||||
|
|
||||||
|
return Reading{
|
||||||
|
Time: time.Now(),
|
||||||
|
Glucose: glucose,
|
||||||
|
Ketone: ketone,
|
||||||
|
GKI: gki,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record holds a pointer to a Reading or Note.
|
||||||
|
type Record struct {
|
||||||
|
Reading *Reading `json:"reading,omitempty"`
|
||||||
|
Note *Note `json:"note,omitempty"`
|
||||||
|
}
|
134
ui/cli/cli.go
Normal file
134
ui/cli/cli.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"ketotrack/data"
|
||||||
|
"ketotrack/model"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(appCtx *data.AppContext) {
|
||||||
|
if err := appCtx.LoadRecords(); err != nil {
|
||||||
|
fmt.Printf("Error loading records from file: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
choice, err := getUserInput("1. Enter new reading\n2. Enter new note\n3. Exit\nYour choice")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading choice: %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if choice == "3" {
|
||||||
|
fmt.Println("Exiting...")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch choice {
|
||||||
|
case "1":
|
||||||
|
// Get a new Reading from the user
|
||||||
|
reading, err := getReading()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting reading: %s\n", err)
|
||||||
|
} else {
|
||||||
|
appCtx.AddReading(reading)
|
||||||
|
}
|
||||||
|
case "2":
|
||||||
|
// Get a new Note from the user
|
||||||
|
note, err := getNote()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting note: %s\n", err)
|
||||||
|
} else {
|
||||||
|
appCtx.AddNote(note)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Printf("Invalid choice, please try again.\n\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save Records before exiting
|
||||||
|
if err := appCtx.SaveRecords(); err != nil {
|
||||||
|
fmt.Printf("Error saving records to file: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNote() (model.Note, error) {
|
||||||
|
text, err := getUserInput("Enter note text")
|
||||||
|
if err != nil {
|
||||||
|
return model.Note{}, err
|
||||||
|
}
|
||||||
|
return model.NewNote(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReading() (model.Reading, error) {
|
||||||
|
// Get glucose reading
|
||||||
|
glucose, err := getUserFloat("Enter glucose reading (mg/dL)")
|
||||||
|
if err != nil {
|
||||||
|
return model.Reading{}, fmt.Errorf("error getting glucose reading: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate glucose reading
|
||||||
|
if glucose <= 0 {
|
||||||
|
return model.Reading{}, errors.New("glucose reading cannot be less than or equal to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ketone reading
|
||||||
|
ketone, err := getUserFloat("Enter ketone reading (mmol/L)")
|
||||||
|
if err != nil {
|
||||||
|
return model.Reading{}, fmt.Errorf("error getting ketone reading: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ketone reading
|
||||||
|
if ketone < 0 {
|
||||||
|
fmt.Println("")
|
||||||
|
return model.Reading{}, errors.New("ketone reading cannot be less than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
reading := model.NewReading(glucose, ketone)
|
||||||
|
|
||||||
|
// Display GKI and level of ketosis
|
||||||
|
fmt.Printf("\nYour GKI is: %0.2f\n", reading.GKI)
|
||||||
|
switch {
|
||||||
|
case reading.GKI <= 1:
|
||||||
|
fmt.Println("You're in the highest level of ketosis.")
|
||||||
|
case reading.GKI < 3:
|
||||||
|
fmt.Println("You're in a high therapeutic level of ketosis.")
|
||||||
|
case reading.GKI < 6:
|
||||||
|
fmt.Println("You're in a moderate level of ketosis.")
|
||||||
|
case reading.GKI <= 9:
|
||||||
|
fmt.Println("You're in a low level of ketosis.")
|
||||||
|
default:
|
||||||
|
fmt.Println("You are not in ketosis.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return reading, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserInput(prompt string) (string, error) {
|
||||||
|
fmt.Printf("%s: ", prompt)
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
input, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(input), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserFloat(prompt string) (float64, error) {
|
||||||
|
// Get input as a string
|
||||||
|
s, err := getUserInput(prompt)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert string to float
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user