feat: lazysecrets open --action goes, copies or shows without asking
This commit is contained in:
4 files changed
+65
-19
No files matched your search
+22
-10
@@ -90,19 +90,29 @@ func cmpOr(a, b string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// refArg parses get's and open's arguments: a reference, and optionally
|
// refArg parses get's and open's arguments: a reference, and optionally
|
||||||
// the account to use when several could (lazysecrets list's column).
|
// the account to use when several could (lazysecrets list's column) and,
|
||||||
func refArg(cmd string, args []string) (ref.Ref, error) {
|
// for open, what to do with it rather than asking (--action).
|
||||||
|
func refArg(cmd string, args []string) (r ref.Ref, action string, err error) {
|
||||||
fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
|
fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
|
||||||
account := fs.String("account", "", "the account to use when several could, as lazysecrets list prints it")
|
account := fs.String("account", "", "the account to use when several could, as lazysecrets list prints it")
|
||||||
|
usage := "usage: lazysecrets get [--account <account>] <ref>"
|
||||||
|
act := new(string)
|
||||||
|
if cmd == "open" {
|
||||||
|
act = fs.String("action", "", "do this rather than ask: go (to it), copy (go, and copy its value) or show (go, and show it)")
|
||||||
|
usage = "usage: lazysecrets open [--account <account>] [--action go|copy|show] <ref>"
|
||||||
|
}
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := fs.Parse(args); err != nil {
|
||||||
return ref.Ref{}, err
|
return r, "", err
|
||||||
}
|
}
|
||||||
if fs.NArg() != 1 {
|
if fs.NArg() != 1 {
|
||||||
return ref.Ref{}, fmt.Errorf("usage: lazysecrets %s [--account <account>] <ref>", cmd)
|
return r, "", errors.New(usage)
|
||||||
}
|
}
|
||||||
r, err := ref.Parse(fs.Arg(0))
|
if _, ok := app.OpenActions[*act]; *act != "" && !ok {
|
||||||
|
return r, "", fmt.Errorf("--action: %q is not go, copy or show", *act)
|
||||||
|
}
|
||||||
|
r, err = ref.Parse(fs.Arg(0))
|
||||||
r.Account = *account
|
r.Account = *account
|
||||||
return r, err
|
return r, *act, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultConfigPath is $LAZYSECRETS_CONFIG_FILE, else config.yaml in the
|
// defaultConfigPath is $LAZYSECRETS_CONFIG_FILE, else config.yaml in the
|
||||||
@@ -131,7 +141,7 @@ func run() error {
|
|||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(flag.CommandLine.Output(), `Usage:
|
fmt.Fprintf(flag.CommandLine.Output(), `Usage:
|
||||||
lazysecrets [flags] browse secrets
|
lazysecrets [flags] browse secrets
|
||||||
lazysecrets [flags] [open [--account <account>]] <ref>
|
lazysecrets [flags] [open [--account <account>] [--action go|copy|show]] <ref>
|
||||||
open a lazysecrets:// link (also a share link),
|
open a lazysecrets:// link (also a share link),
|
||||||
Key Vault URL, @Microsoft.KeyVault(...) or
|
Key Vault URL, @Microsoft.KeyVault(...) or
|
||||||
{{secrets/scope/key}}
|
{{secrets/scope/key}}
|
||||||
@@ -168,6 +178,7 @@ Flags:
|
|||||||
}
|
}
|
||||||
|
|
||||||
var open, getRef *ref.Ref
|
var open, getRef *ref.Ref
|
||||||
|
var openAction string
|
||||||
var shareArgs, listArgs []string
|
var shareArgs, listArgs []string
|
||||||
printKeys := false
|
printKeys := false
|
||||||
switch args := flag.Args(); {
|
switch args := flag.Args(); {
|
||||||
@@ -181,7 +192,7 @@ Flags:
|
|||||||
case len(args) == 1 && args[0] == "keybindings":
|
case len(args) == 1 && args[0] == "keybindings":
|
||||||
printKeys = true
|
printKeys = true
|
||||||
case len(args) > 0 && args[0] == "get":
|
case len(args) > 0 && args[0] == "get":
|
||||||
r, err := refArg("get", args[1:])
|
r, _, err := refArg("get", args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -194,11 +205,11 @@ Flags:
|
|||||||
return updateCmd(*configPath, args[1:])
|
return updateCmd(*configPath, args[1:])
|
||||||
case len(args) == 0:
|
case len(args) == 0:
|
||||||
case args[0] == "open" && len(args) >= 2:
|
case args[0] == "open" && len(args) >= 2:
|
||||||
r, err := refArg("open", args[1:])
|
r, act, err := refArg("open", args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
open = &r
|
open, openAction = &r, act
|
||||||
case len(args) == 1 && looksLikeRef(args[0]):
|
case len(args) == 1 && looksLikeRef(args[0]):
|
||||||
// lazysecrets <link>, like lazysecrets open <link>
|
// lazysecrets <link>, like lazysecrets open <link>
|
||||||
r, err := ref.Parse(args[0])
|
r, err := ref.Parse(args[0])
|
||||||
@@ -464,6 +475,7 @@ Flags:
|
|||||||
ShareKey: cfg.Sharing.Key,
|
ShareKey: cfg.Sharing.Key,
|
||||||
ShareRules: shareRules(cfg),
|
ShareRules: shareRules(cfg),
|
||||||
Open: open,
|
Open: open,
|
||||||
|
OpenAction: openAction,
|
||||||
Styles: &theme,
|
Styles: &theme,
|
||||||
DefaultFg: defaultFg,
|
DefaultFg: defaultFg,
|
||||||
})
|
})
|
||||||
|
|||||||
+7
-2
@@ -121,6 +121,9 @@ account) and jumps to it, asking which account to use when several can see
|
|||||||
it, ending on a secret's details as `enter` on it would. Links say what,
|
it, ending on a secret's details as `enter` on it would. Links say what,
|
||||||
not what to do: opening a link to a secret asks whether to just go to it,
|
not what to do: opening a link to a secret asks whether to just go to it,
|
||||||
also copy or show its value, or (`y`) copy its value without going there.
|
also copy or show its value, or (`y`) copy its value without going there.
|
||||||
|
`lazysecrets open --action go|copy|show <link>` does one without asking,
|
||||||
|
and `--account <account>` picks the account (as [`list`](#listing-for-fzf-and-pickers)
|
||||||
|
prints it) when several can see it.
|
||||||
|
|
||||||
## In scripts
|
## In scripts
|
||||||
|
|
||||||
@@ -153,7 +156,7 @@ instead, showing the names and acting on the links:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
lazysecrets list | fzf --ansi --with-nth='2..-3' --accept-nth=-1 \
|
lazysecrets list | fzf --ansi --with-nth='2..-3' --accept-nth=-1 \
|
||||||
--bind 'ctrl-o:become(lazysecrets open --account {-2} {-1})' \
|
--bind 'ctrl-o:become(lazysecrets open --action go --account {-2} {-1})' \
|
||||||
--bind 'ctrl-y:execute-silent(lazysecrets get --account {-2} {-1} | pbcopy)'
|
--bind 'ctrl-y:execute-silent(lazysecrets get --account {-2} {-1} | pbcopy)'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -170,7 +173,9 @@ clipboard after `values.clipboardClear`, as one copied in lazysecrets is.
|
|||||||
`--account` makes `open` and `get` use the account it was listed through,
|
`--account` makes `open` and `get` use the account it was listed through,
|
||||||
when several of yours can see it: without, `open` asks which, and `get`
|
when several of yours can see it: without, `open` asks which, and `get`
|
||||||
tries the one that last read it first. The link stays as it is, to copy
|
tries the one that last read it first. The link stays as it is, to copy
|
||||||
and share; the account isn't part of it.
|
and share; the account isn't part of it. `--action` tells `open` what to
|
||||||
|
do rather than asking, as it does for a link clicked elsewhere: `go` to
|
||||||
|
it, `copy` its value too, or `show` it.
|
||||||
|
|
||||||
What's [cached](config.md#cache) prints at once. Then, like Go to, it loads
|
What's [cached](config.md#cache) prints at once. Then, like Go to, it loads
|
||||||
what's missing or older than `--max-age` (2m), and what's new in it follows
|
what's missing or older than `--max-age` (2m), and what's new in it follows
|
||||||
|
|||||||
+23
-7
@@ -133,6 +133,9 @@ type Options struct {
|
|||||||
NoHistory bool
|
NoHistory bool
|
||||||
// Open is a reference to open at startup (lazysecrets open <ref>).
|
// Open is a reference to open at startup (lazysecrets open <ref>).
|
||||||
Open *ref.Ref
|
Open *ref.Ref
|
||||||
|
// OpenAction, if set, is what to do with Open, rather than asking:
|
||||||
|
// go to it, and maybe copy or show its value (OpenActions).
|
||||||
|
OpenAction string
|
||||||
// Styles, if set, replaces the default theme (styles.FromTheme), and
|
// Styles, if set, replaces the default theme (styles.FromTheme), and
|
||||||
// DefaultFg the terminal's default text colour.
|
// DefaultFg the terminal's default text colour.
|
||||||
Styles *styles.Styles
|
Styles *styles.Styles
|
||||||
@@ -283,6 +286,7 @@ type Model struct {
|
|||||||
prompt *modal // popup that has the keyboard
|
prompt *modal // popup that has the keyboard
|
||||||
linked []provider.Container // vaults opened from links, this session
|
linked []provider.Container // vaults opened from links, this session
|
||||||
open *ref.Ref // reference to open at startup
|
open *ref.Ref // reference to open at startup
|
||||||
|
openAction string // and what to do with it, or ask
|
||||||
|
|
||||||
shareKey string // the config's sharing.key
|
shareKey string // the config's sharing.key
|
||||||
shareRules share.Rules // and sharing.keyRules
|
shareRules share.Rules // and sharing.keyRules
|
||||||
@@ -447,7 +451,7 @@ func New(providers []provider.Provider, opts Options) Model {
|
|||||||
}
|
}
|
||||||
m.defaultFg = opts.DefaultFg
|
m.defaultFg = opts.DefaultFg
|
||||||
m.help = newHelp(m.st)
|
m.help = newHelp(m.st)
|
||||||
m.open = opts.Open
|
m.open, m.openAction = opts.Open, opts.OpenAction
|
||||||
saved := m.store.Layout()
|
saved := m.store.Layout()
|
||||||
m.split, m.heights = saved.Split, saved.Heights
|
m.split, m.heights = saved.Split, saved.Heights
|
||||||
|
|
||||||
@@ -521,18 +525,26 @@ func (m Model) Init() tea.Cmd {
|
|||||||
cmds = append(cmds, m.fetchLatest(false))
|
cmds = append(cmds, m.fetchLatest(false))
|
||||||
}
|
}
|
||||||
if m.open != nil {
|
if m.open != nil {
|
||||||
r := *m.open
|
msg := openRefMsg{ref: *m.open, action: m.openAction}
|
||||||
cmds = append(cmds, func() tea.Msg { return openRefMsg{r} })
|
cmds = append(cmds, func() tea.Msg { return msg })
|
||||||
}
|
}
|
||||||
return tea.Batch(cmds...)
|
return tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// openRefMsg starts opening a reference from outside the key handler.
|
// openRefMsg starts opening a reference from outside the key handler:
|
||||||
type openRefMsg struct{ ref ref.Ref }
|
// asking what to do with it, or doing action (OpenActions).
|
||||||
|
type openRefMsg struct {
|
||||||
|
ref ref.Ref
|
||||||
|
action string
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenActions are what lazysecrets open --action takes, the Open link
|
||||||
|
// menu's choices that go there.
|
||||||
|
var OpenActions = map[string]linkAction{"go": actGo, "copy": actCopy, "show": actShow}
|
||||||
|
|
||||||
// OpenLink is a message offering r in the Open link popup, for links
|
// OpenLink is a message offering r in the Open link popup, for links
|
||||||
// handed over by the link handler (Program.Send).
|
// handed over by the link handler (Program.Send).
|
||||||
func OpenLink(r ref.Ref) tea.Msg { return openRefMsg{r} }
|
func OpenLink(r ref.Ref) tea.Msg { return openRefMsg{ref: r} }
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
@@ -589,7 +601,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
case openRefMsg:
|
case openRefMsg:
|
||||||
m.deferHandlerSetup() // the link first
|
m.deferHandlerSetup() // the link first
|
||||||
cmds = append(cmds, m.offerRef("Open link", msg.ref, false))
|
if act, ok := OpenActions[msg.action]; ok && !msg.ref.IsShare() {
|
||||||
|
cmds = append(cmds, m.openRef(msg.ref, act))
|
||||||
|
} else {
|
||||||
|
cmds = append(cmds, m.offerRef("Open link", msg.ref, false))
|
||||||
|
}
|
||||||
|
|
||||||
case resolvedMsg:
|
case resolvedMsg:
|
||||||
m.handleResolved(msg)
|
m.handleResolved(msg)
|
||||||
|
|||||||
@@ -206,6 +206,19 @@ func TestOpenPromptAndStartupOpen(t *testing.T) {
|
|||||||
if _, _, s := d2.selected(); s != "api-key" {
|
if _, _, s := d2.selected(); s != "api-key" {
|
||||||
t.Errorf("open at startup: selected %q", s)
|
t.Errorf("open at startup: selected %q", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --action: no question, it does that.
|
||||||
|
d3 := newDriverOpts(t, Options{Open: &r, OpenAction: "go"})
|
||||||
|
if d3.m.prompt != nil {
|
||||||
|
t.Fatalf("--action go asked: %q", d3.m.prompt.title)
|
||||||
|
}
|
||||||
|
if _, _, s := d3.selected(); s != "api-key" {
|
||||||
|
t.Errorf("--action go: selected %q", s)
|
||||||
|
}
|
||||||
|
d4 := newDriverOpts(t, Options{Open: &r, OpenAction: "copy"})
|
||||||
|
if _, _, s := d4.selected(); s != "api-key" || d4.clip().content == "" {
|
||||||
|
t.Errorf("--action copy: selected %q, copied %q", s, d4.clip().content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShareLinks(t *testing.T) {
|
func TestShareLinks(t *testing.T) {
|
||||||
|
|||||||
Reference in new issue
Block a user