commit fee08de124dc0715238be62cb426f8cffc226de4
parent f380c603ce6e09f85bcbcc663b5bbe71e0fb2ade
Author: Sean Enck <sean@ttypty.com>
Date: Sun, 15 Jun 2025 08:23:06 -0400
help is always full, though it is now going to add more information
Diffstat:
6 files changed, 42 insertions(+), 92 deletions(-)
diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go
@@ -76,6 +76,7 @@ var (
KeyFile string
NoKey string
}{"keyfile", "nokey"}
+ ReadOnly = []string{Insert, Move, ReKey, Remove, Unset}
)
// AllowedInReadOnly indicates any commands that are allowed in readonly mode
@@ -83,7 +84,7 @@ func AllowedInReadOnly(cmds ...string) []string {
if config.EnvReadOnly.Get() {
var allowed []string
for _, item := range cmds {
- if slices.Contains([]string{Move, Insert, Unset, Remove, ReKey}, item) {
+ if slices.Contains(ReadOnly, item) {
continue
}
allowed = append(allowed, item)
diff --git a/internal/app/commands/core_test.go b/internal/app/commands/core_test.go
@@ -1,6 +1,8 @@
package commands_test
import (
+ "fmt"
+ "sort"
"testing"
"git.sr.ht/~enckse/lockbox/internal/app/commands"
@@ -23,3 +25,12 @@ func TestIsReadOnly(t *testing.T) {
t.Error("invalid, is not readonly")
}
}
+
+func TestReadOnlyOrder(t *testing.T) {
+ had := fmt.Sprintf("%v", commands.ReadOnly)
+ v := commands.ReadOnly
+ sort.Strings(v)
+ if had != fmt.Sprintf("%v", v) {
+ t.Error("invalid readonly sort")
+ }
+}
diff --git a/internal/app/help/core.go b/internal/app/help/core.go
@@ -36,6 +36,7 @@ type (
HelpCommand string
HelpConfigCommand string
NoColor string
+ ReadOnlyCommands string
Config struct {
Env string
Home string
@@ -76,11 +77,7 @@ func Usage(verbose bool, exe string) ([]string, error) {
isGroup = "group"
)
var results []string
- isReadOnly := config.EnvReadOnly.Get()
- canClip := config.EnvFeatureClip.Get()
- if canClip {
- results = append(results, command(commands.Clip, isEntry, "copy the entry's value into the clipboard"))
- }
+ results = append(results, command(commands.Clip, isEntry, "copy the entry's value into the clipboard"))
results = append(results, command(commands.Completions, "<shell>", "generate completions via auto-detection"))
for _, c := range commands.CompletionTypes {
results = append(results, subCommand(commands.Completions, c, "", fmt.Sprintf("generate %s completions", c)))
@@ -89,35 +86,27 @@ func Usage(verbose bool, exe string) ([]string, error) {
results = append(results, command(commands.Help, "", "show this usage information"))
results = append(results, subCommand(commands.Help, commands.HelpAdvanced, "", "display verbose help information"))
results = append(results, subCommand(commands.Help, commands.HelpConfig, "", "display verbose configuration information"))
- if !isReadOnly {
- results = append(results, command(commands.Insert, isEntry, "insert a new entry into the store"))
- results = append(results, command(commands.Unset, isEntry, "clear an entry value"))
- results = append(results, command(commands.Move, fmt.Sprintf("%s %s", isGroup, isGroup), "move a group from source to destination"))
- results = append(results, command(commands.ReKey, "", "rekey/reinitialize the database credentials"))
- results = append(results, command(commands.Remove, isGroup, "remove an entry from the store"))
- }
+ results = append(results, command(commands.Insert, isEntry, "insert a new entry into the store"))
+ results = append(results, command(commands.Unset, isEntry, "clear an entry value"))
+ results = append(results, command(commands.Move, fmt.Sprintf("%s %s", isGroup, isGroup), "move a group from source to destination"))
+ results = append(results, command(commands.ReKey, "", "rekey/reinitialize the database credentials"))
+ results = append(results, command(commands.Remove, isGroup, "remove an entry from the store"))
results = append(results, command(commands.JSON, isFilter, "display detailed information"))
results = append(results, command(commands.List, isFilter, "list entries"))
results = append(results, command(commands.Groups, isFilter, "list groups"))
results = append(results, command(commands.Show, isEntry, "show the entry's value"))
- canTOTP := config.EnvFeatureTOTP.Get()
- if canTOTP {
- results = append(results, command(commands.TOTP, isEntry, "display an updating totp generated code"))
- if canClip {
- results = append(results, subCommand(commands.TOTP, commands.TOTPClip, isEntry, "copy totp code to clipboard"))
- }
- results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings"))
- results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code"))
- results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)"))
- results = append(results, subCommand(commands.TOTP, commands.TOTPURL, isEntry, "display TOTP url information"))
- results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)"))
- results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry"))
- }
+ results = append(results, command(commands.TOTP, isEntry, "display an updating totp generated code"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPClip, isEntry, "copy totp code to clipboard"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPURL, isEntry, "display TOTP url information"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry"))
results = append(results, command(commands.Version, "", "display version information"))
sort.Strings(results)
usage := []string{fmt.Sprintf("%s usage:", exe)}
if verbose {
- canColor := config.EnvFeatureColor.Get()
results = append(results, "")
document := Documentation{
Executable: filepath.Base(exe),
@@ -128,6 +117,7 @@ func Usage(verbose bool, exe string) ([]string, error) {
HelpCommand: commands.Help,
HelpConfigCommand: commands.HelpConfig,
NoColor: config.NoColorFlag,
+ ReadOnlyCommands: strings.Join(commands.ReadOnly, ", "),
}
document.Config.Env = config.ConfigEnv
document.Config.Home = config.ConfigHome
@@ -156,30 +146,18 @@ func Usage(verbose bool, exe string) ([]string, error) {
if !strings.HasSuffix(n, textFile) {
continue
}
- section := strings.TrimSuffix(filepath.Base(n), textFile)
- skip := false
adding := ""
- for k, v := range map[string]bool{
- "totp": canTOTP,
- "color": canColor,
- "clipboard": canClip,
+ section := strings.TrimSuffix(filepath.Base(n), textFile)
+ for _, key := range []string{
+ "totp",
+ "color",
+ "clipboard",
} {
- if section == k {
- if !v {
- skip = true
- }
+ if section == key {
adding = "This functionality can be controlled by a configuration feature flag."
break
}
}
- if !skip {
- if section == "rekey" || section == "globs" {
- skip = isReadOnly
- }
- }
- if skip {
- continue
- }
header := fmt.Sprintf("[%s]", section)
s, err := processDoc(header, n, document)
if err != nil {
diff --git a/internal/app/help/core_test.go b/internal/app/help/core_test.go
@@ -1,12 +1,10 @@
package help_test
import (
- "fmt"
"strings"
"testing"
"git.sr.ht/~enckse/lockbox/internal/app/help"
- "git.sr.ht/~enckse/lockbox/internal/config/store"
)
func TestUsage(t *testing.T) {
@@ -15,7 +13,7 @@ func TestUsage(t *testing.T) {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = help.Usage(true, "lb")
- if len(u) != 121 {
+ if len(u) != 128 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
@@ -26,46 +24,3 @@ func TestUsage(t *testing.T) {
}
}
}
-
-func TestFlags(t *testing.T) {
- defer store.Clear()
- for _, feature := range []string{"clip", "totp", "color"} {
- store.Clear()
- key := fmt.Sprintf("LOCKBOX_FEATURE_%s", strings.ToUpper(feature))
- store.SetBool(key, true)
- u, _ := help.Usage(true, "lb")
- if !strings.Contains(strings.Join(u, "\n"), feature) {
- t.Errorf("verbose help lacks: %s", feature)
- }
- store.SetBool(key, false)
- u, _ = help.Usage(true, "lb")
- if strings.Contains(strings.Join(u, "\n"), feature) {
- t.Errorf("verbose help has: %s", feature)
- }
- }
-}
-
-func TestReadOnly(t *testing.T) {
- defer store.Clear()
- check := func(require bool) {
- u, _ := help.Usage(true, "lb")
- text := strings.Join(u, "\n")
- for _, need := range []string{" mv ", " rm ", "[rekey]", " insert ", " unset ", "[globs]"} {
- has := strings.Contains(text, need)
- if has {
- if require {
- continue
- }
- t.Errorf("has unwanted text: %s", need)
- } else {
- if require {
- t.Errorf("missing required text: %s", need)
- }
- }
- }
- }
-
- check(true)
- store.SetBool("LOCKBOX_READONLY", true)
- check(false)
-}
diff --git a/internal/app/help/doc/completions.txt b/internal/app/help/doc/completions.txt
@@ -2,5 +2,5 @@ Completions are available for certain shells. Generation of completions is
handled by `{{ $.Executable }} {{ $.CompletionsCommand }}` (detecting the
shell by default), provide an additional argument to the command of a
the specific shell to generate completions for. Generated completions use
-various `{{ $.Executable }}` settings to disable components depending on
+various `{{ $.Executable }}` feature flags and settings to disable components depending on
user settings.
diff --git a/internal/app/help/doc/readonly.txt b/internal/app/help/doc/readonly.txt
@@ -0,0 +1,5 @@
+In readonly mode the underlying database will block any commands that
+write to the underlying store when `{{ $.Executable }}` is called. The
+following commands are not allowed in readonly mode: {{ $.ReadOnlyCommands }}
+
+This functionality can be controlled via configuration.