feat: lazysecrets list colours names from the theme, with icons
This commit is contained in:
4 files changed
+83
-10
No files matched your search
+48
-7
@@ -13,38 +13,50 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
|
||||||
"git.kebcloud.no/keb/lazysecrets/internal/cachefile"
|
"git.kebcloud.no/keb/lazysecrets/internal/cachefile"
|
||||||
"git.kebcloud.no/keb/lazysecrets/internal/listing"
|
"git.kebcloud.no/keb/lazysecrets/internal/listing"
|
||||||
"git.kebcloud.no/keb/lazysecrets/internal/provider"
|
"git.kebcloud.no/keb/lazysecrets/internal/provider"
|
||||||
|
"git.kebcloud.no/keb/lazysecrets/internal/ui/icons"
|
||||||
|
"git.kebcloud.no/keb/lazysecrets/internal/ui/styles"
|
||||||
)
|
)
|
||||||
|
|
||||||
// listCmd prints every source, vault and secret lazysecrets can find, one
|
// listCmd prints every source, vault and secret lazysecrets can find, one
|
||||||
// per line, for fzf, editor pickers and scripts:
|
// per line, for fzf, editor pickers and scripts:
|
||||||
//
|
//
|
||||||
// lazysecrets list | fzf --delimiter='\t' --with-nth=3 | cut -f1
|
// lazysecrets list | fzf --ansi --delimiter='\t' --with-nth=3 | cut -f1
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
//
|
//
|
||||||
// What's cached prints at once; what's missing or older than --max-age
|
// What's cached prints at once; what's missing or older than --max-age
|
||||||
// follows as it loads. Lists that can't be read are reported on stderr.
|
// follows as it loads. Lists that can't be read are reported on stderr.
|
||||||
func listCmd(providers []provider.Provider, cachePath string, timeout func(provider.Kind) time.Duration, args []string, out, errOut io.Writer) error {
|
func listCmd(providers []provider.Provider, cachePath string, timeout func(provider.Kind) time.Duration, theme styles.Styles, withIcons bool, args []string, out, errOut io.Writer) error {
|
||||||
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
||||||
cached := fs.Bool("cached", false, "only what's cached, and repos: load nothing from Azure or Databricks")
|
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")
|
maxAge := fs.Duration("max-age", 2*time.Minute, "load cached lists older than this again")
|
||||||
format := fs.String("format", "text", "text: link, kind and name, tab-separated; json: an object per line")
|
format := fs.String("format", "text", "text: link, kind and name, 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)")
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := fs.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if fs.NArg() > 0 {
|
if fs.NArg() > 0 {
|
||||||
return fmt.Errorf("usage: lazysecrets list [--cached] [--max-age 2m] [--format text|json]")
|
return fmt.Errorf("usage: lazysecrets list [--cached] [--max-age 2m] [--format text|json]")
|
||||||
}
|
}
|
||||||
|
if *color != "always" && *color != "never" {
|
||||||
|
return fmt.Errorf("--color: %q is not always or never", *color)
|
||||||
|
}
|
||||||
|
iconSet := icons.None
|
||||||
|
if *iconsOn {
|
||||||
|
iconSet = icons.NerdFont
|
||||||
|
}
|
||||||
var write func(listing.Entry) error
|
var write func(listing.Entry) error
|
||||||
switch *format {
|
switch *format {
|
||||||
case "text":
|
case "text":
|
||||||
write = func(e listing.Entry) error {
|
write = func(e listing.Entry) error {
|
||||||
name := strings.Join(e.Path, " / ")
|
_, err := fmt.Fprintf(out, "%s\t%s\t%s\n", e.Link, e.Kind, nameOf(e, theme, iconSet, *color == "always"))
|
||||||
if e.Account != "" {
|
|
||||||
name += " · " + e.Account
|
|
||||||
}
|
|
||||||
_, err := fmt.Fprintf(out, "%s\t%s\t%s\n", e.Link, e.Kind, name)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "json":
|
case "json":
|
||||||
@@ -95,3 +107,32 @@ func listCmd(providers []provider.Provider, cachePath string, timeout func(provi
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
var name string
|
||||||
|
if n := len(e.Path); n > 0 {
|
||||||
|
if n > 1 {
|
||||||
|
name = render(theme.Muted, strings.Join(e.Path[:n-1], " / ")+" / ")
|
||||||
|
}
|
||||||
|
name += e.Path[n-1]
|
||||||
|
}
|
||||||
|
if e.Account != "" {
|
||||||
|
name += render(theme.Muted, " · "+e.Account)
|
||||||
|
}
|
||||||
|
return render(theme.Kind(kind), strings.TrimSuffix(icon, " ")) + strings.Repeat(" ", min(len(icon), 1)) + name
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.kebcloud.no/keb/lazysecrets/internal/listing"
|
||||||
|
"git.kebcloud.no/keb/lazysecrets/internal/ui/icons"
|
||||||
|
"git.kebcloud.no/keb/lazysecrets/internal/ui/styles"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListNames(t *testing.T) {
|
||||||
|
e := listing.Entry{Provider: "azure", Path: []string{"platform-prod", "kv-app-prod", "db-password"}, Account: "alice"}
|
||||||
|
if got := nameOf(e, styles.Default(), icons.None, 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") {
|
||||||
|
t.Errorf("name not set apart: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,6 +122,7 @@ func run() error {
|
|||||||
lazysecrets [flags] get <ref> print the value a lazysecrets:// link points at,
|
lazysecrets [flags] get <ref> print the value a lazysecrets:// link points at,
|
||||||
for scripts: VAR=$(lazysecrets get <ref>)
|
for scripts: VAR=$(lazysecrets get <ref>)
|
||||||
lazysecrets [flags] list [--cached] [--max-age 2m] [--format text|json]
|
lazysecrets [flags] list [--cached] [--max-age 2m] [--format text|json]
|
||||||
|
[--color always|never] [--icons=false]
|
||||||
print every source, vault and secret it can
|
print every source, vault and secret it can
|
||||||
find, one per line, for fzf and pickers:
|
find, one per line, for fzf and pickers:
|
||||||
what's cached at once, then what loads
|
what's cached at once, then what loads
|
||||||
@@ -356,7 +357,7 @@ Flags:
|
|||||||
if d, err := config.CacheDir(); err == nil && !*demo && config.On(cfg.Storage.Cache) {
|
if d, err := config.CacheDir(); err == nil && !*demo && config.On(cfg.Storage.Cache) {
|
||||||
cachePath = filepath.Join(d, "cache.json")
|
cachePath = filepath.Join(d, "cache.json")
|
||||||
}
|
}
|
||||||
return listCmd(providers, cachePath, timeoutFor, listArgs, os.Stdout, os.Stderr)
|
return listCmd(providers, cachePath, timeoutFor, theme, *icons, listArgs, os.Stdout, os.Stderr)
|
||||||
}
|
}
|
||||||
if shareArgs != nil {
|
if shareArgs != nil {
|
||||||
// The files from a repo, the key from whichever provider it's
|
// The files from a repo, the key from whichever provider it's
|
||||||
|
|||||||
+8
-2
@@ -144,7 +144,7 @@ and its name, tab-separated. It's what Go to searches, so fzf, or an
|
|||||||
editor's picker, can search it instead:
|
editor's picker, can search it instead:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
lazysecrets list | fzf --delimiter='\t' --with-nth=3 \
|
lazysecrets list | fzf --ansi --delimiter='\t' --with-nth=3 \
|
||||||
--bind 'enter:become(lazysecrets open {1})' \
|
--bind 'enter:become(lazysecrets open {1})' \
|
||||||
--bind 'ctrl-y:execute-silent(lazysecrets get {1} | pbcopy)'
|
--bind 'ctrl-y:execute-silent(lazysecrets get {1} | pbcopy)'
|
||||||
```
|
```
|
||||||
@@ -156,7 +156,13 @@ Azure or Databricks, for an answer at once. Repos are always read, from
|
|||||||
disk. What it loads is cached for the TUI too. Lists it can't read are
|
disk. What it loads is cached for the TUI too. Lists it can't read are
|
||||||
reported on stderr; it stops when the reader goes, after a pick in fzf.
|
reported on stderr; it stops when the reader goes, after a pick in fzf.
|
||||||
|
|
||||||
`--format json` prints an object per line instead, for editor plugins:
|
The name is coloured from your [theme](config.md#theme), with the
|
||||||
|
provider's icon when [icons](config.md#theme) are on (`ui.icons`); fzf
|
||||||
|
shows the colours with `--ansi`. `--color never` leaves them out, for
|
||||||
|
scripts, and `--icons=false` the icons. The link is never coloured.
|
||||||
|
|
||||||
|
`--format json` prints an object per line instead, for editor plugins,
|
||||||
|
without colours or icons:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"link":"lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password","kind":"secret","provider":"azure","path":["platform-prod","kv-app-prod","db-password"],"account":"alice"}
|
{"link":"lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password","kind":"secret","provider":"azure","path":["platform-prod","kv-app-prod","db-password"],"account":"alice"}
|
||||||
|
|||||||
Reference in new issue
Block a user