commit 5596d2a8798af49d3a09478cf8535b105fd267bb
parent 47651e781b0d093678e5fcbb415d6dd00b9933c0
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 14 Jun 2025 09:12:21 -0400
introduce feature flags
Diffstat:
11 files changed, 116 insertions(+), 14 deletions(-)
diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go
@@ -105,6 +105,12 @@ func (r runner) raw(pipeIn, command, stdout, stderr string) error {
return cmd.Run()
}
+func (r runner) feature(command, useBinary string) error {
+ text := fmt.Sprintf("%s %s >> %s 2>&1", fmt.Sprintf("%s-%s", binary, useBinary), command, r.log)
+ cmd := exec.Command("/bin/sh", "-c", text)
+ return cmd.Run()
+}
+
func (r runner) logAppend(command string) error {
return exec.Command("/bin/sh", "-c", fmt.Sprintf("%s >> %s", command, r.log)).Run()
}
@@ -349,6 +355,10 @@ func test(profile string) error {
r.section("env")
r.run("", fmt.Sprintf("vars | sed 's#/%s#/datadir#g' | grep -v CREDENTIALS | sort", profile))
+ r.section("features")
+ r.feature("clip abc", "noclip")
+ r.feature("totp ls", "nototp")
+
// cleanup and diff results
tmpFile := fmt.Sprintf("%s.tmp", r.log)
for _, item := range []string{"'s/\"modtime\": \"[0-9].*$/\"modtime\": \"XXXX-XX-XX\",/g'", "'s/^[0-9][0-9][0-9][0-9][0-9][0-9]$/XXXXXX/g'"} {
diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log
@@ -330,3 +330,6 @@ LOCKBOX_JSON_HASH_LENGTH=3
LOCKBOX_JSON_MODE=hash
LOCKBOX_STORE=testdata/datadir/pass.kdbx
LOCKBOX_TOTP_TIMEOUT=1
+features
+unable to get clipboard: clip feature is disabled
+totp feature is disabled
diff --git a/internal/app/completions/core.go b/internal/app/completions/core.go
@@ -11,6 +11,7 @@ import (
"text/template"
"git.sr.ht/~enckse/lockbox/internal/app/commands"
+ "git.sr.ht/~enckse/lockbox/internal/config/features"
)
type (
@@ -67,8 +68,14 @@ func Generate(completionType, exe string) ([]string, error) {
ExportCommand: fmt.Sprintf("%s %s %s", exe, commands.Env, commands.Completions),
}
- c.Options = []string{commands.Help, commands.List, commands.Show, commands.Version, commands.JSON, commands.Groups, commands.Clip, commands.TOTP, commands.Move, commands.Remove, commands.Insert, commands.Unset}
- c.TOTPSubCommands = []string{commands.TOTPMinimal, commands.TOTPOnce, commands.TOTPShow, commands.TOTPURL, commands.TOTPSeed, commands.TOTPClip}
+ c.Options = []string{commands.Help, commands.List, commands.Show, commands.Version, commands.JSON, commands.Groups, commands.Move, commands.Remove, commands.Insert, commands.Unset}
+ if features.CanClip() {
+ c.Options = append(c.Options, commands.Clip)
+ }
+ if features.CanTOTP() {
+ c.Options = append(c.Options, commands.TOTP)
+ c.TOTPSubCommands = []string{commands.TOTPMinimal, commands.TOTPOnce, commands.TOTPShow, commands.TOTPURL, commands.TOTPSeed, commands.TOTPClip}
+ }
sort.Strings(c.Options)
sort.Strings(c.TOTPSubCommands)
diff --git a/internal/app/help/core.go b/internal/app/help/core.go
@@ -12,6 +12,7 @@ import (
"git.sr.ht/~enckse/lockbox/internal/app/commands"
"git.sr.ht/~enckse/lockbox/internal/config"
+ "git.sr.ht/~enckse/lockbox/internal/config/features"
"git.sr.ht/~enckse/lockbox/internal/kdbx"
"git.sr.ht/~enckse/lockbox/internal/output"
)
@@ -75,7 +76,9 @@ func Usage(verbose bool, exe string) ([]string, error) {
isGroup = "group"
)
var results []string
- results = append(results, command(commands.Clip, isEntry, "copy the entry's value into the clipboard"))
+ if features.CanClip() {
+ 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)))
@@ -93,14 +96,16 @@ func Usage(verbose bool, exe string) ([]string, error) {
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.Show, isEntry, "show the entry's value"))
- 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"))
+ if features.CanTOTP() {
+ 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)}
diff --git a/internal/app/totp.go b/internal/app/totp.go
@@ -14,6 +14,7 @@ import (
"git.sr.ht/~enckse/lockbox/internal/app/commands"
"git.sr.ht/~enckse/lockbox/internal/config"
+ "git.sr.ht/~enckse/lockbox/internal/config/features"
"git.sr.ht/~enckse/lockbox/internal/kdbx"
"git.sr.ht/~enckse/lockbox/internal/platform/clip"
)
@@ -208,6 +209,9 @@ func (args *TOTPArguments) Do(opts TOTPOptions) error {
// NewTOTPArguments will parse the input arguments
func NewTOTPArguments(args []string) (*TOTPArguments, error) {
+ if !features.CanTOTP() {
+ return nil, features.NewError("totp")
+ }
if len(args) == 0 {
return nil, errors.New("not enough arguments for totp")
}
diff --git a/internal/config/features/clip.go b/internal/config/features/clip.go
@@ -0,0 +1,7 @@
+//go:build noclip
+
+package features
+
+func init() {
+ clipFeature = false
+}
diff --git a/internal/config/features/flags.go b/internal/config/features/flags.go
@@ -0,0 +1,24 @@
+// Package features controls build flag features
+package features
+
+import "fmt"
+
+var (
+ clipFeature = true
+ totpFeature = true
+)
+
+// CanTOTP indicates if TOTP is enabled
+func CanTOTP() bool {
+ return totpFeature
+}
+
+// CanClip indicates if clip(board) is enabled
+func CanClip() bool {
+ return clipFeature
+}
+
+// NewError creates an error if a feature is not enabled
+func NewError(name string) error {
+ return fmt.Errorf("%s feature is disabled", name)
+}
diff --git a/internal/config/features/flags_test.go b/internal/config/features/flags_test.go
@@ -0,0 +1,26 @@
+package features_test
+
+import (
+ "testing"
+
+ "git.sr.ht/~enckse/lockbox/internal/config/features"
+)
+
+func TestCanTOTP(t *testing.T) {
+ if !features.CanTOTP() {
+ t.Error("flag should be on by default")
+ }
+}
+
+func TestCanClip(t *testing.T) {
+ if !features.CanClip() {
+ t.Error("flag should be on by default")
+ }
+}
+
+func TestNewError(t *testing.T) {
+ err := features.NewError("abc")
+ if err == nil || err.Error() != "abc feature is disabled" {
+ t.Errorf("invalid error: %v", err)
+ }
+}
diff --git a/internal/config/features/totp.go b/internal/config/features/totp.go
@@ -0,0 +1,7 @@
+//go:build nototp
+
+package features
+
+func init() {
+ totpFeature = false
+}
diff --git a/internal/platform/clip/core.go b/internal/platform/clip/core.go
@@ -8,6 +8,7 @@ import (
"git.sr.ht/~enckse/lockbox/internal/app/commands"
"git.sr.ht/~enckse/lockbox/internal/config"
+ "git.sr.ht/~enckse/lockbox/internal/config/features"
"git.sr.ht/~enckse/lockbox/internal/platform"
osc "github.com/aymanbagabas/go-osc52"
)
@@ -30,8 +31,11 @@ func newBoard(copying, pasting []string) (Board, error) {
return Board{copying: copying, pasting: pasting, MaxTime: maximum, isOSC52: false}, nil
}
-// New will retrieve the commands to use for clipboard operations.
+// New creates a new clipboard
func New() (Board, error) {
+ if !features.CanClip() {
+ return Board{}, features.NewError("clip")
+ }
overridePaste := config.EnvClipPaste.Get()
overrideCopy := config.EnvClipCopy.Get()
setPaste := len(overridePaste) > 0
diff --git a/justfile b/justfile
@@ -6,21 +6,26 @@ ldflags := env_var_or_default("LDFLAGS", "")
gotest := "LOCKBOX_CONFIG_TOML=fake go test"
files := `find . -type f -name "*.go" | tr '\n' ' '`
cmd := "cmd/lb"
+tags := ""
default: build
build:
mkdir -p "{{target}}"
- go build {{goflags}} -ldflags "{{ldflags}} -X main.version={{version}}" -o "{{object}}" {{cmd}}/main.go
+ go build {{goflags}} -tags={{tags}} -ldflags "{{ldflags}} -X main.version={{version}}" -o "{{object}}" {{cmd}}/main.go
unittest:
{{gotest}} ./...
check: unittest tests
-tests: build
+tests: features
{{gotest}} {{cmd}}/main_test.go
+features: build
+ just tags=noclip object={{target}}/lb-noclip
+ just tags=nototp object={{target}}/lb-nototp
+
clean:
rm -f "{{object}}"
find internal/ {{cmd}} -type f -wholename "*testdata*" -delete