From 61945bd9f96c7e1e72f705ea0a386be31370df9c Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Mon, 2 Feb 2026 19:22:41 -0500 Subject: [PATCH] cmd/loop: print homedir in help messages as ~ Make the output homedir-independent to facilitate tests. --- cmd/loop/default_path_text_test.go | 82 ++++++++++++++++++++++++++++++ cmd/loop/main.go | 58 +++++++++++++++++---- 2 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 cmd/loop/default_path_text_test.go diff --git a/cmd/loop/default_path_text_test.go b/cmd/loop/default_path_text_test.go new file mode 100644 index 00000000..1785966b --- /dev/null +++ b/cmd/loop/default_path_text_test.go @@ -0,0 +1,82 @@ +package main + +import ( + "errors" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestDefaultPathText verifies HOME-based path elision behavior used for help +// defaults without relying on the real environment. +func TestDefaultPathText(t *testing.T) { + sep := string(filepath.Separator) + home := filepath.Clean(sep + "home" + sep + "alice") + + homeDir := func() (string, error) { return home, nil } + + tests := []struct { + name string + value string + homeFn func() (string, error) + want string + }{ + { + name: "empty value", + value: "", + homeFn: func() (string, error) { + return home, nil + }, + want: "", + }, + { + name: "nil homedir func", + value: home + sep + "data", + homeFn: nil, + want: home + sep + "data", + }, + { + name: "homedir error", + value: home + sep + "data", + homeFn: func() (string, error) { + return "", errors.New("homedir error") + }, + want: home + sep + "data", + }, + { + name: "exact home", + value: home, + homeFn: homeDir, + want: "~", + }, + { + name: "home prefix", + value: home + sep + "dir" + sep + "file", + homeFn: homeDir, + want: "~" + sep + "dir" + sep + "file", + }, + { + name: "non-home path", + value: filepath.Clean(sep + "var" + sep + "tmp"), + homeFn: homeDir, + want: filepath.Clean(sep + "var" + sep + "tmp"), + }, + { + name: "prefix but not path segment", + value: home + "x" + sep + "dir", + homeFn: homeDir, + want: home + "x" + sep + "dir", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := defaultPathText(test.value, test.homeFn) + require.Equalf( + t, test.want, got, + "defaultPathText(%q)", test.value, + ) + }) + } +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 3f82be77..873a198d 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -52,10 +52,11 @@ var ( defaultInitiator = "loop-cli" loopDirFlag = &cli.StringFlag{ - Name: "loopdir", - Value: loopd.LoopDirBase, - Usage: "path to loop's base directory", - Sources: cli.EnvVars(envVarLoopDir), + Name: "loopdir", + Value: loopd.LoopDirBase, + DefaultText: defaultPathText(loopd.LoopDirBase, os.UserHomeDir), + Usage: "path to loop's base directory", + Sources: cli.EnvVars(envVarLoopDir), } networkFlag = &cli.StringFlag{ Name: "network", @@ -66,15 +67,21 @@ var ( } tlsCertFlag = &cli.StringFlag{ - Name: "tlscertpath", - Usage: "path to loop's TLS certificate", - Value: loopd.DefaultTLSCertPath, + Name: "tlscertpath", + Usage: "path to loop's TLS certificate", + Value: loopd.DefaultTLSCertPath, + DefaultText: defaultPathText( + loopd.DefaultTLSCertPath, os.UserHomeDir, + ), Sources: cli.EnvVars(envVarTLSCertPath), } macaroonPathFlag = &cli.StringFlag{ - Name: "macaroonpath", - Usage: "path to macaroon file", - Value: loopd.DefaultMacaroonPath, + Name: "macaroonpath", + Usage: "path to macaroon file", + Value: loopd.DefaultMacaroonPath, + DefaultText: defaultPathText( + loopd.DefaultMacaroonPath, os.UserHomeDir, + ), Sources: cli.EnvVars(envVarMacaroonPath), } verboseFlag = &cli.BoolFlag{ @@ -132,6 +139,37 @@ const ( envVarMacaroonPath = "LOOPCLI_MACAROONPATH" ) +// defaultPathText returns a help-friendly path string that replaces the user's +// home directory with "~". The homeDir function is injected so callers can +// control environment-dependent behavior in tests. +func defaultPathText(value string, homeDir func() (string, error)) string { + if value == "" { + return value + } + + if homeDir == nil { + return value + } + + home, err := homeDir() + if err != nil || home == "" { + return value + } + + cleanHome := filepath.Clean(home) + cleanValue := filepath.Clean(value) + if cleanValue == cleanHome { + return "~" + } + + prefix := cleanHome + string(filepath.Separator) + if suffix, ok := strings.CutPrefix(cleanValue, prefix); ok { + return "~" + string(filepath.Separator) + suffix + } + + return value +} + func printJSON(resp any) { b, err := json.Marshal(resp) if err != nil {