Compare commits

..

7 Commits

Author SHA1 Message Date
61a4c0dee6 refactor user input 2024-05-12 14:58:23 -04:00
e7a78a4d33 NewNote returns a new Note 2024-05-12 14:45:04 -04:00
4a006be08f remove main_test 2024-05-12 14:40:14 -04:00
18436e050f implement AppContext, add Note type, add menu 2024-05-12 14:38:55 -04:00
7128dbd38e add AppContext 2024-05-12 14:22:04 -04:00
e5ab7ec5ef add Record and Note types 2024-05-12 14:13:24 -04:00
174173f99b refactor: add GKI field to Reading 2024-05-12 14:09:50 -04:00
3 changed files with 121 additions and 78 deletions

View File

@ -22,3 +22,11 @@ Reading data will be saved to `$HOME/.ketotrack/readings.json`.
go build go build
mv ketotrack $HOME/.local/bin mv ketotrack $HOME/.local/bin
``` ```
## Refactoring Notes
- [X] Create `Record` and `Note` struct types
- [X] Add `GKI` field to `Reading` struct type
- [X] Encapsulate application data with an `AppContext` struct that holds a `Records` slice
- [X] `LoadRecords`, `SaveRecords`, `AddReading`, `AddNote` receiver methods
- [X] Add interactive menu for adding either a `Note` or a `Record`
- [ ] Standardize method names for getting user input to `handleNewNote` and `handleNewReading`

171
main.go
View File

@ -12,7 +12,7 @@ import (
"time" "time"
) )
var readingsFilename string var recordsFilename string
func init() { func init() {
// Get user's homeDir directory // Get user's homeDir directory
@ -33,7 +33,27 @@ func init() {
} }
// Set data file path for readings // Set data file path for readings
readingsFilename = filepath.Join(ketotrackDir, "readings.json") 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. // Reading holds the glucose and ketone level measurements along with the time the measurements were taken.
@ -41,89 +61,124 @@ type Reading struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
Glucose float64 `json:"glucose"` Glucose float64 `json:"glucose"`
Ketone float64 `json:"ketone"` 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 // 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. // 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 { func NewReading(glucose, ketone float64) Reading {
var gki float64
if ketone == 0 {
gki = 0
} else {
gki = (glucose / 18) / ketone
}
return Reading{ return Reading{
Time: time.Now(), Time: time.Now(),
Glucose: glucose, Glucose: glucose,
Ketone: ketone, Ketone: ketone,
GKI: gki,
} }
} }
// GKI calculates and returns the Glucose Ketone Index of the reading. // AppContext is the application data store that holds Records
// The Glucose Ketone Index helps determine the efficiency of glucose and ketone levels within the body. It is type AppContext struct {
// calculated as (glucose level in mg/dL / 18) divided by the ketone level. Records []Record
func (r Reading) GKI() (float64, error) {
if r.Ketone == 0 {
return 0, errors.New("ketone level must not be zero to calculate GKI")
}
return (r.Glucose / 18) / r.Ketone, nil
} }
func main() { // LoadRecords will load records from file
// Take a new reading from the user func (ctx *AppContext) LoadRecords(filename string) error {
reading, err := getReading() data, err := os.ReadFile(filename)
if err != nil { if err != nil {
fmt.Println(err.Error()) return err
return }
return json.Unmarshal(data, &ctx.Records)
} }
// Calculate GKI // SaveRecords will save records to a file
gki, err := reading.GKI() func (ctx *AppContext) SaveRecords(filename string) error {
if err != nil { jsonData, err := json.Marshal(ctx.Records)
fmt.Println(err.Error())
fmt.Println("You are not in ketosis.")
return
}
fmt.Printf("\nYour GKI is: %0.2f\n", gki)
// Display level of ketosis
switch {
case gki <= 1:
fmt.Println("You're in the highest level of ketosis.")
case gki < 3:
fmt.Println("You're in a high therapeutic level of ketosis.")
case gki < 6:
fmt.Println("You're in a moderate level of ketosis.")
case gki <= 9:
fmt.Println("You're in a low level of ketosis.")
default:
fmt.Println("You are not in ketosis.")
}
// Save latest reading to readings file
readings, _ := loadReadingsFromFile(readingsFilename)
readings = append(readings, reading)
err = saveReadingsToFile(readingsFilename, readings)
if err != nil {
fmt.Printf("error saving data: %s\n", err.Error())
}
}
func saveReadingsToFile(filename string, data []Reading) error {
jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
return err return err
} }
return os.WriteFile(filename, jsonData, 0660) return os.WriteFile(filename, jsonData, 0660)
} }
func loadReadingsFromFile(filename string) ([]Reading, error) { // AddReading will add a Reading to the AppContext
data, err := os.ReadFile(filename) func (ctx *AppContext) AddReading(reading Reading) {
if err != nil { ctx.Records = append(ctx.Records, Record{Reading: &reading})
return nil, err
} }
var readings []Reading // AddNote will a add a Note to the AppContext
err = json.Unmarshal(data, &readings) func (ctx *AppContext) AddNote(note Note) {
if err != nil { ctx.Records = append(ctx.Records, Record{Note: &note})
return nil, err
} }
return readings, nil func main() {
// TODO: Clean up menu prompt
// TODO: Write getNote method
// 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 {
// 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.")
}
appCtx.AddReading(reading)
}
case "2":
// Get a new Note from the user
noteText, err := getUserInput("Enter your note text: ")
if err != nil {
fmt.Printf("Error getting note: %s\n", err)
} else {
note := NewNote(noteText)
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 getReading() (Reading, error) { func getReading() (Reading, error) {

View File

@ -1,20 +0,0 @@
package main
import (
"math"
"testing"
)
func TestGKI(t *testing.T) {
r := Reading{
Glucose: 77,
Ketone: 1.1,
}
expected := (77.0 / 18) / 1.1
epsilon := 0.0001 // Tolerance level
gki, _ := r.GKI()
if math.Abs(gki-expected) > epsilon {
t.Errorf("expected %v, got %v", expected, gki)
}
}