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.
master
joce 3 years ago
parent 60cf8cc1bf
commit 62ed00ed4e
  1. 28
      cmd/mop/main.go
  2. 2
      go.mod
  3. 4
      go.sum
  4. 65
      profile.go

@ -6,10 +6,14 @@ package main
import ( import (
"flag" "flag"
"fmt"
"os"
"os/user" "os/user"
"path" "path"
"strings"
"time" "time"
"github.com/eiannone/keyboard"
"github.com/mop-tracker/mop" "github.com/mop-tracker/mop"
"github.com/nsf/termbox-go" "github.com/nsf/termbox-go"
) )
@ -138,7 +142,29 @@ func main() {
profileName := flag.String("profile", path.Join(usr.HomeDir, defaultProfile), "path to profile") profileName := flag.String("profile", path.Join(usr.HomeDir, defaultProfile), "path to profile")
flag.Parse() 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) screen := mop.NewScreen(profile)
defer screen.Close() defer screen.Close()

@ -4,6 +4,8 @@ go 1.15
require ( require (
github.com/Knetic/govaluate v3.0.0+incompatible 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/mattn/go-runewidth v0.0.13 // indirect
github.com/nsf/termbox-go v1.1.1 github.com/nsf/termbox-go v1.1.1
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
) )

@ -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 h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 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 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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 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/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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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=

@ -44,6 +44,7 @@ type Profile struct {
filename string // Path to the file in which the configuration is stored 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 { func IsSupportedColor(colorName string) bool {
switch colorName { switch colorName {
case case
@ -70,41 +71,51 @@ func IsSupportedColor(colorName string) bool {
// Creates the profile and attempts to load the settings from ~/.moprc file. // Creates the profile and attempts to load the settings from ~/.moprc file.
// If the file is not there it gets created with default values. // 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} profile := &Profile{filename: filename}
data, err := ioutil.ReadFile(filename) data, err := ioutil.ReadFile(filename)
if err != nil { // Set default values: if err == nil {
profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute). err = json.Unmarshal(data, profile)
profile.QuotesRefresh = 5 // Stock quotes get updated every 5s (12 times per minute).
profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining. if err == nil {
profile.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} InitColor(&profile.Colors.Gain, defaultGainColor)
profile.SortColumn = 0 // Stock quotes are sorted by ticker name. InitColor(&profile.Colors.Loss, defaultLossColor)
profile.Ascending = true // A to Z. InitColor(&profile.Colors.Tag, defaultTagColor)
profile.Filter = "" InitColor(&profile.Colors.Header, defaultHeaderColor)
profile.Colors.Gain = defaultGainColor InitColor(&profile.Colors.Time, defaultTimeColor)
profile.Colors.Loss = defaultLossColor InitColor(&profile.Colors.Default, defaultColor)
profile.Colors.Tag = defaultTagColor
profile.Colors.Header = defaultHeaderColor profile.SetFilter(profile.Filter)
profile.Colors.Time = defaultTimeColor }
profile.Colors.Default = defaultColor
profile.Save()
} else { } else {
json.Unmarshal(data, profile) profile.InitDefaultProfile()
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)
} }
profile.selectedColumn = -1 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) { func InitColor(color *string, defaultValue string) {
*color = strings.ToLower(*color) *color = strings.ToLower(*color)
if !IsSupportedColor(*color) { if !IsSupportedColor(*color) {

Loading…
Cancel
Save