diff --git a/README.md b/README.md index b454cff..7b4ba58 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ program. It's written in Go using the [Bubble Tea][tea] library, and uses ### Configuration By default, the program will use Bitcoin along with various goals and bases -of comparison. With the `--config-file` flag, however, one can specify a TOML -file that overrides these defaults. - -See [this one for Ethereum](./config/eth.toml) for an example. +of comparison. With the `--asset` flag, another asset supported by Coindesk can +be chosen. The [builtin default config](./config/default.yaml) only has special +goals for Ethereum ("ETH"). With the `--config-file` flag, however, one can +specify a YAML file that overrides these defaults and adds goals for other +assets. ### "Theory" diff --git a/config/config.go b/config/config.go index dbf4ea0..acb067c 100644 --- a/config/config.go +++ b/config/config.go @@ -1,31 +1,33 @@ package config import ( + _ "embed" "slices" "time" "code.humancabbage.net/sam/moonmath/coindesk" "code.humancabbage.net/sam/moonmath/moon" - "github.com/knadh/koanf/parsers/toml" + "github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/providers/rawbytes" "github.com/knadh/koanf/providers/structs" "github.com/knadh/koanf/v2" ) var k = koanf.New(".") -func Load(path string) (data Data, err error) { +func Load(path string) (all All, err error) { err = k.Load(structs.Provider(Default, "koanf"), nil) if err != nil { return } if path != "" { - err = k.Load(file.Provider(path), toml.Parser()) + err = k.Load(file.Provider(path), yaml.Parser()) if err != nil { return } } - err = k.Unmarshal("", &data) + err = k.Unmarshal("", &all) if err != nil { return } @@ -33,11 +35,11 @@ func Load(path string) (data Data, err error) { return } -var Default = Data{ - Asset: coindesk.BTC, - Goals: moon.DefaultGoals, - ConstantBases: moon.DefaultConstantBases, - RelativeBases: moon.DefaultRelativeBases, +var Default All + +type All struct { + Defaults Data `koanf:"defaults"` + Assets map[coindesk.Asset]Data `koanf:"assets"` } type Data struct { @@ -47,9 +49,24 @@ type Data struct { RelativeBases []moon.RelativeBase `koanf:"relativeBases"` } -type Goal struct { - Name string `koanf:"name"` - Value float64 `koanf:"value"` +func (all All) GetData(asset coindesk.Asset) (data Data, err error) { + data, ok := all.Assets[asset] + if !ok { + data = all.Defaults + } + if data.Asset == "" { + data.Asset = asset + } + if data.Goals == nil || len(data.Goals) == 0 { + data.Goals = all.Defaults.Goals + } + if data.ConstantBases == nil || len(data.ConstantBases) == 0 { + data.ConstantBases = all.Defaults.ConstantBases + } + if data.RelativeBases == nil || len(data.RelativeBases) == 0 { + data.RelativeBases = all.Defaults.RelativeBases + } + return } // GetBases returns the concatenation of the constant and relative bases, sorted @@ -77,3 +94,18 @@ func GetBases(d *Data) (bases []moon.Base) { }) return } + +//go:embed default.yaml +var defaultYamlBytes []byte + +func init() { + var k = koanf.New(".") + err := k.Load(rawbytes.Provider(defaultYamlBytes), yaml.Parser()) + if err != nil { + panic(err) + } + err = k.Unmarshal("", &Default) + if err != nil { + panic(err) + } +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..47af0c6 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,10 @@ +package config_test + +import ( + "testing" +) + +func TestDefault(t *testing.T) { + // config.Default is parsed in config.init(), which panics on error. + // so just getting this far means that at least it parsed successfully. +} diff --git a/config/default.yaml b/config/default.yaml new file mode 100644 index 0000000..27f42a6 --- /dev/null +++ b/config/default.yaml @@ -0,0 +1,46 @@ +defaults: + goals: + - name: $100k + value: 100000 + - name: $150k + value: 150000 + - name: $200k + value: 200000 + - name: $250k + value: 250000 + - name: $300k + value: 300000 + - name: $500k + value: 500000 + - name: $1m + value: 1000000 + relativeBases: + - name: Month + offset: -720h0m0s + - name: Quarter + offset: -2160h0m0s + - name: Half-Year + offset: -4368h0m0s + - name: Year + offset: -8760h0m0s + constantBases: + - name: 2020- + time: 2019-12-31T16:00:00-08:00 + - name: 2017- + time: 2016-12-31T16:00:00-08:00 + +assets: + ETH: + goals: + - name: $5k + value: 5000 + - name: $7.5k + value: 7500 + - name: $10k + value: 10000 + - name: $15k + value: 15000 + - name: $20k + value: 20000 + - name: $25k + value: 25000 diff --git a/config/eth.toml b/config/eth.toml deleted file mode 100644 index a355948..0000000 --- a/config/eth.toml +++ /dev/null @@ -1,25 +0,0 @@ -asset = "ETH" - -[[goals]] -name = "$5k" -value = 5000 - -[[goals]] -name = "$7.5k" -value = 7500 - -[[goals]] -name = "$10k" -value = 10000 - -[[goals]] -name = "$15k" -value = 15000 - -[[goals]] -name = "$20k" -value = 15000 - -[[goals]] -name = "$25k" -value = 25000 diff --git a/go.mod b/go.mod index 88862ca..30e1d9e 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,9 @@ require ( github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/lipgloss v0.10.0 - github.com/knadh/koanf/parsers/toml v0.1.0 + github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/providers/file v0.1.0 + github.com/knadh/koanf/providers/rawbytes v0.1.0 github.com/knadh/koanf/providers/structs v0.1.0 github.com/knadh/koanf/v2 v2.1.0 github.com/sourcegraph/conc v0.3.0 @@ -23,6 +24,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -33,7 +35,6 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect github.com/rivo/uniseg v0.4.7 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.9.0 // indirect @@ -41,4 +42,5 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index cf5ba16..fd310e9 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,7 @@ github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMt github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -29,14 +30,20 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= -github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI= -github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18= +github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= +github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c= github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA= +github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= +github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= github.com/knadh/koanf/providers/structs v0.1.0 h1:wJRteCNn1qvLtE5h8KQBvLJovidSdntfdyIbbCzEyE0= github.com/knadh/koanf/providers/structs v0.1.0/go.mod h1:sw2YZ3txUcqA3Z27gPlmmBzWn1h8Nt9O6EP/91MkcWE= github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8= github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -58,14 +65,14 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +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/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -88,5 +95,8 @@ golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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= diff --git a/moonmath.go b/moonmath.go index c1c91b1..4029935 100644 --- a/moonmath.go +++ b/moonmath.go @@ -3,14 +3,17 @@ package main import ( "fmt" "os" + "strings" + "code.humancabbage.net/sam/moonmath/coindesk" "code.humancabbage.net/sam/moonmath/config" "code.humancabbage.net/sam/moonmath/tui" "github.com/alecthomas/kong" ) var CLI struct { - ConfigFile string `help:"Path to TOML configuration file."` + Asset string `short:"a" default:"BTC" help:"Asset to project."` + ConfigFile string `short:"c" help:"Path to YAML configuration file."` } func main() { @@ -18,7 +21,12 @@ func main() { if ctx.Error != nil { fail(ctx.Error) } - cfg, err := config.Load(CLI.ConfigFile) + allCfg, err := config.Load(CLI.ConfigFile) + if err != nil { + fail(err) + } + CLI.Asset = strings.ToUpper(CLI.Asset) + cfg, err := allCfg.GetData(coindesk.Asset(CLI.Asset)) if err != nil { fail(err) }