From 62ed00ed4e08911c2e8f80acdba8f3b3cc87d35d Mon Sep 17 00:00:00 2001 From: joce Date: Sun, 20 Mar 2022 17:12:16 -0400 Subject: [PATCH] Fix for #97: loading invalid config wipes file Attempting to load an invalid config file will wipe the existing config, leaving the user with an empty config. This change adds a check when an invalid file is loaded, and asks the user if the invalid config file should be overwritten. If the user chooses `yes`, then the default config is loaded instead, and the existing config will be overwritten with it. If the user chooses `no`, then the program exits. Note this is intended only for the "interactive" mode (the only one existing right now). If and when we go forward with the suggestion in from issue #88, there should be no prompt, just an error message on stderr. --- cmd/mop/main.go | 28 ++++++++++++++++++++- go.mod | 2 ++ go.sum | 4 +++ profile.go | 65 +++++++++++++++++++++++++++++-------------------- 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index 2418a52..0e7e8e8 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -6,10 +6,14 @@ package main import ( "flag" + "fmt" + "os" "os/user" "path" + "strings" "time" + "github.com/eiannone/keyboard" "github.com/mop-tracker/mop" "github.com/nsf/termbox-go" ) @@ -138,7 +142,29 @@ func main() { profileName := flag.String("profile", path.Join(usr.HomeDir, defaultProfile), "path to profile") flag.Parse() - profile := mop.NewProfile(*profileName) + profile, err := mop.NewProfile(*profileName) + if err != nil { + fmt.Fprintf(os.Stderr, "The profile read from `%s` is corrupted.\n\tError: %s\n\n", *profileName, err) + + // Loop until we get a "y" or "n" answer. + // Note: This is only for the interactive mode. Once we have the "one-shot", this should be skipped + for { + fmt.Fprintln(os.Stderr, "Do you want to overwrite the current profile with the default one? [y/n]") + rne, _, _ := keyboard.GetSingleKey() + res := strings.ToLower(string(rne)) + if res != "y" && res != "n" { + fmt.Fprintf(os.Stderr, "Invalid answer `%s`\n\n", res) + continue + } + + if res == "y" { + profile.InitDefaultProfile() + break + } else { + os.Exit(1) + } + } + } screen := mop.NewScreen(profile) defer screen.Close() diff --git a/go.mod b/go.mod index b08be4e..8fb4af4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.15 require ( github.com/Knetic/govaluate v3.0.0+incompatible + github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807 github.com/mattn/go-runewidth v0.0.13 // indirect github.com/nsf/termbox-go v1.1.1 + golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect ) diff --git a/go.sum b/go.sum index ffcb180..13a636e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807 h1:jdjd5e68T4R/j4PWxfZqcKY8KtT9oo8IPNVuV4bSXDQ= +github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807/go.mod h1:Xoiu5VdKMvbRgHuY7+z64lhu/7lvax/22nzASF6GrO8= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= @@ -10,3 +12,5 @@ github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/profile.go b/profile.go index d6a3779..93bbfaa 100644 --- a/profile.go +++ b/profile.go @@ -44,6 +44,7 @@ type Profile struct { filename string // Path to the file in which the configuration is stored } +// Checks if a string represents a supported color or not. func IsSupportedColor(colorName string) bool { switch colorName { case @@ -70,41 +71,51 @@ func IsSupportedColor(colorName string) bool { // Creates the profile and attempts to load the settings from ~/.moprc file. // If the file is not there it gets created with default values. -func NewProfile(filename string) *Profile { +func NewProfile(filename string) (*Profile, error) { profile := &Profile{filename: filename} data, err := ioutil.ReadFile(filename) - if err != nil { // Set default values: - profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute). - profile.QuotesRefresh = 5 // Stock quotes get updated every 5s (12 times per minute). - profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining. - profile.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} - profile.SortColumn = 0 // Stock quotes are sorted by ticker name. - profile.Ascending = true // A to Z. - profile.Filter = "" - profile.Colors.Gain = defaultGainColor - profile.Colors.Loss = defaultLossColor - profile.Colors.Tag = defaultTagColor - profile.Colors.Header = defaultHeaderColor - profile.Colors.Time = defaultTimeColor - profile.Colors.Default = defaultColor - profile.Save() + if err == nil { + err = json.Unmarshal(data, profile) + + if err == nil { + InitColor(&profile.Colors.Gain, defaultGainColor) + InitColor(&profile.Colors.Loss, defaultLossColor) + InitColor(&profile.Colors.Tag, defaultTagColor) + InitColor(&profile.Colors.Header, defaultHeaderColor) + InitColor(&profile.Colors.Time, defaultTimeColor) + InitColor(&profile.Colors.Default, defaultColor) + + profile.SetFilter(profile.Filter) + } } else { - json.Unmarshal(data, profile) - - InitColor(&profile.Colors.Gain, defaultGainColor) - InitColor(&profile.Colors.Loss, defaultLossColor) - InitColor(&profile.Colors.Tag, defaultTagColor) - InitColor(&profile.Colors.Header, defaultHeaderColor) - InitColor(&profile.Colors.Time, defaultTimeColor) - InitColor(&profile.Colors.Default, defaultColor) - - profile.SetFilter(profile.Filter) + profile.InitDefaultProfile() + err = nil } profile.selectedColumn = -1 - return profile + return profile, err +} + +// Initializes a profile with the default values +func (profile *Profile) InitDefaultProfile() { + profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute). + profile.QuotesRefresh = 5 // Stock quotes get updated every 5s (12 times per minute). + profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining. + profile.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} + profile.SortColumn = 0 // Stock quotes are sorted by ticker name. + profile.Ascending = true // A to Z. + profile.Filter = "" + profile.Colors.Gain = defaultGainColor + profile.Colors.Loss = defaultLossColor + profile.Colors.Tag = defaultTagColor + profile.Colors.Header = defaultHeaderColor + profile.Colors.Time = defaultTimeColor + profile.Colors.Default = defaultColor + profile.Save() } +// Initializes a color to the given string, or to the default value if the given +// string does not represent a supported color. func InitColor(color *string, defaultValue string) { *color = strings.ToLower(*color) if !IsSupportedColor(*color) {