package modlist import ( "encoding/json" "os" "regexp" "strings" "github.com/charmbracelet/log" ) // fileRegex matches const fileRegex string = "([\\w\\s-]+)_([0-9.]+)\\.zip" /// Modlist types and related functions. // Mod holds data about a single mod. type Mod struct { Name string `json:"name"` Enabled bool `json:"enabled"` } // Modlist holds a slice of mods. type Modlist struct { dir string Mods []Mod `json:"mods"` } /// Non exported functions func (modlist *Modlist) setModStatus(name string, status bool) { for i := range modlist.Mods { mod := &modlist.Mods[i] if mod.Name == name && mod.Enabled != status { mod.Enabled = status log.Debugf("Setting status of mod %s to %v", mod.Name, mod.Enabled) } } } func (modlist *Modlist) setAllStatus(status bool) { for i := range modlist.Mods { mod := &modlist.Mods[i] mod.Enabled = status log.Debugf("Setting status of mod %s to %v", mod.Name, mod.Enabled) } } // EnableMod enables the given mod. func (modlist *Modlist) EnableMod(name string) { modlist.setModStatus(name, true) } // EnableMods enables the given mods. func (modlist *Modlist) EnableMods(names ...string) { for _, name := range names { modlist.EnableMod(name) } } // DisableMod disables the given mod. func (modlist *Modlist) DisableMod(name string) { modlist.setModStatus(name, false) } // DisableMods disables the given mods. func (modlist *Modlist) DisableMods(names ...string) { for _, name := range names { modlist.DisableMod(name) } } // EnableAll enables all mods. func (modlist *Modlist) EnableAll() { modlist.setAllStatus(true) } // DisableAll disables all mods. func (modlist *Modlist) DisableAll() { modlist.setAllStatus(false) } // HasMod reports if a modlist has a mod with name `s` func (modlist *Modlist) HasMod(s string) bool { for _, mod := range modlist.Mods { if mod.Name == s { return true } } return false } // HasMod reports if a modlist has an enabled mod with name `s` func (modlist *Modlist) HasModEnabled(s string) bool { for _, mod := range modlist.Mods { if mod.Name == s && mod.Enabled { return true } } return false } // Print out each mod and its status. func (modlist *Modlist) Print(prefix string) { log.Info(prefix + ": {") for _, mod := range modlist.Mods { var endis string // I wish go had a ternary so this could all be in one line if mod.Enabled { endis = "en" } else { endis = "dis" } log.Infof("\t%s: %sabled,", mod.Name, endis) } log.Info("}") } func (modlist *Modlist) String() { modlist.Print("mods") } // AddModsNotInList finds mod archives in the mod folder that aren't in the modlist, and adds them. func (modlist *Modlist) AddModsNotInList(state bool) error { r := regexp.MustCompile(fileRegex) files, err := os.ReadDir(modlist.dir) if err != nil { return err } for _, file := range files { if strings.HasSuffix(file.Name(), ".zip") && !file.IsDir() { groups := r.FindAllStringSubmatch(file.Name(), -1) name := groups[0][1] version := groups[0][2] if !modlist.HasMod(name) { log.Infof("Found %s not in modlist, adding v%s in disabled state.", name, version) modlist.Mods = append(modlist.Mods, Mod{ Name: name, Enabled: state, }) } } } return nil } // RemoveModsNotInFolder removes mods from the modlist if a corresponding // zip file cannot be found in the mods dir. func (modlist *Modlist) RemoveModsNotInFolder() error { for i, mod := range modlist.Mods { in, err := isModInFolder(mod.Name, modlist.dir) if !in && err == nil { log.Warnf("Zip file for %s could not be found, removing!", mod.Name) // mods.Mods[i] = nil modlist.Mods = append(modlist.Mods[:i], modlist.Mods[i+1:]...) } else if err != nil { return err } } return nil } /// Functions related to the modlist but that shouldn't be attached to the types func isModInFolder(modname, modsdir string) (bool, error) { if modname == "base" { return true, nil } // TODO: try for less O(n) files, err := os.ReadDir(modsdir) if err != nil { return false, err } for _, file := range files { if strings.HasPrefix(file.Name(), modname) { return true, nil } } return false, nil } // WriteToFile writes the modlist to the given filename. func WriteToFile(filename string, mods *Modlist) error { data, err := json.MarshalIndent(&mods, "", " ") if err != nil { return err } os.WriteFile(filename, data, os.FileMode(0744)) log.Infof("Wrote modlist to file %s.", filename) return nil } // ReadFromFile reads the modlist from the given filename. func ReadFromFile(filename, modsdir string) (Modlist, error) { var mods Modlist = Modlist{ dir: modsdir, } data, err := os.ReadFile(filename) if err != nil { return Modlist{}, err } err = json.Unmarshal(data, &mods) if err != nil { return Modlist{}, err } log.Infof("Read modlist from file %s.", filename) return mods, nil }