commit 3c3c0c1492a3200439e796d7be17ddb0695ba784
parent 153043dd7054e2eb53f9d201f81b35736c0d1931
Author: Sean Enck <sean@ttypty.com>
Date: Fri, 6 Dec 2024 20:04:39 -0500
generate a TOML file with all the mappings laid out
Diffstat:
6 files changed, 101 insertions(+), 17 deletions(-)
diff --git a/internal/app/core.go b/internal/app/core.go
@@ -284,7 +284,11 @@ func Usage(verbose bool, exe string) ([]string, error) {
buf.WriteString(s)
if header == "[toml]" {
buf.WriteString("========================================\nconfig.toml\n---\n")
- buf.WriteString(config.ExampleTOML)
+ def, err := config.DefaultTOML()
+ if err != nil {
+ return nil, err
+ }
+ buf.WriteString(def)
buf.WriteString("========================================\n\n")
}
}
diff --git a/internal/app/core_test.go b/internal/app/core_test.go
@@ -13,7 +13,7 @@ func TestUsage(t *testing.T) {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = app.Usage(true, "lb")
- if len(u) != 181 {
+ if len(u) != 211 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/config/core.go b/internal/config/core.go
@@ -17,7 +17,7 @@ import (
)
const (
- colorWindowDelimiter = ","
+ colorWindowDelimiter = " "
colorWindowSpan = ":"
exampleColorWindow = "start" + colorWindowSpan + "end"
yes = "yes"
diff --git a/internal/config/core_test.go b/internal/config/core_test.go
@@ -76,31 +76,31 @@ func TestParseWindows(t *testing.T) {
if _, err := config.ParseColorWindow(""); err.Error() != "invalid colorization rules for totp, none found" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",2"); err.Error() != "invalid colorization rule found: 2" {
+ if _, err := config.ParseColorWindow(" 2"); err.Error() != "invalid colorization rule found: 2" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",1:200"); err.Error() != "invalid time found for colorization rule: 1:200" {
+ if _, err := config.ParseColorWindow(" 1:200"); err.Error() != "invalid time found for colorization rule: 1:200" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",1:-1"); err.Error() != "invalid time found for colorization rule: 1:-1" {
+ if _, err := config.ParseColorWindow(" 1:-1"); err.Error() != "invalid time found for colorization rule: 1:-1" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",200:1"); err.Error() != "invalid time found for colorization rule: 200:1" {
+ if _, err := config.ParseColorWindow(" 200:1"); err.Error() != "invalid time found for colorization rule: 200:1" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",-1:1"); err.Error() != "invalid time found for colorization rule: -1:1" {
+ if _, err := config.ParseColorWindow(" -1:1"); err.Error() != "invalid time found for colorization rule: -1:1" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",2:1"); err.Error() != "invalid time found for colorization rule: 2:1" {
+ if _, err := config.ParseColorWindow(" 2:1"); err.Error() != "invalid time found for colorization rule: 2:1" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",xxx:1"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
+ if _, err := config.ParseColorWindow("xxx:1"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",1:xxx"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
+ if _, err := config.ParseColorWindow(" 1:xxx"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
t.Errorf("invalid error: %v", err)
}
- if _, err := config.ParseColorWindow(",1:2,11:22"); err != nil {
+ if _, err := config.ParseColorWindow("1:2 11:22"); err != nil {
t.Errorf("invalid error: %v", err)
}
}
diff --git a/internal/config/toml.go b/internal/config/toml.go
@@ -9,11 +9,14 @@ import (
"os"
"path/filepath"
"slices"
+ "sort"
"strings"
"github.com/BurntSushi/toml"
)
+const isInclude = "include"
+
type (
// Loader indicates how included files should be sourced
Loader func(string) (io.Reader, error)
@@ -21,6 +24,7 @@ type (
ShellEnv struct {
Key string
Value string
+ raw string
}
)
@@ -66,8 +70,80 @@ var (
EnvPasswordGenTitle.Key(),
EnvReadOnly.Key(),
}
+ reverseMap = map[string][]string{
+ "[]": arrayTypes,
+ "0": intTypes,
+ "true": boolTypes,
+ }
)
+// DefaultTOML will load the internal, default TOML with additional comment markups
+func DefaultTOML() (string, error) {
+ s, err := LoadConfig(strings.NewReader(ExampleTOML), nil)
+ if err != nil {
+ return "", err
+ }
+ const root = "_root_"
+ unmapped := make(map[string][]string)
+ keys := []string{}
+ for _, item := range s {
+ raw := item.raw
+ parts := strings.Split(raw, "_")
+ length := len(parts)
+ if length == 0 {
+ return "", fmt.Errorf("invalid internal TOML structure: %v", item)
+ }
+ key := parts[0]
+ sub := ""
+ switch length {
+ case 1:
+ key = root
+ sub = parts[0]
+ case 2:
+ sub = parts[1]
+ default:
+ sub = strings.Join(parts[1:], "_")
+ }
+ field := "\"\""
+ for to, fromKey := range reverseMap {
+ if slices.Contains(fromKey, item.Key) {
+ field = to
+ break
+ }
+ }
+ sub = fmt.Sprintf(`# environment map: %s
+%s = %s
+`, item.Key, sub, field)
+ had, ok := unmapped[key]
+ if !ok {
+ had = []string{}
+ keys = append(keys, key)
+ }
+ had = append(had, sub)
+ unmapped[key] = had
+ }
+ sort.Strings(keys)
+ builder := strings.Builder{}
+ if _, err := fmt.Fprintf(&builder, `# include additional configs, can NOT nest, but does allow globs ('*')
+%s = []
+`, isInclude); err != nil {
+ return "", err
+ }
+ for _, k := range keys {
+ if k != root {
+ if _, err := fmt.Fprintf(&builder, "\n[%s]\n", k); err != nil {
+ return "", err
+ }
+ }
+ for _, sub := range unmapped[k] {
+ if _, err := builder.WriteString(sub); err != nil {
+ return "", err
+ }
+ }
+ }
+ return builder.String(), nil
+}
+
// LoadConfig will read the input reader and use the loader to source configuration files
func LoadConfig(r io.Reader, loader Loader) ([]ShellEnv, error) {
m := make(map[string]interface{})
@@ -123,7 +199,7 @@ func LoadConfig(r io.Reader, loader Loader) ([]ShellEnv, error) {
}
}
value = os.Expand(value, os.Getenv)
- res = append(res, ShellEnv{Key: export, Value: value})
+ res = append(res, ShellEnv{Key: export, Value: value, raw: k})
}
return res, nil
@@ -135,9 +211,9 @@ func overlayConfig(r io.Reader, canInclude bool, m *map[string]interface{}, load
return err
}
res := *m
- includes, ok := res["include"]
+ includes, ok := res[isInclude]
if ok {
- delete(*m, "include")
+ delete(*m, isInclude)
including, err := parseStringArray(includes)
if err != nil {
return err
diff --git a/internal/config/toml_test.go b/internal/config/toml_test.go
@@ -257,12 +257,16 @@ format = -1
}
}
-func TestLoadFile(t *testing.T) {
+func TestDefaultTOMLToLoadFile(t *testing.T) {
os.Mkdir("testdata", 0o755)
defer os.RemoveAll("testdata")
defer os.Clearenv()
file := filepath.Join("testdata", "config.toml")
- os.WriteFile(file, []byte(config.ExampleTOML), 0o644)
+ loaded, err := config.DefaultTOML()
+ if err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ os.WriteFile(file, []byte(loaded), 0o644)
if err := config.LoadConfigFile(file); err != nil {
t.Errorf("invalid error: %v", err)
}