/* Copyright © 2024 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "fmt" "os" "path/filepath" "strconv" "strings" "github.com/knadh/koanf/parsers/toml" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/posflag" "github.com/knadh/koanf/v2" flag "github.com/spf13/pflag" ) const ( VERSION string = "0.0.1" Usage string = `This is kage-viewer, a shader viewer. Usage: kage-viewer [-vd] [-c ] [-g geom] [-p geom] \ -i -i -s Options: -c --config Config file to use (optional) -i --image Image to load (multiple times allowed, up to 4) -s --shader Shader to run -g --geometry Window size -p --position Position of image0 -d --debug Show debugging output -v --version Show program version ` ) type Config struct { Showversion bool `koanf:"version"` // -v Debug bool `koanf:"debug"` // -d Config string `koanf:"config"` // -c Image []string `koanf:"image"` // -i Shader string `koanf:"shader"` // -s Geo string `koanf:"geometry"` // -g Posision string `koanf:"position"` // -p X, Y, Width, Height int // feed from -g + -p } func InitConfig() (*Config, error) { var kloader = koanf.New(".") // Load default values using the confmap provider. /* not needed yet if err := kloader.Load(confmap.Provider(map[string]interface{}{ }, "."), nil); err != nil { return nil, fmt.Errorf("failed to load default values into koanf: %w", err) } */ // setup custom usage flagset := flag.NewFlagSet("config", flag.ContinueOnError) flagset.Usage = func() { fmt.Println(Usage) os.Exit(0) } // parse commandline flags flagset.BoolP("version", "v", false, "show program version") flagset.BoolP("debug", "d", false, "enable debug output") flagset.StringP("config", "c", "", "config file") flagset.StringP("geometry", "g", "256x256", "window geometry") flagset.StringP("position", "p", "0x0", "position of shader") flagset.StringArrayP("image", "i", nil, "image file") flagset.StringP("shader", "s", "", "shader file") if err := flagset.Parse(os.Args[1:]); err != nil { return nil, fmt.Errorf("failed to parse program arguments: %w", err) } // generate a list of config files to try to load, including the // one provided via -c, if any var configfiles []string configfile, _ := flagset.GetString("config") home, _ := os.UserHomeDir() if configfile != "" { configfiles = []string{configfile} } else { configfiles = []string{ "/etc/kage-viewer.conf", "/usr/local/etc/kage-viewer.conf", // unix variants filepath.Join(home, ".config", "kage-viewer", "config"), filepath.Join(home, ".kage-viewer"), "kage-viewer.conf", } } // Load the config file[s] for _, cfgfile := range configfiles { if path, err := os.Stat(cfgfile); !os.IsNotExist(err) { if !path.IsDir() { if err := kloader.Load(file.Provider(cfgfile), toml.Parser()); err != nil { return nil, fmt.Errorf("error loading config file: %w", err) } } } // else: we ignore the file if it doesn't exists } // command line setup if err := kloader.Load(posflag.Provider(flagset, ".", kloader), nil); err != nil { return nil, fmt.Errorf("error loading flags: %w", err) } // fetch values conf := &Config{} if err := kloader.Unmarshal("", &conf); err != nil { return nil, fmt.Errorf("error unmarshalling: %w", err) } if err := SanitiyCheck(conf); err != nil { return nil, err } return conf, nil } func SanitiyCheck(conf *Config) error { if len(conf.Image) < 1 { return fmt.Errorf("at least 1 image must be specified") } if conf.Shader == "" { return fmt.Errorf("shader file must be specified") } geoerr := "window geometry must be specified as NUMBERxNUMBER, e.g. 32x32" geo := strings.Split(conf.Geo, "x") if len(geo) != 2 { return fmt.Errorf(geoerr) } w, errw := strconv.Atoi(geo[0]) h, errh := strconv.Atoi(geo[1]) if errw != nil || errh != nil { return fmt.Errorf(geoerr) } conf.Width = w conf.Height = h poserr := "shader position must be specified as NUMBERxNUMBER, e.g. 32x32" pos := strings.Split(conf.Posision, "x") if len(geo) != 2 { return fmt.Errorf(poserr) } x, errx := strconv.Atoi(pos[0]) y, erry := strconv.Atoi(pos[1]) if errx != nil || erry != nil { return fmt.Errorf(poserr) } conf.X = x conf.Y = y return nil } func fileExists(filename string) bool { info, err := os.Stat(filename) if os.IsNotExist(err) { return false } return !info.IsDir() }