commit d8362a9f27a988eae9e245f0d295312360e0a14d
parent 10c0f563b4ffacbfd6d19adb2da76d2799762107
Author: Sean Enck <sean@ttypty.com>
Date: Fri, 6 Dec 2024 21:36:55 -0500
toml becomes first class configuration means and documentation
Diffstat:
9 files changed, 72 insertions(+), 120 deletions(-)
diff --git a/internal/app/core.go b/internal/app/core.go
@@ -278,19 +278,10 @@ func Usage(verbose bool, exe string) ([]string, error) {
return nil, err
}
switch header {
- case "[environment]":
+ case "[toml]":
env = s
default:
buf.WriteString(s)
- if header == "[toml]" {
- buf.WriteString("========================================\nconfig.toml\n---\n")
- def, err := config.DefaultTOML()
- if err != nil {
- return nil, err
- }
- buf.WriteString(def)
- buf.WriteString("========================================\n\n")
- }
}
}
if env == "" {
@@ -299,7 +290,11 @@ func Usage(verbose bool, exe string) ([]string, error) {
buf.WriteString(env)
results = append(results, strings.Split(strings.TrimSpace(buf.String()), "\n")...)
results = append(results, "")
- results = append(results, config.ListEnvironmentVariables()...)
+ toml, err := config.DefaultTOML()
+ if err != nil {
+ return nil, err
+ }
+ results = append(results, toml)
}
return append(usage, results...), nil
}
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) != 215 {
+ if len(u) != 98 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/app/doc/environment.txt b/internal/app/doc/environment.txt
@@ -1 +0,0 @@
-The following environment variables can alter how '{{ $.Executable }}' works.
diff --git a/internal/app/doc/toml.txt b/internal/app/doc/toml.txt
@@ -1,5 +1,8 @@
-the environment settings can also be set via TOML configuration
-files. The TOML file is read, the values transformed into the
-corresponding environment keys, values parsed (if not strings),
-expanded for shell variables, and then set into the process. The
-following is a outline of what is allowed in the TOML file:
+The core components of `{{ $.Executable }}` are controlled via
+environment settings, but these settings are secondary (overriden)
+by TOML configuration file(s) if given. The following represents
+all available environment and TOML settings as a TOML file with
+comments indicating the mapping to the underlying environment
+settings:
+
+---
diff --git a/internal/config/core.go b/internal/config/core.go
@@ -44,7 +44,7 @@ var (
xdgPaths = []string{configDirOffsetFile, tomlFile}
homePaths = []string{filepath.Join(configDir, configDirOffsetFile), filepath.Join(configDir, tomlFile)}
exampleColorWindows = []string{strings.Join([]string{exampleColorWindow, exampleColorWindow, exampleColorWindow + "..."}, colorWindowDelimiter)}
- registeredEnv = []printer{}
+ registry = map[string]printer{}
)
type (
@@ -356,7 +356,7 @@ func IsUnset(k, v string) (bool, error) {
func Environ() []string {
var results []string
for _, k := range os.Environ() {
- for _, r := range registeredEnv {
+ for _, r := range registry {
key := r.self().Key()
if key == EnvConfig.Key() {
continue
@@ -427,7 +427,7 @@ func wrap(in string, maxLength int) string {
}
func environmentRegister[T printer](obj T) T {
- registeredEnv = append(registeredEnv, obj)
+ registry[obj.self().Key()] = obj
return obj
}
diff --git a/internal/config/toml.go b/internal/config/toml.go
@@ -121,9 +121,13 @@ func DefaultTOML() (string, error) {
break
}
}
- sub = fmt.Sprintf(`# environment map: %s
+ text, err := generateDetailText(item.Key)
+ if err != nil {
+ return "", err
+ }
+ sub = fmt.Sprintf(`%s
%s = %s
-`, item.Key, sub, field)
+`, text, sub, field)
had, ok := unmapped[key]
if !ok {
had = []string{}
@@ -134,11 +138,21 @@ func DefaultTOML() (string, error) {
}
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 {
+ configEnv, err := generateDetailText(EnvConfig.Key())
+ if err != nil {
return "", err
}
+ for _, header := range []string{configEnv, "\n", fmt.Sprintf(`
+# include additional configs, can NOT nest, but does allow globs ('*')
+# this field is not configurable via environment variables
+# and it is not considered part of the environment either
+# it is ONLY used during TOML configuration loading
+%s = []
+`, isInclude), "\n"} {
+ if _, err := builder.WriteString(header); err != nil {
+ return "", err
+ }
+ }
for _, k := range keys {
if k != root {
if _, err := fmt.Fprintf(&builder, "\n[%s]\n", k); err != nil {
@@ -154,16 +168,37 @@ func DefaultTOML() (string, error) {
return builder.String(), nil
}
+func generateDetailText(key string) (string, error) {
+ data, ok := registry[key]
+ if !ok {
+ return "", fmt.Errorf("unexpected configuration key has no environment settings: %s", key)
+ }
+ env := data.self()
+ value, allow := data.values()
+ if len(value) == 0 {
+ value = "(unset)"
+ }
+ description := Wrap(2, env.desc)
+ requirement := "optional/default"
+ r := strings.TrimSpace(env.requirement)
+ if r != "" {
+ requirement = r
+ }
+ var text []string
+ for _, line := range []string{fmt.Sprintf("environment: %s", key), fmt.Sprintf("description:\n%s", description), fmt.Sprintf("default: %s", requirement), fmt.Sprintf("option: %s", strings.Join(allow, "|"))} {
+ for _, comment := range strings.Split(line, "\n") {
+ text = append(text, fmt.Sprintf("# %s", comment))
+ }
+ }
+ return strings.Join(text, "\n"), 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{})
if err := overlayConfig(r, true, &m, loader); err != nil {
return nil, err
}
- var allowed []string
- for _, k := range registeredEnv {
- allowed = append(allowed, k.self().Key())
- }
m = flatten(m, "")
var res []ShellEnv
for k, v := range m {
@@ -173,7 +208,7 @@ func LoadConfig(r io.Reader, loader Loader) ([]ShellEnv, error) {
} else {
export = environmentPrefix + export
}
- if !slices.Contains(allowed, export) {
+ if _, ok := registry[export]; !ok {
return nil, fmt.Errorf("unknown key: %s (%s)", k, export)
}
value, ok := v.(string)
diff --git a/internal/config/toml_test.go b/internal/config/toml_test.go
@@ -2,6 +2,7 @@ package config_test
import (
"errors"
+ "fmt"
"io"
"os"
"path/filepath"
@@ -267,6 +268,7 @@ func TestDefaultTOMLToLoadFile(t *testing.T) {
t.Errorf("invalid error: %v", err)
}
os.WriteFile(file, []byte(loaded), 0o644)
+ fmt.Println(loaded)
if err := config.LoadConfigFile(file); err != nil {
t.Errorf("invalid error: %v", err)
}
@@ -276,8 +278,7 @@ func TestDefaultTOMLToLoadFile(t *testing.T) {
count++
}
}
- // NOTE: this is one less than available because the default config itself is not configurable...via the config
- if count != expectEnv-1 {
+ if count != 31 {
t.Errorf("invalid environment after load: %d", count)
}
}
diff --git a/internal/config/vars.go b/internal/config/vars.go
@@ -7,7 +7,6 @@ import (
"fmt"
"net/url"
"os"
- "sort"
"strings"
"time"
)
@@ -68,7 +67,7 @@ var (
environmentDefault: newDefaultedEnvironment(0,
environmentBase{
subKey: EnvJSONDataOutput.subKey + "_HASH_LENGTH",
- desc: fmt.Sprintf("Maximum hash string length the JSON output should contain when '%s' mode is set for JSON output.", JSONOutputs.Hash),
+ desc: fmt.Sprintf("Maximum string length of the JSON value when '%s' mode is set for JSON output.", JSONOutputs.Hash),
}),
shortDesc: "hash length",
allowZero: true,
@@ -155,7 +154,7 @@ var (
environmentBase{
subKey: "MAX",
cat: totpCategory,
- desc: "Time, in seconds, in which to show a TOTP token before automatically exiting.",
+ desc: "Time, in seconds, to show a TOTP token before automatically exiting.",
}),
shortDesc: "max totp time",
allowZero: false,
@@ -283,7 +282,8 @@ The keyword '%s' will disable this functionality and the keyword '%s' will
search for a file in the following paths in XDG_CONFIG_HOME (%s) or from the user's HOME (%s).
Matches the first file found.
-Note that this setting is not output as part of the environment.`, noEnvironment, detectEnvironment, strings.Join(xdgPaths, ","), strings.Join(homePaths, ",")),
+Note that this value is not output as part of the environment, nor
+can it be set via TOML configuration.`, noEnvironment, detectEnvironment, strings.Join(xdgPaths, ","), strings.Join(homePaths, ",")),
}),
canDefault: true,
allowed: []string{detectEnvironment, fileExample, noEnvironment},
@@ -343,7 +343,7 @@ Set to '%s' to ignore the set key value`, noKeyMode, IgnoreKeyMode),
environmentBase{
subKey: "TEMPLATE",
cat: genCategory,
- desc: fmt.Sprintf("The go text template to use to format the chosen words into a password (use '%s' to include a '$' to avoid shell expansion issues). Fields available are Text, Position.Start, and Position.End.", TemplateVariable),
+ desc: fmt.Sprintf("The go text template to use to format the chosen words into a password (use '%s' to include a '$' to avoid shell expansion issues). Available fields: Text, Position.Start, and Position.End.", TemplateVariable),
}),
allowed: []string{"<go template>"},
canDefault: true,
@@ -381,7 +381,7 @@ Set to '%s' to ignore the set key value`, noKeyMode, IgnoreKeyMode),
environmentBase{
subKey: "CHARS",
cat: genCategory,
- desc: "The set of allowed characters in output words (empty means any characters are allowed).",
+ desc: "The set of allowed characters in output words (empty means any character is allowed).",
}),
allowed: []string{"<list of characters>"},
canDefault: true,
@@ -404,28 +404,6 @@ func GetReKey(args []string) (ReKeyArgs, error) {
return ReKeyArgs{KeyFile: file, NoKey: noPass}, nil
}
-// ListEnvironmentVariables will print information about env variables
-func ListEnvironmentVariables() []string {
- var results []string
- for _, item := range registeredEnv {
- env := item.self()
- value, allow := item.values()
- if len(value) == 0 {
- value = "(unset)"
- }
- description := Wrap(2, env.desc)
- requirement := "optional/default"
- r := strings.TrimSpace(env.requirement)
- if r != "" {
- requirement = r
- }
- text := fmt.Sprintf("\n%s\n%s requirement: %s\n default: %s\n options: %s\n", env.Key(), description, requirement, value, strings.Join(allow, "|"))
- results = append(results, text)
- }
- sort.Strings(results)
- return results
-}
-
func formatterTOTP(key, value string) string {
const (
otpAuth = "otpauth"
diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go
@@ -3,14 +3,11 @@ package config_test
import (
"fmt"
"os"
- "strings"
"testing"
"github.com/seanenck/lockbox/internal/config"
)
-const expectEnv = 32
-
func checkYesNo(key string, t *testing.T, obj config.EnvironmentBool, onEmpty bool) {
t.Setenv(key, "yes")
c, err := obj.Get()
@@ -94,24 +91,6 @@ func TestTOTP(t *testing.T) {
}
}
-func TestListVariables(t *testing.T) {
- known := make(map[string]struct{})
- for _, v := range config.ListEnvironmentVariables() {
- trim := strings.Split(strings.TrimSpace(v), " ")[0]
- if !strings.HasPrefix(trim, "LOCKBOX_") {
- t.Errorf("invalid env: %s", v)
- }
- if _, ok := known[trim]; ok {
- t.Errorf("invalid re-used env: %s", trim)
- }
- known[trim] = struct{}{}
- }
- l := len(known)
- if l != expectEnv {
- t.Errorf("invalid env count, outdated? %d", l)
- }
-}
-
func TestReKey(t *testing.T) {
if _, err := config.GetReKey([]string{"-nokey"}); err == nil || err.Error() != "a key or keyfile must be passed for rekey" {
t.Errorf("failed: %v", err)
@@ -231,44 +210,6 @@ func checkInt(e config.EnvironmentInt, key, text string, def int, allowZero bool
}
}
-func TestEnvironDefinitions(t *testing.T) {
- os.Clearenv()
- vals := config.ListEnvironmentVariables()
- expect := make(map[string]struct{})
- found := false
- for _, val := range vals {
- found = true
- env := strings.Split(strings.TrimSpace(val), "\n")[0]
- if !strings.HasPrefix(env, "LOCKBOX_") {
- t.Errorf("invalid env var: %s", env)
- }
- if env == "LOCKBOX_CONFIG_TOML" {
- continue
- }
- t.Setenv(env, "test")
- expect[env] = struct{}{}
- }
- if !found {
- t.Errorf("no environment variables found?")
- }
- read := config.Environ()
- if len(read) != len(expect) {
- t.Errorf("invalid environment variable info: %d != %d", len(expect), len(read))
- }
- for k := range expect {
- found := false
- for _, r := range read {
- if r == fmt.Sprintf("%s=test", k) {
- found = true
- break
- }
- }
- if !found {
- t.Errorf("unable to find env: %s", k)
- }
- }
-}
-
func TestCanColor(t *testing.T) {
os.Clearenv()
if can, _ := config.CanColor(); !can {