package main import ( "os" "path/filepath" "runtime" "strings" ) type Config struct { Streams map[string]string // stream in the form stream::name=url DataDir string // location of templates/ and static/ Name string // name of this instance ListenAddress string // address the webserver listens on } func GetConfigLocation() string { home := os.Getenv("HOME") appdata := os.Getenv("APPDATA") switch runtime.GOOS { case "windows": return filepath.Join(appdata, "grimoire") case "darwin": return filepath.Join(home, "Library", "Application Support", "grimoire") case "plan9": return filepath.Join(home, "lib", "grimoire") default: return filepath.Join(home, ".config", "grimoire") } } 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(), "grimoire.conf")) } func (self *Config) Write() error { ensureConfigLocationExists() return writeConfig(self, filepath.Join(GetConfigLocation(), "grimoire.conf")) } func writeConfig(cfg *Config, configFile string) error { f, err := os.Create(configFile) if err != nil { return err } defer f.Close() f.WriteString("listenAddress=" + cfg.ListenAddress + "\n") f.WriteString("dataDir=" + cfg.DataDir + "\n") f.WriteString("name=" + cfg.Name + "\n") for k, v := range cfg.Streams { f.WriteString("stream::" + k + "=" + v) } return nil } func parseConfig(configFile string) *Config { f, err := os.ReadFile(configFile) cfg := &Config{} cfg.Streams = make(map[string]string) 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 "listenAddress": cfg.ListenAddress = v case "dataDir": cfg.DataDir = v case "name": cfg.Name = v default: if strings.HasPrefix(k, "stream::") { stream := strings.Split(k, "stream::")[1] if len(stream) == 0 { panic("stream in config has no name!") } cfg.Streams[stream] = v } else { panic("Unrecognized config option: " + k) } } } return cfg }