mirror of
https://github.com/sstent/go-garth.git
synced 2025-12-05 23:51:42 +00:00
feat: Implement Phase 1A: Package Reorganization & CLI Foundation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ dist/
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
garmin_session.json
|
||||
56
cmd/garth/config.go
Normal file
56
cmd/garth/config.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"go-garth/internal/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
configCmd.AddCommand(configInitCmd)
|
||||
configCmd.AddCommand(configShowCmd)
|
||||
}
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage garth configuration",
|
||||
Long: `Allows you to initialize, show, and manage garth's configuration file.`,
|
||||
}
|
||||
|
||||
var configInitCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize a default config file",
|
||||
Long: `Creates a default garth configuration file in the standard location ($HOME/.config/garth/config.yaml) if one does not already exist.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
configPath := filepath.Join(config.UserConfigDir(), "config.yaml")
|
||||
_, err := config.InitConfig(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing config: %w", err)
|
||||
}
|
||||
fmt.Printf("Default config file initialized at: %s\n", configPath)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var configShowCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show the current configuration",
|
||||
Long: `Displays the currently loaded garth configuration, including values from the config file and environment variables.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("configuration not loaded")
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling config to YAML: %w", err)
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"garmin-connect/cmd/garth/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
Execute()
|
||||
}
|
||||
|
||||
@@ -1 +1,117 @@
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"go-garth/internal/config"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
userConfigDir string
|
||||
cfg *config.Config
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "garth",
|
||||
Short: "Garmin Connect CLI tool",
|
||||
Long: `A comprehensive CLI tool for interacting with Garmin Connect.
|
||||
|
||||
Garth allows you to fetch your Garmin Connect data, including activities,
|
||||
health stats, and more, directly from your terminal.`,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Ensure config is loaded before any command runs
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("configuration not loaded")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Global flags
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/garth/config.yaml)")
|
||||
rootCmd.PersistentFlags().StringVar(&userConfigDir, "config-dir", "", "config directory (default is $HOME/.config/garth)")
|
||||
|
||||
rootCmd.PersistentFlags().String("output", "table", "output format (json, table, csv)")
|
||||
rootCmd.PersistentFlags().Bool("verbose", false, "enable verbose output")
|
||||
rootCmd.PersistentFlags().String("date-from", "", "start date for data fetching (YYYY-MM-DD)")
|
||||
rootCmd.PersistentFlags().String("date-to", "", "end date for data fetching (YYYY-MM-DD)")
|
||||
|
||||
// Bind flags to viper
|
||||
_ = viper.BindPFlag("output.format", rootCmd.PersistentFlags().Lookup("output"))
|
||||
_ = viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
|
||||
_ = viper.BindPFlag("dateFrom", rootCmd.PersistentFlags().Lookup("date-from"))
|
||||
_ = viper.BindPFlag("dateTo", rootCmd.PersistentFlags().Lookup("date-to"))
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if userConfigDir == "" {
|
||||
userConfigDir = config.UserConfigDir()
|
||||
}
|
||||
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Search config in user's config directory with name "config" (without extension).
|
||||
viper.AddConfigPath(userConfigDir)
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
||||
} else {
|
||||
// If config file not found, try to initialize a default one
|
||||
defaultConfigPath := filepath.Join(userConfigDir, "config.yaml")
|
||||
if _, statErr := os.Stat(defaultConfigPath); os.IsNotExist(statErr) {
|
||||
fmt.Fprintln(os.Stderr, "No config file found. Initializing default config at:", defaultConfigPath)
|
||||
var initErr error
|
||||
cfg, initErr = config.InitConfig(defaultConfigPath)
|
||||
if initErr != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error initializing default config:", initErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if statErr != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error checking for config file:", statErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Unmarshal config into our struct
|
||||
if cfg == nil { // Only unmarshal if not already initialized by InitConfig
|
||||
cfg = config.DefaultConfig() // Start with defaults
|
||||
if err := viper.Unmarshal(cfg); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error unmarshaling config:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Override config with flag values
|
||||
if rootCmd.PersistentFlags().Lookup("output").Changed {
|
||||
cfg.Output.Format = viper.GetString("output.format")
|
||||
}
|
||||
// Add other flag overrides as needed
|
||||
}
|
||||
|
||||
BIN
garmin-connect
BIN
garmin-connect
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "garmin.com",
|
||||
"username": "fbleagh",
|
||||
"auth_token": "bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRpLW9hdXRoLXNpZ25lci1wcm9kLTIwMjQtcTEifQ.eyJzY29wZSI6WyJBVFBfUkVBRCIsIkFUUF9XUklURSIsIkNPTU1VTklUWV9DT1VSU0VfUkVBRCIsIkNPTU1VTklUWV9DT1VSU0VfV1JJVEUiLCJDT05ORUNUX01DVF9EQUlMWV9MT0dfUkVBRCIsIkNPTk5FQ1RfUkVBRCIsIkNPTk5FQ1RfV1JJVEUiLCJESVZFX0FQSV9SRUFEIiwiRElfT0FVVEhfMl9BVVRIT1JJWkFUSU9OX0NPREVfQ1JFQVRFIiwiRFRfQ0xJRU5UX0FOQUxZVElDU19XUklURSIsIkdBUk1JTlBBWV9SRUFEIiwiR0FSTUlOUEFZX1dSSVRFIiwiR0NPRkZFUl9SRUFEIiwiR0NPRkZFUl9XUklURSIsIkdIU19TQU1EIiwiR0hTX1VQTE9BRCIsIkdPTEZfQVBJX1JFQUQiLCJHT0xGX0FQSV9XUklURSIsIklOU0lHSFRTX1JFQUQiLCJJTlNJR0hUU19XUklURSIsIk9NVF9DQU1QQUlHTl9SRUFEIiwiT01UX1NVQlNDUklQVElPTl9SRUFEIiwiUFJPRFVDVF9TRUFSQ0hfUkVBRCJdLCJpc3MiOiJodHRwczovL2RpYXV0aC5nYXJtaW4uY29tIiwicmV2b2NhdGlvbl9lbGlnaWJpbGl0eSI6WyJHTE9CQUxfU0lHTk9VVCIsIk1BTkFHRURfU1RBVFVTIl0sImNsaWVudF90eXBlIjoiVU5ERUZJTkVEIiwiZXhwIjoxNzU3MzYyMzI3LCJpYXQiOjE3NTcyNzI0NDEsImdhcm1pbl9ndWlkIjoiNTZlZTk1ZmMtMzY0MC00NGU2LTg1ODAtNDc4NDEwZDQwZGFhIiwianRpIjoiZjVmYzFhMzAtZGVkZi00N2FmLTg5YjgtM2QwNjFjZjkxMTMxIiwiY2xpZW50X2lkIjoiR0FSTUlOX0NPTk5FQ1RfTU9CSUxFX0FORFJPSURfREkifQ.cdgNSDtkYnySkPdHTHxvck3BZXVZ0H6mGcqU0fJqqRO5cuh0_exgGM_VBLxoos_MqEYeryZqkw__UfwA1dvamoClooPpUFZIcPmsTl_uSILd8IIiWFjhgXJnTybE3mI_hPEaILzWnVDzQX4lv1K_oTzCVx0I7moonRAk3mbccKpj_kWcIm-CFVbuGbApTCJzRoOr46yFPUnbOxeA0eJl8BbPFmPWK0z_FvcLS8q7ZKuksBWW2gorQovqesIG63k-wK1PFOvm2EDosSFW0RTCFY7cBMx3nz_f7jFG9E5qt971z8EcKCq83pWs2CHIqy64KkVoub3CD0LQRKIjilNsEA"
|
||||
}
|
||||
18
go.mod
18
go.mod
@@ -1,20 +1,32 @@
|
||||
module garmin-connect
|
||||
module go-garth
|
||||
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/viper v1.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.11.1
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
40
go.sum
40
go.sum
@@ -1,20 +1,56 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
131
internal/config/config.go
Normal file
131
internal/config/config.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config holds the application's configuration.
|
||||
type Config struct {
|
||||
Auth struct {
|
||||
Email string `yaml:"email"`
|
||||
Domain string `yaml:"domain"`
|
||||
Session string `yaml:"session_file"`
|
||||
} `yaml:"auth"`
|
||||
|
||||
Output struct {
|
||||
Format string `yaml:"format"`
|
||||
File string `yaml:"file"`
|
||||
} `yaml:"output"`
|
||||
|
||||
Cache struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
TTL time.Duration `yaml:"ttl"`
|
||||
Dir string `yaml:"dir"`
|
||||
} `yaml:"cache"`
|
||||
}
|
||||
|
||||
// DefaultConfig returns a new Config with default values.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Auth: struct {
|
||||
Email string `yaml:"email"`
|
||||
Domain string `yaml:"domain"`
|
||||
Session string `yaml:"session_file"`
|
||||
}{
|
||||
Domain: "garmin.com",
|
||||
Session: filepath.Join(UserConfigDir(), "session.json"),
|
||||
},
|
||||
Output: struct {
|
||||
Format string `yaml:"format"`
|
||||
File string `yaml:"file"`
|
||||
}{
|
||||
Format: "table",
|
||||
},
|
||||
Cache: struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
TTL time.Duration `yaml:"ttl"`
|
||||
Dir string `yaml:"dir"`
|
||||
}{
|
||||
Enabled: true,
|
||||
TTL: 24 * time.Hour,
|
||||
Dir: filepath.Join(UserCacheDir(), "cache"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig loads configuration from the specified path.
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
config := DefaultConfig()
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return config, nil // Return default config if file doesn't exist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveConfig saves the configuration to the specified path.
|
||||
func SaveConfig(path string, config *Config) error {
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(path), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, data, 0600)
|
||||
}
|
||||
|
||||
// InitConfig ensures the config directory and default config file exist.
|
||||
func InitConfig(path string) (*Config, error) {
|
||||
config := DefaultConfig()
|
||||
|
||||
// Ensure config directory exists
|
||||
configDir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if config file exists, if not, create it with default values
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err := SaveConfig(path, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return LoadConfig(path)
|
||||
}
|
||||
|
||||
// UserConfigDir returns the user's configuration directory for garth.
|
||||
func UserConfigDir() string {
|
||||
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||
return filepath.Join(xdgConfigHome, "garth")
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".config", "garth")
|
||||
}
|
||||
|
||||
// UserCacheDir returns the user's cache directory for garth.
|
||||
func UserCacheDir() string {
|
||||
if xdgCacheHome := os.Getenv("XDG_CACHE_HOME"); xdgCacheHome != "" {
|
||||
return filepath.Join(xdgCacheHome, "garth")
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".cache", "garth")
|
||||
}
|
||||
Reference in New Issue
Block a user