feat: lazysecrets list puts the star and icon in a field of their own, so ^ searches match names
This commit is contained in:
2 files changed
+76
-61
No files matched your search
+51
-41
@@ -27,12 +27,13 @@ import (
|
||||
// listCmd prints every source, vault and secret lazysecrets can find, one
|
||||
// per line, for fzf, editor pickers and scripts:
|
||||
//
|
||||
// lazysecrets list | fzf --ansi --with-nth='2..-3' --accept-nth=-1
|
||||
// lazysecrets list | fzf --ansi --delimiter='\t' --with-nth='2..-3' --nth='2..' --accept-nth=-1
|
||||
//
|
||||
// Each line is the kind, the name, why it couldn't be read (empty unless
|
||||
// it failed), the account and the link, tab-separated: kind, account and
|
||||
// link are one word each, so even fields split on spaces keep them first
|
||||
// and last two. The name is coloured from the theme,
|
||||
// Each line is the kind, a mark (★ and the provider's icon), the name,
|
||||
// why it couldn't be read (empty unless it failed), the account and the
|
||||
// link, tab-separated. The mark has a field of its own so a search can
|
||||
// leave it out (fzf --nth), and ^prod finds names starting with prod. The
|
||||
// name is coloured from the theme,
|
||||
// with the provider's icon (as in the TUI: --icons, LAZYSECRETS_ICONS,
|
||||
// ui.icons); the link never is.
|
||||
//
|
||||
@@ -44,7 +45,7 @@ func listCmd(providers []provider.Provider, st *store.Store, cachePath string, t
|
||||
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
||||
cached := fs.Bool("cached", false, "only what's cached, and repos: load nothing from Azure or Databricks")
|
||||
maxAge := fs.Duration("max-age", 2*time.Minute, "load cached lists older than this again")
|
||||
format := fs.String("format", "text", "text: kind, name, error, account and link, tab-separated; json: an object per line")
|
||||
format := fs.String("format", "text", "text: kind, mark, name, error, account and link, tab-separated; json: an object per line")
|
||||
color := fs.String("color", "always", "colour the names (text): always or never; fzf needs --ansi")
|
||||
iconsOn := fs.Bool("icons", withIcons, "put the provider's Nerd Font icon before names (text)")
|
||||
stars := fs.Bool("stars", true, "put ★ before starred names, in the theme's favouriteColor (text)")
|
||||
@@ -77,13 +78,12 @@ func listCmd(providers []provider.Provider, st *store.Store, cachePath string, t
|
||||
switch *format {
|
||||
case "text":
|
||||
write = func(e listing.Entry) error {
|
||||
colored := *color == "always"
|
||||
mark := markOf(e, theme, iconSet, *stars, colored)
|
||||
name := nameOf(e, theme, colored)
|
||||
why := errorOf(e, theme, colored)
|
||||
// A tab or line break in a name would break the line up.
|
||||
name := nameOf(e, theme, iconSet, *color == "always")
|
||||
if *stars {
|
||||
name = starOf(e, theme, *color == "always") + name
|
||||
}
|
||||
why := errorOf(e, theme, *color == "always")
|
||||
_, err := fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\n", e.Kind, oneLine.Replace(name), oneLine.Replace(why), e.Account, e.Link)
|
||||
_, err := fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\n", e.Kind, mark, oneLine.Replace(name), oneLine.Replace(why), e.Account, e.Link)
|
||||
return err
|
||||
}
|
||||
case "json":
|
||||
@@ -138,18 +138,6 @@ func listCmd(providers []provider.Provider, st *store.Store, cachePath string, t
|
||||
|
||||
var oneLine = strings.NewReplacer("\t", " ", "\r\n", " ", "\n", " ", "\r", " ")
|
||||
|
||||
// starOf is ★ for a starred entry, as the TUI marks them, and spaces
|
||||
// the same width for the rest, so names line up.
|
||||
func starOf(e listing.Entry, theme styles.Styles, color bool) string {
|
||||
switch {
|
||||
case !e.Starred:
|
||||
return " "
|
||||
case color:
|
||||
return theme.Favourite.Render("★") + " "
|
||||
}
|
||||
return "★ "
|
||||
}
|
||||
|
||||
// errorOf is why a list couldn't be read, or "": with colour, a red ✗
|
||||
// before it, as the TUI marks lists it can't read.
|
||||
func errorOf(e listing.Entry, theme styles.Styles, color bool) string {
|
||||
@@ -159,22 +147,10 @@ func errorOf(e listing.Entry, theme styles.Styles, color bool) string {
|
||||
return theme.Error.Render("✗ " + e.Error)
|
||||
}
|
||||
|
||||
// nameOf is an entry's name for the text format: the provider's icon,
|
||||
// the path with the item's own name standing out, and the account.
|
||||
func nameOf(e listing.Entry, theme styles.Styles, iconSet icons.Set, color bool) string {
|
||||
render := func(st lipgloss.Style, s string) string {
|
||||
if !color || s == "" {
|
||||
return s
|
||||
}
|
||||
return st.Render(s)
|
||||
}
|
||||
kind := provider.Kind("")
|
||||
for _, k := range []provider.Kind{provider.KindAzure, provider.KindDatabricks, provider.KindRepo} {
|
||||
if strings.EqualFold(string(k), e.Provider) {
|
||||
kind = k
|
||||
}
|
||||
}
|
||||
icon := iconSet.Prefix(kind)
|
||||
// nameOf is an entry's name for the text format: the path with the
|
||||
// item's own name standing out, and the account.
|
||||
func nameOf(e listing.Entry, theme styles.Styles, color bool) string {
|
||||
render := renderer(color)
|
||||
var name string
|
||||
if n := len(e.Path); n > 0 {
|
||||
if n > 1 {
|
||||
@@ -185,5 +161,39 @@ func nameOf(e listing.Entry, theme styles.Styles, iconSet icons.Set, color bool)
|
||||
if e.AccountLabel != "" {
|
||||
name += render(theme.Muted, " · "+e.AccountLabel)
|
||||
}
|
||||
return render(theme.Kind(kind), strings.TrimSuffix(icon, " ")) + strings.Repeat(" ", min(len(icon), 1)) + name
|
||||
return name
|
||||
}
|
||||
|
||||
// markOf is what goes before the name, in a field of its own so a search
|
||||
// leaves it out: ★ for a starred entry (as the TUI marks them; a space
|
||||
// as wide for the rest, so names line up), and the provider's icon.
|
||||
func markOf(e listing.Entry, theme styles.Styles, iconSet icons.Set, stars, color bool) string {
|
||||
render := renderer(color)
|
||||
var parts []string
|
||||
if stars {
|
||||
star := " "
|
||||
if e.Starred {
|
||||
star = render(theme.Favourite, "★")
|
||||
}
|
||||
parts = append(parts, star)
|
||||
}
|
||||
kind := provider.Kind("")
|
||||
for _, k := range []provider.Kind{provider.KindAzure, provider.KindDatabricks, provider.KindRepo} {
|
||||
if strings.EqualFold(string(k), e.Provider) {
|
||||
kind = k
|
||||
}
|
||||
}
|
||||
if icon := strings.TrimSpace(iconSet.Prefix(kind)); icon != "" {
|
||||
parts = append(parts, render(theme.Kind(kind), icon))
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func renderer(color bool) func(lipgloss.Style, string) string {
|
||||
return func(st lipgloss.Style, s string) string {
|
||||
if !color || s == "" {
|
||||
return s
|
||||
}
|
||||
return st.Render(s)
|
||||
}
|
||||
}
|
||||
@@ -11,22 +11,39 @@ import (
|
||||
|
||||
func TestListNames(t *testing.T) {
|
||||
e := listing.Entry{Provider: "azure", Path: []string{"platform-prod", "kv-app-prod", "db-password"}, AccountLabel: "alice"}
|
||||
if got := nameOf(e, styles.Default(), icons.None, false); got != "platform-prod / kv-app-prod / db-password · alice" {
|
||||
if got := nameOf(e, styles.Default(), false); got != "platform-prod / kv-app-prod / db-password · alice" {
|
||||
t.Errorf("plain: %q", got)
|
||||
}
|
||||
got := nameOf(e, styles.Default(), icons.NerdFont, true)
|
||||
if !strings.HasPrefix(got, "\x1b[") || !strings.Contains(got, icons.NerdFont.Azure) {
|
||||
t.Errorf("no colour or icon: %q", got)
|
||||
}
|
||||
// The item's own name isn't muted with its path.
|
||||
if !strings.Contains(got, "\x1b[mdb-password") {
|
||||
if got := nameOf(e, styles.Default(), true); !strings.Contains(got, "\x1b[mdb-password") {
|
||||
t.Errorf("name not set apart: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListErrorNames(t *testing.T) {
|
||||
func TestListMarks(t *testing.T) {
|
||||
az := icons.NerdFont.Azure
|
||||
for _, c := range []struct {
|
||||
e listing.Entry
|
||||
stars, color bool
|
||||
set icons.Set
|
||||
want string
|
||||
}{
|
||||
{listing.Entry{Provider: "azure", Starred: true}, true, false, icons.NerdFont, "★ " + az},
|
||||
{listing.Entry{Provider: "azure"}, true, false, icons.NerdFont, " " + az}, // as wide as ★
|
||||
{listing.Entry{Provider: "azure", Starred: true}, false, false, icons.NerdFont, az},
|
||||
{listing.Entry{Provider: "azure", Starred: true}, true, false, icons.None, "★"},
|
||||
{listing.Entry{Provider: "azure", Starred: true}, false, false, icons.None, ""},
|
||||
{listing.Entry{Provider: "azure", Starred: true}, true, true, icons.None, styles.Default().Favourite.Render("★")},
|
||||
} {
|
||||
if got := markOf(c.e, styles.Default(), c.set, c.stars, c.color); got != c.want {
|
||||
t.Errorf("markOf(starred %v, stars %v, colour %v) = %q, want %q", c.e.Starred, c.stars, c.color, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListErrors(t *testing.T) {
|
||||
e := listing.Entry{Provider: "azure", Path: []string{"platform-prod", "kv-restricted"}, Error: "403 Forbidden"}
|
||||
if got := nameOf(e, styles.Default(), icons.NerdFont, false); got != icons.NerdFont.Azure+" platform-prod / kv-restricted" {
|
||||
if got := nameOf(e, styles.Default(), false); got != "platform-prod / kv-restricted" {
|
||||
t.Errorf("the name has more: %q", got)
|
||||
}
|
||||
if got := errorOf(e, styles.Default(), false); got != "403 Forbidden" {
|
||||
@@ -39,15 +56,3 @@ func TestListErrorNames(t *testing.T) {
|
||||
t.Errorf("no error: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListStars(t *testing.T) {
|
||||
if got := starOf(listing.Entry{Starred: true}, styles.Default(), false); got != "★ " {
|
||||
t.Errorf("starred: %q", got)
|
||||
}
|
||||
if got := starOf(listing.Entry{}, styles.Default(), true); got != " " {
|
||||
t.Errorf("not starred: %q, want spaces as wide as ★", got)
|
||||
}
|
||||
if got := starOf(listing.Entry{Starred: true}, styles.Default(), true); got != styles.Default().Favourite.Render("★")+" " {
|
||||
t.Errorf("not in the favourite colour: %q", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user