package main import ( "fmt" "os" "path/filepath" "runtime" "strconv" "strings" ) func GetConfigLocation() string { home := os.Getenv("HOME") appdata := os.Getenv("APPDATA") switch runtime.GOOS { case "windows": return filepath.Join(appdata, "raven") case "darwin": return filepath.Join(home, "Library", "Application Support", "raven") case "plan9": return filepath.Join(home, "lib", "raven") default: return filepath.Join(home, ".config", "raven") } } func ensureConfigLocationExists() { fileInfo, err := os.Stat(GetConfigLocation()) if os.IsNotExist(err) { os.MkdirAll(GetConfigLocation(), os.ModePerm) } else if !fileInfo.IsDir() { panic("Config location is not a directory!") } } func ReadConfig() *Config { ensureConfigLocationExists() return parseConfig(filepath.Join(GetConfigLocation(), "raven.conf")) } func (self *Config) Write() error { ensureConfigLocationExists() return writeConfig(self, filepath.Join(GetConfigLocation(), "raven.conf")) } func (self *Config) IsNull() bool { // zero-values for StaticShowHTML, StaticShowHidden, and StaticMaxUploadMB are valid return len(self.Nick) == 0 || len(self.FeedFile) == 0 || len(self.FriendsFile) == 0 } func (self *Config) RunWizard() { fmt.Printf("All options are required.\n") defer func(cfg *Config) { if r := recover(); r != nil { fmt.Printf("Invalid selection, starting over...") cfg.RunWizard() } }(self) inputBuf := "" fmt.Printf("your nickname? ") ensureNonEmptyOption(&inputBuf) self.Nick = inputBuf inputBuf = "" fmt.Printf("your twtxt file location? ") ensureNonEmptyOption(&inputBuf) self.FeedFile = inputBuf inputBuf = "" fmt.Printf("your feed in ascending order? ") self.FeedAscend = ensureBooleanOption(&inputBuf) inputBuf = "" fmt.Printf("file location containing your friends' feeds? ") ensureNonEmptyOption(&inputBuf) self.FriendsFile = inputBuf inputBuf = "" fmt.Printf("number of posts to store in memory? ") self.MaxPosts = ensureNumberOption(&inputBuf) fmt.Printf("Configuration complete!\n") self.Write() } func ensureNonEmptyOption(buffer *string) { for { fmt.Scanln(buffer) if len(strings.TrimSpace(*buffer)) != 0 { break } fmt.Println("Please enter a nonempty value") } } func ensureBooleanOption(buffer *string) bool { for { fmt.Scanln(buffer) trimmedBuf := strings.TrimSpace(*buffer) v, err := strconv.ParseBool(trimmedBuf) if err == nil { return v } fmt.Println("Please enter a true or false value") } } func ensureNumberOption(buffer *string) int64 { for { fmt.Scanln(buffer) trimmedBuf := strings.TrimSpace(*buffer) v, err := strconv.ParseInt(trimmedBuf, 10, 64) if err == nil && v >= 0 { return v } fmt.Println("Please enter a nonnegative integer") } } func writeConfig(cfg *Config, configFile string) error { f, err := os.Create(configFile) if err != nil { return err } defer f.Close() f.WriteString("nick=" + cfg.Nick + "\n") f.WriteString("feedfile=" + cfg.FeedFile + "\n") f.WriteString("feed_ascend=" + strconv.FormatBool(cfg.FeedAscend) + "\n") f.WriteString("friendsfile=" + cfg.FriendsFile + "\n") f.WriteString("max_posts=" + strconv.FormatInt(cfg.MaxPosts, 10) + "\n") return nil } func parseConfig(configFile string) *Config { f, err := os.ReadFile(configFile) cfg := &Config{} if err != nil { fmt.Println(err.Error()) return cfg } fileData := string(f[:]) lines := strings.Split(fileData, "\n") for _, l := range lines { if len(l) == 0 { continue } if !strings.Contains(l, "=") { panic("Malformed config not in INI format") } kvp := strings.Split(l, "=") k := strings.TrimSpace(kvp[0]) v := strings.TrimSpace(kvp[1]) switch k { case "feedfile": cfg.FeedFile = v case "feed_ascend": cfg.FeedAscend, _ = strconv.ParseBool(v) case "friendsfile": cfg.FriendsFile = v case "max_posts": cfg.MaxPosts, _ = strconv.ParseInt(v, 10, 64) case "nick": cfg.Nick = v default: panic("Unrecognized config option: " + k) } } return cfg }