From cbaa1c37132e73d4fd112418a7544ae71b46dfe0 Mon Sep 17 00:00:00 2001
From: Jeff Carr <jcarr@wit.com>
Date: Sat, 8 Feb 2025 06:35:38 -0600
Subject: [PATCH] make a config file

---
 Makefile            |   1 +
 config.go           | 116 ++++++++++++++++++++++++++++++++++++++++++++
 init.go             |  17 +++++++
 structs.go          |   1 +
 toolkitConfig.proto |  19 ++++++++
 5 files changed, 154 insertions(+)
 create mode 100644 config.go
 create mode 100644 toolkitConfig.proto

diff --git a/Makefile b/Makefile
index 7f25730..11e9166 100644
--- a/Makefile
+++ b/Makefile
@@ -16,6 +16,7 @@ goimports:
 
 proto:
 	autogenpb --proto widget.proto
+	autogenpb --proto toolkitConfig.proto
 
 clean:
 	rm -f go.* *.pb.go
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..a68b562
--- /dev/null
+++ b/config.go
@@ -0,0 +1,116 @@
+// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
+
+package tree
+
+// functions to import and export the protobuf
+// data to and from config files
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"go.wit.com/log"
+)
+
+// load the ~/.config/forge/ files
+func configLoad() *ToolkitConfigs {
+	if os.Getenv("FORGE_CONFIG") == "" {
+		homeDir, _ := os.UserHomeDir()
+		fullpath := filepath.Join(homeDir, ".config/forge")
+		os.Setenv("FORGE_CONFIG", fullpath)
+	}
+
+	c, err := loadText()
+	if err != nil {
+		log.Info("gui toolkit configLoad() error", err)
+	}
+	if c != nil {
+		return c
+	}
+
+	// first time user. make a template config file
+	c = sampleConfig()
+	return c
+}
+
+// makes a sample config (and saves it)
+func sampleConfig() *ToolkitConfigs {
+	all := NewToolkitConfigs()
+	new1 := new(ToolkitConfig)
+	new1.Plugin = "tree"
+	new1.Name = "test"
+	new1.Value = "apple"
+	all.Append(new1)
+
+	all.configSave()
+
+	fmt.Println("first time user. adding an example config file with", len(all.ToolkitConfigs), "repos")
+	return all
+}
+
+// write to ~/.config/forge/ unless ENV{FORGE_CONFIG} is set
+func (c *ToolkitConfigs) configSave() error {
+	s := c.FormatTEXT()
+	configWrite("toolkit.text", []byte(s))
+	return nil
+}
+
+func loadText() (*ToolkitConfigs, error) {
+	// this lets the user hand edit the config
+	data, err := loadFile("toolkit.text")
+	if err != nil {
+		return nil, err
+	}
+	if data == nil {
+		return nil, fmt.Errorf("toolkit.text data was nil")
+	}
+	if len(data) == 0 {
+		return nil, fmt.Errorf("toolkit.text was empty")
+	}
+
+	c := new(ToolkitConfigs)
+
+	// attempt to marshal toolkit.text
+	if err := c.UnmarshalTEXT(data); err != nil {
+		return nil, err
+	}
+	log.Log(TREE, "ConfigLoad()", len(c.ToolkitConfigs), "entries in ~/.config/forge")
+	return c, nil
+}
+
+func loadFile(filename string) ([]byte, error) {
+	fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
+	data, err := os.ReadFile(fullname)
+	if errors.Is(err, os.ErrNotExist) {
+		// if file does not exist, just return nil. this
+		// will cause ConfigLoad() to try the next config file like "toolkit.text"
+		// because the user might want to edit the .config by hand
+		return nil, nil
+	}
+	if err != nil {
+		// log.Info("open config file :", err)
+		return nil, err
+	}
+	return data, nil
+}
+
+func configWrite(filename string, data []byte) error {
+	fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
+
+	cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+	defer cfgfile.Close()
+	if err != nil {
+		log.Warn("open config file :", err)
+		return err
+	}
+	if filename == "toolkit.text" {
+		// add header
+		cfgfile.Write([]byte("\n"))
+		cfgfile.Write([]byte("# you can customize your GUI toolkit user settings here\n"))
+		cfgfile.Write([]byte("\n"))
+	}
+	cfgfile.Write(data)
+	return nil
+}
diff --git a/init.go b/init.go
index f87bbb5..19a828b 100644
--- a/init.go
+++ b/init.go
@@ -5,6 +5,7 @@
 package tree
 
 import (
+	"fmt"
 	"os"
 	"runtime/debug"
 	"sync"
@@ -109,9 +110,25 @@ func (me *TreeInfo) catchActionChannel() {
 func New() *TreeInfo {
 	me := new(TreeInfo)
 	me.pluginChan = make(chan widget.Action, 1)
+	me.config = configLoad()
 
 	log.Log(TREE, "Init() start channel reciever")
 	go me.catchActionChannel()
 	log.Log(TREE, "Init() END")
 	return me
 }
+
+func (t *TreeInfo) ConfigFind(n string) (string, error) {
+	all := t.config.All() // get the list of repos
+	for all.Scan() {
+		r := all.Next()
+		if t.PluginName != r.Plugin {
+			continue
+		}
+		if n == r.Name {
+			return r.Value, nil
+		}
+		log.Info("toolkit config", r.Plugin, r.Name, r.Value, n)
+	}
+	return "", fmt.Errorf("toolkit config %s not found", n)
+}
diff --git a/structs.go b/structs.go
index a3b9a63..eb76f39 100644
--- a/structs.go
+++ b/structs.go
@@ -18,6 +18,7 @@ var treeRoot *Node
 
 type TreeInfo struct {
 	PluginName string
+	config     *ToolkitConfigs
 
 	// this is the channel we send user events like
 	// mouse clicks or keyboard events back to the program
diff --git a/toolkitConfig.proto b/toolkitConfig.proto
new file mode 100644
index 0000000..a53fdc4
--- /dev/null
+++ b/toolkitConfig.proto
@@ -0,0 +1,19 @@
+// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
+
+syntax = "proto3";
+
+package forgepb;
+
+import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
+
+message ToolkitConfig {                                   //
+        string                   plugin             = 1;  // 'gocui', 'andlabs', etc
+        string                   name               = 2;  // variable name 'fullscreen'
+        string                   value              = 3;  // value "true"
+}
+
+message ToolkitConfigs {                                  // `autogenpb:marshal` `autogenpb:nomutex`
+        string                   uuid               = 1;  // `autogenpb:uuid:d7886d47-a3b9-43b9-b0f6-17074a9844e6`
+        string                   version            = 2;  // `autogenpb:version:v0.0.1`
+        repeated ToolkitConfig   ToolkitConfigs     = 3;
+}