package config import ( "fmt" "os" "path/filepath" "runtime" "strconv" "strings" ) type Config struct { Port int Uploads string UploadMaxMB int MongoURI string } func GetConfigLocation() string { home := os.Getenv("HOME") appdata := os.Getenv("APPDATA") switch runtime.GOOS { case "windows": return filepath.Join(appdata, "felt") case "darwin": return filepath.Join(home, "Library", "Application Support", "felt") case "plan9": return filepath.Join(home, "lib", "felt") default: return filepath.Join(home, ".config", "felt") } } 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(), "felt.conf")) } func (self *Config) Write() error { ensureConfigLocationExists() return writeConfig(self, filepath.Join(GetConfigLocation(), "felt.conf")) } func (self *Config) IsNull() bool { return self.Port == 0 || self.UploadMaxMB == 0 || self.Uploads == "" } 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("MongoDB URI? ") ensureNonEmptyOption(&inputBuf) self.MongoURI = inputBuf inputBuf = "" fmt.Printf("TCP port? ") self.Port = ensurePortOption(&inputBuf) fmt.Printf("file uploads location? ") ensureNonEmptyOption(&inputBuf) self.Uploads = inputBuf inputBuf = "" fmt.Printf("Max file upload size (MB)? ") self.UploadMaxMB = 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) int { for { fmt.Scanln(buffer) trimmedBuf := strings.TrimSpace(*buffer) v, err := strconv.ParseInt(trimmedBuf, 10, 32) if err == nil && v > 0 { return int(v) } fmt.Println("Please enter a positive integer") } } func ensurePortOption(buffer *string) int { for { fmt.Scanln(buffer) trimmedBuf := strings.TrimSpace(*buffer) v, err := strconv.ParseInt(trimmedBuf, 10, 32) if err == nil && v > 0 && v <= 65536 { return int(v) } fmt.Println("Please enter a valid port [1, 65536]") } } func writeConfig(cfg *Config, configFile string) error { f, err := os.Create(configFile) if err != nil { return err } defer f.Close() f.WriteString("mongoURI=" + cfg.MongoURI + "\n") f.WriteString("uploads=" + cfg.Uploads + "\n") f.WriteString("uploadMaxMB=" + strconv.FormatInt(int64(cfg.UploadMaxMB), 10) + "\n") f.WriteString("port=" + strconv.FormatInt(int64(cfg.Port), 10) + "\n") return nil } func parseConfig(configFile string) *Config { f, err := os.ReadFile(configFile) cfg := &Config{} if err != nil { 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 "mongoURI": cfg.MongoURI = v case "uploads": cfg.Uploads = v case "port": port, _ := strconv.ParseInt(v, 10, 32) cfg.Port = int(port) case "uploadMaxMB": maxUpload, _ := strconv.ParseInt(v, 10, 32) cfg.UploadMaxMB = int(maxUpload) default: panic("Unrecognized config option: " + k) } } return cfg }