fml/internal/modlist/modlist.go
Lilian Jónsdóttir 5f7df27d76 add cli flag to remove mods without zip files from the list
also refactor modlist a bit
  - modlist knows about its own directory
  - AddModsNotInList and RemoveModsNotInFolder use said directory instead of taking one as an argument
2024-02-12 10:20:15 -08:00

221 lines
4.8 KiB
Go

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
}