mirror of
https://github.com/sstent/go-garth.git
synced 2026-01-26 00:52:40 +00:00
feat: Implement Phase 1B: Enhanced CLI Commands
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"garmin-connect/pkg/garmin"
|
||||
)
|
||||
@@ -41,6 +46,7 @@ var (
|
||||
statsMonth bool
|
||||
statsYear bool
|
||||
statsFrom string
|
||||
statsAggregate string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -48,12 +54,15 @@ func init() {
|
||||
|
||||
statsCmd.AddCommand(stepsCmd)
|
||||
stepsCmd.Flags().BoolVar(&statsMonth, "month", false, "Fetch data for the current month")
|
||||
stepsCmd.Flags().StringVar(&statsAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
|
||||
|
||||
statsCmd.AddCommand(distanceCmd)
|
||||
distanceCmd.Flags().BoolVar(&statsYear, "year", false, "Fetch data for the current year")
|
||||
distanceCmd.Flags().StringVar(&statsAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
|
||||
|
||||
statsCmd.AddCommand(caloriesCmd)
|
||||
caloriesCmd.Flags().StringVar(&statsFrom, "from", "", "Start date for data fetching (YYYY-MM-DD)")
|
||||
caloriesCmd.Flags().StringVar(&statsAggregate, "aggregate", "", "Aggregate data by (day, week, month, year)")
|
||||
}
|
||||
|
||||
func runSteps(cmd *cobra.Command, args []string) error {
|
||||
@@ -88,14 +97,108 @@ func runSteps(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Steps Data:")
|
||||
for _, data := range stepsData {
|
||||
fmt.Printf("- Date: %s, Steps: %d\n", data.Date.Format("2006-01-02"), data.Steps)
|
||||
// Apply aggregation if requested
|
||||
if statsAggregate != "" {
|
||||
aggregatedSteps := make(map[string]struct {
|
||||
Steps int
|
||||
Count int
|
||||
})
|
||||
|
||||
for _, data := range stepsData {
|
||||
key := ""
|
||||
switch statsAggregate {
|
||||
case "day":
|
||||
key = data.Date.Format("2006-01-02")
|
||||
case "week":
|
||||
year, week := data.Date.ISOWeek()
|
||||
key = fmt.Sprintf("%d-W%02d", year, week)
|
||||
case "month":
|
||||
key = data.Date.Format("2006-01")
|
||||
case "year":
|
||||
key = data.Date.Format("2006")
|
||||
default:
|
||||
return fmt.Errorf("unsupported aggregation period: %s", statsAggregate)
|
||||
}
|
||||
|
||||
entry := aggregatedSteps[key]
|
||||
entry.Steps += data.Steps
|
||||
entry.Count++
|
||||
aggregatedSteps[key] = entry
|
||||
}
|
||||
|
||||
// Convert aggregated data back to a slice for output
|
||||
stepsData = []garmin.StepsData{}
|
||||
for key, entry := range aggregatedSteps {
|
||||
stepsData = append(stepsData, garmin.StepsData{
|
||||
Date: parseAggregationKey(key, statsAggregate), // Helper to parse key back to date
|
||||
Steps: entry.Steps / entry.Count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
outputFormat := viper.GetString("output")
|
||||
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
data, err := json.MarshalIndent(stepsData, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal steps data to JSON: %w", err)
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
case "csv":
|
||||
writer := csv.NewWriter(os.Stdout)
|
||||
defer writer.Flush()
|
||||
|
||||
writer.Write([]string{"Date", "Steps"})
|
||||
for _, data := range stepsData {
|
||||
writer.Write([]string{
|
||||
data.Date.Format("2006-01-02"),
|
||||
fmt.Sprintf("%d", data.Steps),
|
||||
})
|
||||
}
|
||||
case "table":
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Steps"})
|
||||
for _, data := range stepsData {
|
||||
table.Append([]string{
|
||||
data.Date.Format("2006-01-02"),
|
||||
fmt.Sprintf("%d", data.Steps),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
default:
|
||||
return fmt.Errorf("unsupported output format: %s", outputFormat)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function to parse aggregation key back to a time.Time object
|
||||
func parseAggregationKey(key, aggregate string) time.Time {
|
||||
switch aggregate {
|
||||
case "day":
|
||||
t, _ := time.Parse("2006-01-02", key)
|
||||
return t
|
||||
case "week":
|
||||
year, _ := strconv.Atoi(key[:4])
|
||||
week, _ := strconv.Atoi(key[6:])
|
||||
t := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
// Find the first Monday of the year
|
||||
for t.Weekday() != time.Monday {
|
||||
t = t.AddDate(0, 0, 1)
|
||||
}
|
||||
// Add weeks
|
||||
return t.AddDate(0, 0, (week-1)*7)
|
||||
case "month":
|
||||
t, _ := time.Parse("2006-01", key)
|
||||
return t
|
||||
case "year":
|
||||
t, _ := time.Parse("2006", key)
|
||||
return t
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func runDistance(cmd *cobra.Command, args []string) error {
|
||||
garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
|
||||
if err != nil {
|
||||
@@ -128,9 +231,77 @@ func runDistance(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Distance Data:")
|
||||
for _, data := range distanceData {
|
||||
fmt.Printf("- Date: %s, Distance: %.2f km\n", data.Date.Format("2006-01-02"), data.Distance/1000)
|
||||
// Apply aggregation if requested
|
||||
if statsAggregate != "" {
|
||||
aggregatedDistance := make(map[string]struct {
|
||||
Distance float64
|
||||
Count int
|
||||
})
|
||||
|
||||
for _, data := range distanceData {
|
||||
key := ""
|
||||
switch statsAggregate {
|
||||
case "day":
|
||||
key = data.Date.Format("2006-01-02")
|
||||
case "week":
|
||||
year, week := data.Date.ISOWeek()
|
||||
key = fmt.Sprintf("%d-W%02d", year, week)
|
||||
case "month":
|
||||
key = data.Date.Format("2006-01")
|
||||
case "year":
|
||||
key = data.Date.Format("2006")
|
||||
default:
|
||||
return fmt.Errorf("unsupported aggregation period: %s", statsAggregate)
|
||||
}
|
||||
|
||||
entry := aggregatedDistance[key]
|
||||
entry.Distance += data.Distance
|
||||
entry.Count++
|
||||
aggregatedDistance[key] = entry
|
||||
}
|
||||
|
||||
// Convert aggregated data back to a slice for output
|
||||
distanceData = []garmin.DistanceData{}
|
||||
for key, entry := range aggregatedDistance {
|
||||
distanceData = append(distanceData, garmin.DistanceData{
|
||||
Date: parseAggregationKey(key, statsAggregate), // Helper to parse key back to date
|
||||
Distance: entry.Distance / float64(entry.Count),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
outputFormat := viper.GetString("output")
|
||||
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
data, err := json.MarshalIndent(distanceData, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal distance data to JSON: %w", err)
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
case "csv":
|
||||
writer := csv.NewWriter(os.Stdout)
|
||||
defer writer.Flush()
|
||||
|
||||
writer.Write([]string{"Date", "Distance(km)"})
|
||||
for _, data := range distanceData {
|
||||
writer.Write([]string{
|
||||
data.Date.Format("2006-01-02"),
|
||||
fmt.Sprintf("%.2f", data.Distance/1000),
|
||||
})
|
||||
}
|
||||
case "table":
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Distance (km)"})
|
||||
for _, data := range distanceData {
|
||||
table.Append([]string{
|
||||
data.Date.Format("2006-01-02"),
|
||||
fmt.Sprintf("%.2f", data.Distance/1000),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
default:
|
||||
return fmt.Errorf("unsupported output format: %s", outputFormat)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -170,9 +341,77 @@ func runCalories(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Calories Data:")
|
||||
for _, data := range caloriesData {
|
||||
fmt.Printf("- Date: %s, Calories: %d\n", data.Date.Format("2006-01-02"), data.Calories)
|
||||
// Apply aggregation if requested
|
||||
if statsAggregate != "" {
|
||||
aggregatedCalories := make(map[string]struct {
|
||||
Calories int
|
||||
Count int
|
||||
})
|
||||
|
||||
for _, data := range caloriesData {
|
||||
key := ""
|
||||
switch statsAggregate {
|
||||
case "day":
|
||||
key = data.Date.Format("2006-01-02")
|
||||
case "week":
|
||||
year, week := data.Date.ISOWeek()
|
||||
key = fmt.Sprintf("%d-W%02d", year, week)
|
||||
case "month":
|
||||
key = data.Date.Format("2006-01")
|
||||
case "year":
|
||||
key = data.Date.Format("2006")
|
||||
default:
|
||||
return fmt.Errorf("unsupported aggregation period: %s", statsAggregate)
|
||||
}
|
||||
|
||||
entry := aggregatedCalories[key]
|
||||
entry.Calories += data.Calories
|
||||
entry.Count++
|
||||
aggregatedCalories[key] = entry
|
||||
}
|
||||
|
||||
// Convert aggregated data back to a slice for output
|
||||
caloriesData = []garmin.CaloriesData{}
|
||||
for key, entry := range aggregatedCalories {
|
||||
caloriesData = append(caloriesData, garmin.CaloriesData{
|
||||
Date: parseAggregationKey(key, statsAggregate), // Helper to parse key back to date
|
||||
Calories: entry.Calories / entry.Count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
outputFormat := viper.GetString("output")
|
||||
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
data, err := json.MarshalIndent(caloriesData, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal calories data to JSON: %w", err)
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
case "csv":
|
||||
writer := csv.NewWriter(os.Stdout)
|
||||
defer writer.Flush()
|
||||
|
||||
writer.Write([]string{"Date", "Calories"})
|
||||
for _, data := range caloriesData {
|
||||
writer.Write([]string{
|
||||
data.Date.Format("2006-01-02"),
|
||||
fmt.Sprintf("%d", data.Calories),
|
||||
})
|
||||
}
|
||||
case "table":
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Calories"})
|
||||
for _, data := range caloriesData {
|
||||
table.Append([]string{
|
||||
data.Date.Format("2006-01-02"),
|
||||
fmt.Sprintf("%d", data.Calories),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
default:
|
||||
return fmt.Errorf("unsupported output format: %s", outputFormat)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user