From fc17dc394aedbc5fd636676202bad40c1f98c1de Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Tue, 14 Oct 2025 00:15:41 -0500 Subject: implement application specific Config files (and Verbose()) --- config.Save.go | 16 ++++++ config.proto | 8 +-- findFilename.go | 165 -------------------------------------------------------- flags.go | 23 ++++++++ init.go | 76 ++++++++++++++++++++++++++ load.go | 18 ++++--- lookupPB.go | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ save.go | 13 +++-- verbose.go | 26 +++++++++ 9 files changed, 332 insertions(+), 178 deletions(-) create mode 100644 config.Save.go delete mode 100644 findFilename.go create mode 100644 flags.go create mode 100644 init.go create mode 100644 lookupPB.go create mode 100644 verbose.go diff --git a/config.Save.go b/config.Save.go new file mode 100644 index 0000000..d7d0bbf --- /dev/null +++ b/config.Save.go @@ -0,0 +1,16 @@ +package config + +import ( + "os" + "path/filepath" +) + +// saves your applications config file +func Save() error { + basedir, _ := filepath.Split(configPB.Filename) + if err := os.MkdirAll(basedir, os.ModePerm); err != nil { + return err + } + err := SavePB(configPB) + return err +} diff --git a/config.proto b/config.proto index b86de8f..2716967 100644 --- a/config.proto +++ b/config.proto @@ -7,14 +7,16 @@ package config; import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp message Config { // - string name = 1; // a map for what thing? - map vals = 2; // a simple map + string key = 1; // config key name `autogenpb:unique` `autogenpb:sort` + string value = 2; // config value name google.protobuf.Timestamp ctime = 3; // create time of the patch + map vals = 4; // a simple map } message Configs { // `autogenpb:marshal` `autogenpb:nomutex` string uuid = 1; // `autogenpb:uuid:3135d0f9-82a9-40b6-8aa1-b683ebe7bedd` - string version = 2; // `autogenpb:version:v0.0.1 go.wit.com/lib/config` + string version = 2; // `autogenpb:version:v0.0.2 go.wit.com/lib/config` repeated Config configs = 3; string filename = 4; // can store where the filename is so that saves can be automated + map flags = 5; // a simple map } diff --git a/findFilename.go b/findFilename.go deleted file mode 100644 index 9fb73a2..0000000 --- a/findFilename.go +++ /dev/null @@ -1,165 +0,0 @@ -package config - -import ( - "errors" - "fmt" - - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" -) - -var ErrProtoNoVarName error = errors.New("name not in .proto") -var ErrProtoVarNotString error = errors.New("name exists but is not a string") - -// Gemini AI can help author some pretty good protobuf code. -// I never remember the syntax for 'reflect' on these things. - -// sets "Filename" if it exists in the protobuf -func SetFilename(pb proto.Message, filename string) (bool, error) { - msg := pb.ProtoReflect() // This is the entry point to the reflection API. - - descriptor := msg.Descriptor() // Get the message's descriptor, which contains metadata about its fields. - - fieldName := protoreflect.Name("Filename") - fieldDescriptor := descriptor.Fields().ByName(fieldName) - - if fieldDescriptor == nil { - fieldName = protoreflect.Name("filename") - fieldDescriptor = descriptor.Fields().ByName(fieldName) - } - - if fieldDescriptor == nil { - return false, fmt.Errorf("fieldDescriptor == nil") - } - - if fieldDescriptor.Kind() != protoreflect.StringKind { - // The field exists but is not a string, so we can't return it as one. - return false, fmt.Errorf("The field exists but is not a string") - } - - valueToSet := protoreflect.ValueOfString(filename) - - // 6. If the field exists and is a string, get its value. - // The value is returned as a protoreflect.Value. - msg.Set(fieldDescriptor, valueToSet) - - // 7. Convert the protoreflect.Value to a native Go string. - return true, nil -} - -// this will try both "filename" and "Filename" -func GetFilename(pb proto.Message) (string, error) { - // 1. Get the protoreflect.Message interface from the message. - // This is the entry point to the reflection API. - msg := pb.ProtoReflect() - - // 2. Get the message's descriptor, which contains metadata about its fields. - descriptor := msg.Descriptor() - - // 3. Find the specific field descriptor by its protobuf name ("Filename"). - // Note: The field name must match the name in the .proto file. - fieldName := protoreflect.Name("Filename") - fieldDescriptor := descriptor.Fields().ByName(fieldName) - - // try upper case - if fieldDescriptor == nil { - fieldName = protoreflect.Name("filename") - fieldDescriptor = descriptor.Fields().ByName(fieldName) - // if fieldDescriptor == nil { - // panic(".proto file: try 'Filename', not 'filename'? or maybe nomutex if pb.Marshal() fails") - // } - } - - // 4. Check if the field was found. If not, return false. - if fieldDescriptor == nil { - return "", ErrProtoNoVarName - } - - // 5. (Optional but recommended) Verify the field is a string type. - if fieldDescriptor.Kind() != protoreflect.StringKind { - // The field exists but is not a string, so we can't return it as one. - return "", ErrProtoVarNotString - } - - // 6. If the field exists and is a string, get its value. - // The value is returned as a protoreflect.Value. - value := msg.Get(fieldDescriptor) - - // 7. Convert the protoreflect.Value to a native Go string. - return value.String(), nil -} - -// this will try both "filename" and "Filename" -func GetString(pb proto.Message, varname string) (string, error) { - // 1. Get the protoreflect.Message interface from the message. - // This is the entry point to the reflection API. - msg := pb.ProtoReflect() - - // 2. Get the message's descriptor, which contains metadata about its fields. - descriptor := msg.Descriptor() - - // 3. Find the specific field descriptor by its protobuf name ("Filename"). - // Note: The field name must match the name in the .proto file. - fieldName := protoreflect.Name(varname) - fieldDescriptor := descriptor.Fields().ByName(fieldName) - - // 4. Check if the field was found. If not, return false. - if fieldDescriptor == nil { - return "", ErrProtoNoVarName - } - - // 5. (Optional but recommended) Verify the field is a string type. - if fieldDescriptor.Kind() != protoreflect.StringKind { - // The field exists but is not a string, so we can't return it as one. - return "", ErrProtoVarNotString - } - - // 6. If the field exists and is a string, get its value. - // The value is returned as a protoreflect.Value. - value := msg.Get(fieldDescriptor) - - // 7. Convert the protoreflect.Value to a native Go string. - return value.String(), nil -} - -// don't do this. use prototext.Format(pb) -// duh. I'm dumb. I literally wasted my time + was being lazy so I -// just asked asking Gemini AI to make some function for this -// when, for years, I use prototext.Format() all over the place -func printStrings(pb proto.Message) { - // 1. Get the protoreflect.Message interface. - msg := pb.ProtoReflect() - - // It's good practice to check if the message is valid. - if !msg.IsValid() { - fmt.Printf("Error: Provided protobuf message is not valid.") - return - } - - // 2. Get the message's descriptor. - descriptor := msg.Descriptor() - fmt.Printf("--- Listing String Fields in [%s] ---\n", descriptor.FullName()) - - // 3. Get the collection of all field descriptors for this message. - fields := descriptor.Fields() - - // 4. Iterate over all the fields. - for i := 0; i < fields.Len(); i++ { - // Get the descriptor for the field at the current index. - fieldDescriptor := fields.Get(i) - - // 5. Check if the field's kind is a string. - if fieldDescriptor.Kind() == protoreflect.StringKind { - // 6. If it is a string, get its name and value. - fieldName := fieldDescriptor.Name() - value := msg.Get(fieldDescriptor).String() - - // 7. Print the formatted result. - // We add a check to see if the field is populated. An empty string - // is a valid value, but you might only want to see set fields. - if msg.Has(fieldDescriptor) { - fmt.Printf(" %s: \"%s\"\n", fieldName, value) - } - } - } -} diff --git a/flags.go b/flags.go new file mode 100644 index 0000000..d827f7e --- /dev/null +++ b/flags.go @@ -0,0 +1,23 @@ +package config + +func Get(flag string) string { + if configPB == nil { + return "" + } + found := configPB.FindByKey(flag) + if found == nil { + return "" + } + return found.Value +} + +func GetError(flag string) error { + return nil +} + +func Set(flag string) { +} + +func SetError(flag string) error { + return nil +} diff --git a/init.go b/init.go new file mode 100644 index 0000000..eb5afbe --- /dev/null +++ b/init.go @@ -0,0 +1,76 @@ +package config + +// this is an experiment at this point to +// see how this turns out + +import ( + "errors" + "fmt" + "os" + "path/filepath" +) + +var configPB *Configs + +// these are normally what are sent from ldflags +var APPNAME string +var BUILDTIME string +var VERSION string + +var argv []string + +func Init(appname, version, buildtime string, fromargv []string) error { + APPNAME = appname + VERSION = version + BUILDTIME = buildtime + argv = fromargv + + configDir, err := os.UserConfigDir() + if err != nil { + fmt.Println("OS isn't returning UserConfigDir()", err) + return err + } + fullname := filepath.Join(configDir, appname, "config.text") + configPB = NewConfigs() + err = loadTEXT(configPB, fullname) + if err == nil { + return nil + } + + if errors.Is(err, os.ErrNotExist) { + // file doesn't exist, make a new file + return makeNewConfigFile(appname) + } + if errors.Is(err, ErrEmpty) { + fmt.Printf("config file size was empty. out of diskspace? %s\n", fullname) + return err + } + fmt.Println("config.Init()", err) + // panic("config") + return err +} + +func makeNewConfigFile(appname string) error { + configDir, err := os.UserConfigDir() + if err != nil { + fmt.Println("OS isn't returning UserConfigDir()", err) + return err + } + fullname := filepath.Join(configDir, appname, "config.text") + + configPB = NewConfigs() + configPB.Filename = fullname + + newvar := new(Config) + newvar.Key = "example config var" + newvar.Value = "protobufs are neat" + configPB.Clone(newvar) + + newvar.Key = "Verbose" + newvar.Value = "true" + configPB.Clone(newvar) + + // writes the config file to disk + err = Save() + return err +} diff --git a/load.go b/load.go index 84a78f6..2434f9e 100644 --- a/load.go +++ b/load.go @@ -31,12 +31,12 @@ var ErrMarshal error = fmt.Errorf("protobuf parse error") func ConfigLoad(pb proto.Message, argname string, protoname string) error { var fullname string var err error - homeDir, err := os.UserHomeDir() + configDir, err := os.UserConfigDir() if err != nil { return err } - fullname = filepath.Join(homeDir, ".config", argname, protoname+".text") + fullname = filepath.Join(configDir, argname, protoname+".text") SetFilename(pb, fullname) // if both don't exist or both are empty, return known errors @@ -72,11 +72,11 @@ func LoadCache(pb proto.Message, argname string, protoname string) error { os.MkdirAll(fullpath, os.ModePerm) fullname := filepath.Join(fullpath, protoname+".pb") _, err := SetFilename(pb, fullname) - return errors.Join(err, Load(pb)) + return errors.Join(err, LoadPB(pb)) } // this logic isn't great yet -func Load(pb proto.Message) error { +func LoadPB(pb proto.Message) error { fullname, err := GetFilename(pb) if err != nil { return err @@ -125,6 +125,10 @@ func Load(pb proto.Message) error { return nil } +func LoadFromFilename(pb proto.Message, fullname string) error { + return LoadFile(pb, fullname) +} + func LoadFile(pb proto.Message, fullname string) error { if strings.HasSuffix(fullname, ".text") { return loadTEXT(pb, fullname) @@ -153,17 +157,17 @@ func loadPB(pb proto.Message, fullname string) error { return nil } -func LoadPB(pb proto.Message, argname string, protoname string) (string, error) { +func LoadConfigPB(pb proto.Message, argname string, protoname string) (string, error) { var fullname string if strings.HasPrefix(argname, "/") { fullname = filepath.Join(argname, protoname+".pb") } else { - homeDir, err := os.UserHomeDir() + configDir, err := os.UserConfigDir() if err != nil { return "", err } - fullname = filepath.Join(homeDir, ".config", argname, protoname+".pb") + fullname = filepath.Join(configDir, argname, protoname+".pb") } data, err := loadFile(fullname) diff --git a/lookupPB.go b/lookupPB.go new file mode 100644 index 0000000..9fb73a2 --- /dev/null +++ b/lookupPB.go @@ -0,0 +1,165 @@ +package config + +import ( + "errors" + "fmt" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +var ErrProtoNoVarName error = errors.New("name not in .proto") +var ErrProtoVarNotString error = errors.New("name exists but is not a string") + +// Gemini AI can help author some pretty good protobuf code. +// I never remember the syntax for 'reflect' on these things. + +// sets "Filename" if it exists in the protobuf +func SetFilename(pb proto.Message, filename string) (bool, error) { + msg := pb.ProtoReflect() // This is the entry point to the reflection API. + + descriptor := msg.Descriptor() // Get the message's descriptor, which contains metadata about its fields. + + fieldName := protoreflect.Name("Filename") + fieldDescriptor := descriptor.Fields().ByName(fieldName) + + if fieldDescriptor == nil { + fieldName = protoreflect.Name("filename") + fieldDescriptor = descriptor.Fields().ByName(fieldName) + } + + if fieldDescriptor == nil { + return false, fmt.Errorf("fieldDescriptor == nil") + } + + if fieldDescriptor.Kind() != protoreflect.StringKind { + // The field exists but is not a string, so we can't return it as one. + return false, fmt.Errorf("The field exists but is not a string") + } + + valueToSet := protoreflect.ValueOfString(filename) + + // 6. If the field exists and is a string, get its value. + // The value is returned as a protoreflect.Value. + msg.Set(fieldDescriptor, valueToSet) + + // 7. Convert the protoreflect.Value to a native Go string. + return true, nil +} + +// this will try both "filename" and "Filename" +func GetFilename(pb proto.Message) (string, error) { + // 1. Get the protoreflect.Message interface from the message. + // This is the entry point to the reflection API. + msg := pb.ProtoReflect() + + // 2. Get the message's descriptor, which contains metadata about its fields. + descriptor := msg.Descriptor() + + // 3. Find the specific field descriptor by its protobuf name ("Filename"). + // Note: The field name must match the name in the .proto file. + fieldName := protoreflect.Name("Filename") + fieldDescriptor := descriptor.Fields().ByName(fieldName) + + // try upper case + if fieldDescriptor == nil { + fieldName = protoreflect.Name("filename") + fieldDescriptor = descriptor.Fields().ByName(fieldName) + // if fieldDescriptor == nil { + // panic(".proto file: try 'Filename', not 'filename'? or maybe nomutex if pb.Marshal() fails") + // } + } + + // 4. Check if the field was found. If not, return false. + if fieldDescriptor == nil { + return "", ErrProtoNoVarName + } + + // 5. (Optional but recommended) Verify the field is a string type. + if fieldDescriptor.Kind() != protoreflect.StringKind { + // The field exists but is not a string, so we can't return it as one. + return "", ErrProtoVarNotString + } + + // 6. If the field exists and is a string, get its value. + // The value is returned as a protoreflect.Value. + value := msg.Get(fieldDescriptor) + + // 7. Convert the protoreflect.Value to a native Go string. + return value.String(), nil +} + +// this will try both "filename" and "Filename" +func GetString(pb proto.Message, varname string) (string, error) { + // 1. Get the protoreflect.Message interface from the message. + // This is the entry point to the reflection API. + msg := pb.ProtoReflect() + + // 2. Get the message's descriptor, which contains metadata about its fields. + descriptor := msg.Descriptor() + + // 3. Find the specific field descriptor by its protobuf name ("Filename"). + // Note: The field name must match the name in the .proto file. + fieldName := protoreflect.Name(varname) + fieldDescriptor := descriptor.Fields().ByName(fieldName) + + // 4. Check if the field was found. If not, return false. + if fieldDescriptor == nil { + return "", ErrProtoNoVarName + } + + // 5. (Optional but recommended) Verify the field is a string type. + if fieldDescriptor.Kind() != protoreflect.StringKind { + // The field exists but is not a string, so we can't return it as one. + return "", ErrProtoVarNotString + } + + // 6. If the field exists and is a string, get its value. + // The value is returned as a protoreflect.Value. + value := msg.Get(fieldDescriptor) + + // 7. Convert the protoreflect.Value to a native Go string. + return value.String(), nil +} + +// don't do this. use prototext.Format(pb) +// duh. I'm dumb. I literally wasted my time + was being lazy so I +// just asked asking Gemini AI to make some function for this +// when, for years, I use prototext.Format() all over the place +func printStrings(pb proto.Message) { + // 1. Get the protoreflect.Message interface. + msg := pb.ProtoReflect() + + // It's good practice to check if the message is valid. + if !msg.IsValid() { + fmt.Printf("Error: Provided protobuf message is not valid.") + return + } + + // 2. Get the message's descriptor. + descriptor := msg.Descriptor() + fmt.Printf("--- Listing String Fields in [%s] ---\n", descriptor.FullName()) + + // 3. Get the collection of all field descriptors for this message. + fields := descriptor.Fields() + + // 4. Iterate over all the fields. + for i := 0; i < fields.Len(); i++ { + // Get the descriptor for the field at the current index. + fieldDescriptor := fields.Get(i) + + // 5. Check if the field's kind is a string. + if fieldDescriptor.Kind() == protoreflect.StringKind { + // 6. If it is a string, get its name and value. + fieldName := fieldDescriptor.Name() + value := msg.Get(fieldDescriptor).String() + + // 7. Print the formatted result. + // We add a check to see if the field is populated. An empty string + // is a valid value, but you might only want to see set fields. + if msg.Has(fieldDescriptor) { + fmt.Printf(" %s: \"%s\"\n", fieldName, value) + } + } + } +} diff --git a/save.go b/save.go index 78175bd..f926a31 100644 --- a/save.go +++ b/save.go @@ -17,15 +17,22 @@ func ConfigSave(pb proto.Message) error { return saveTEXT(pb, "") } -func Save(pb proto.Message) error { +// writes the protobuf to disk +// uses the already configured Filename +func SavePB(pb proto.Message) error { fullname, err := GetFilename(pb) if err != nil { return err } - return SavePB(pb, fullname) + return SaveToFilename(pb, fullname) } -func SavePB(pb proto.Message, fullname string) error { +// writes the protobuf to disk (sets Filename if PB has 'Filename') +func SaveToFilename(pb proto.Message, fullname string) error { + basedir, _ := filepath.Split(fullname) + if err := os.MkdirAll(basedir, os.ModePerm); err != nil { + return err + } if strings.HasSuffix(fullname, ".pb") { return saveProto(pb, fullname) } diff --git a/verbose.go b/verbose.go new file mode 100644 index 0000000..4658dcf --- /dev/null +++ b/verbose.go @@ -0,0 +1,26 @@ +package config + +// this is an experiment at this point to +// see how this turns out + +func Verbose() bool { + // always use the config file value first + if configPB != nil { + found := configPB.FindByKey("Verbose") + + if found != nil { + if found.Value == "true" { + return true + } + return false + } + } + + // nothing in the config file. check argv + for _, v := range argv { + if v == "--verbose" { + return true + } + } + return false +} -- cgit v1.2.3