lockbox

password manager
Log | Files | Refs | README | LICENSE

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:
Minternal/app/core.go | 6+++++-
Minternal/app/core_test.go | 2+-
Minternal/config/core.go | 2+-
Minternal/config/core_test.go | 18+++++++++---------
Minternal/config/toml.go | 82++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Minternal/config/toml_test.go | 8++++++--
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) }