From 5579498720c5c1e0cdf31f97f7ad31dfe9dbf0aa Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Fri, 17 Oct 2025 01:38:38 -0500 Subject: tweaks on config load. more smarters than befores --- README.md | 25 +++++++++++++++------ load.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++------------- save.go | 5 +++++ 3 files changed, 84 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 29264c2..67dbb17 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,26 @@ // Copyright 2025 WIT.COM Inc Licensed GPL 3.0 common config file handling for protobuf defined config files +intended to be super simple so the code you need to write is simple. -By default, the config files are stored as: +Enables Load functions: -~/.config//.text +// loads ~/.config/myapp/trees.text +cfg := new(MyPB) +err := config.ConfigLoad(cfg, "myapp", "trees") -assumes config files are simple, intended to be edited by hand -by the user and can be exported by protobuf FormatTEXT() +Enables Save functions: -intended to be called by functions that are automatically -generated by 'autogenpb' for protobuf defined config files. +err := cfg.Save() // it automatically knows where to save -If you aren't using .proto defined config files, this package is not for you +### Errors #### + +if errors.Is(err, config.VersionMismatch) { + // protobuf structure changed +} +if errors.Is(err, config.ErrEmpty) { + // config file was empty +} +if errors.Is(err, config.ErrNotExist) { + // config file didn't exist (yes, this is the os.ExistErr) +} diff --git a/load.go b/load.go index 48b6d4e..fc9a5a0 100644 --- a/load.go +++ b/load.go @@ -10,14 +10,15 @@ import ( "path/filepath" "strings" + "go.wit.com/log" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" ) /* -// loads a file from ~/.config// -func Load(argname string) ([]byte, string) { +// loads a file from ~/.config// +func Load(appname string) ([]byte, string) { } */ @@ -25,20 +26,30 @@ var ErrEmpty error = fmt.Errorf("config file was empty") var ErrMarshal error = fmt.Errorf("protobuf parse error") // returns: -// - Full path to the config file. usually: ~/.config/ +// - Full path to the config file. usually: ~/.config/ // - []byte : the contents of the file // - error on read -func ConfigLoad(pb proto.Message, argname string, protoname string) error { - var fullname string +func ConfigLoad(pb proto.Message, appname string, protoname string) error { + // Get ~/.config/appname/protoname.text + fullname := GetConfigFilename(appname, protoname) + + var pbFilenameSupport bool var err error - configDir, err := os.UserConfigDir() + curfilename, err := GetFilename(pb) if err != nil { - return err + log.Info("This protobuf doesn't support pb.Filename") + // make note this protobuf doesn't support Filenames + } else { + pbFilenameSupport = true } + // panic(curfilename) - fullname = filepath.Join(configDir, argname, protoname+".text") - SetFilename(pb, fullname) - + // potential syntax with this GO library is starting to look like: + // + // if errors.Is(err, config.ErrEmpty) + // + // if errors.Is(err, config.VersionMismatch) + // // if both don't exist or both are empty, return known errors // these can be used to detect if the user is new to the application if err := missingConfig(fullname); err != nil { @@ -52,11 +63,33 @@ func ConfigLoad(pb proto.Message, argname string, protoname string) error { } if err = loadTEXT(pb, fullname); err == nil { + if pbFilenameSupport { + // If the config is old or broken, this sets the filename + if curfilename != fullname { + _, err := SetFilename(pb, fullname) + if err != nil { + log.Info("FILENAME COULD NOT BE SET old=", curfilename) + log.Info("FILENAME COULD NOT BE SET new=", fullname) + panic("something is wrong in lib/config") + } + } + } return nil } else { if strings.HasSuffix(fullname, ".text") { fulljson := fullname + ".json" + // If the config is old or broken, this sets the filename if err := loadJSON(pb, fulljson); err == nil { + if pbFilenameSupport { + if curfilename != fullname { + _, err := SetFilename(pb, fullname) + if err != nil { + log.Info("FILENAME COULD NOT BE SET old=", curfilename) + log.Info("FILENAME COULD NOT BE SET new=", fullname) + panic("something is wrong in lib/config") + } + } + } return nil } } @@ -64,14 +97,26 @@ func ConfigLoad(pb proto.Message, argname string, protoname string) error { return ErrMarshal } +// returns the default constructed filename: +// ~/.config/appname/protoname.text +func GetConfigFilename(appname string, protoname string) string { + var err error + configDir, err := os.UserConfigDir() + if err != nil { + // todo: get something better than /tmp/ if anyone cares + return filepath.Join("/tmp", appname, protoname+".text") + } + return filepath.Join(configDir, appname, protoname+".text") +} + // loads from the users .cache dir // if the .proto file version changes, automatically delete the .pb // file. This is important to avoid marshalling garbage data // .cache files are treated as such, a "cache" file. don't keep important // things in here. argv stores the information here for autodelete -func LoadCache(pb proto.Message, argname string, protoname string) error { +func LoadCache(pb proto.Message, appname string, protoname string) error { cacheDir, _ := os.UserCacheDir() - fullpath := filepath.Join(cacheDir, argname) + fullpath := filepath.Join(cacheDir, appname) os.MkdirAll(fullpath, os.ModePerm) fullname := filepath.Join(fullpath, protoname+".pb") _, err := SetFilename(pb, fullname) @@ -198,17 +243,17 @@ func loadPB(pb proto.Message, fullname string) error { return nil } -func LoadConfigPB(pb proto.Message, argname string, protoname string) (string, error) { +func LoadConfigPB(pb proto.Message, appname string, protoname string) (string, error) { var fullname string - if strings.HasPrefix(argname, "/") { - fullname = filepath.Join(argname, protoname+".pb") + if strings.HasPrefix(appname, "/") { + fullname = filepath.Join(appname, protoname+".pb") } else { configDir, err := os.UserConfigDir() if err != nil { return "", err } - fullname = filepath.Join(configDir, argname, protoname+".pb") + fullname = filepath.Join(configDir, appname, protoname+".pb") } data, err := loadFile(fullname) diff --git a/save.go b/save.go index f926a31..d257649 100644 --- a/save.go +++ b/save.go @@ -94,6 +94,11 @@ func saveTEXT(pb proto.Message, header string) error { if err != nil { return err } + fullname = strings.TrimSpace(fullname) + if fullname == "" { + return fmt.Errorf("saveTEXT() pb.Filename was blank") + } + if !strings.HasSuffix(fullname, ".text") { // todo: append .text here? return fmt.Errorf("not .text file: %s", fullname) -- cgit v1.2.3