mirror of
https://github.com/FiloSottile/age.git
synced 2026-03-11 08:55:41 +00:00
feat: move tui code to tui package
Makes PluginTerminalUI available for sops (and other third parties) so they can more easily support plugins.
This commit is contained in:
parent
482cf6fc9b
commit
266c094091
4 changed files with 89 additions and 85 deletions
|
|
@ -7,6 +7,7 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"filippo.io/age/tui"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -104,7 +105,7 @@ func main() {
|
|||
|
||||
if len(os.Args) == 1 {
|
||||
flag.Usage()
|
||||
exit(1)
|
||||
tui.Exit(1)
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -180,47 +181,47 @@ func main() {
|
|||
hints = append(hints, "only a single input file may be specified at a time")
|
||||
}
|
||||
|
||||
errorWithHint("too many INPUT arguments: "+quotedArgs, hints...)
|
||||
tui.ErrorWithHint("too many INPUT arguments: "+quotedArgs, hints...)
|
||||
}
|
||||
|
||||
switch {
|
||||
case decryptFlag:
|
||||
if encryptFlag {
|
||||
errorf("-e/--encrypt can't be used with -d/--decrypt")
|
||||
tui.Errorf("-e/--encrypt can't be used with -d/--decrypt")
|
||||
}
|
||||
if armorFlag {
|
||||
errorWithHint("-a/--armor can't be used with -d/--decrypt",
|
||||
tui.ErrorWithHint("-a/--armor can't be used with -d/--decrypt",
|
||||
"note that armored files are detected automatically")
|
||||
}
|
||||
if passFlag {
|
||||
errorWithHint("-p/--passphrase can't be used with -d/--decrypt",
|
||||
tui.ErrorWithHint("-p/--passphrase can't be used with -d/--decrypt",
|
||||
"note that password protected files are detected automatically")
|
||||
}
|
||||
if len(recipientFlags) > 0 {
|
||||
errorWithHint("-r/--recipient can't be used with -d/--decrypt",
|
||||
tui.ErrorWithHint("-r/--recipient can't be used with -d/--decrypt",
|
||||
"did you mean to use -i/--identity to specify a private key?")
|
||||
}
|
||||
if len(recipientsFileFlags) > 0 {
|
||||
errorWithHint("-R/--recipients-file can't be used with -d/--decrypt",
|
||||
tui.ErrorWithHint("-R/--recipients-file can't be used with -d/--decrypt",
|
||||
"did you mean to use -i/--identity to specify a private key?")
|
||||
}
|
||||
default: // encrypt
|
||||
if len(identityFlags) > 0 && !encryptFlag {
|
||||
errorWithHint("-i/--identity and -j can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt",
|
||||
tui.ErrorWithHint("-i/--identity and -j can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt",
|
||||
"did you forget to specify -d/--decrypt?")
|
||||
}
|
||||
if len(recipientFlags)+len(recipientsFileFlags)+len(identityFlags) == 0 && !passFlag {
|
||||
errorWithHint("missing recipients",
|
||||
tui.ErrorWithHint("missing recipients",
|
||||
"did you forget to specify -r/--recipient, -R/--recipients-file or -p/--passphrase?")
|
||||
}
|
||||
if len(recipientFlags) > 0 && passFlag {
|
||||
errorf("-p/--passphrase can't be combined with -r/--recipient")
|
||||
tui.Errorf("-p/--passphrase can't be combined with -r/--recipient")
|
||||
}
|
||||
if len(recipientsFileFlags) > 0 && passFlag {
|
||||
errorf("-p/--passphrase can't be combined with -R/--recipients-file")
|
||||
tui.Errorf("-p/--passphrase can't be combined with -R/--recipients-file")
|
||||
}
|
||||
if len(identityFlags) > 0 && passFlag {
|
||||
errorf("-p/--passphrase can't be combined with -i/--identity and -j")
|
||||
tui.Errorf("-p/--passphrase can't be combined with -i/--identity and -j")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,7 +242,7 @@ func main() {
|
|||
inUseFiles = append(inUseFiles, absPath(name))
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
errorf("failed to open input file %q: %v", name, err)
|
||||
tui.Errorf("failed to open input file %q: %v", name, err)
|
||||
}
|
||||
defer f.Close()
|
||||
in = f
|
||||
|
|
@ -251,9 +252,9 @@ func main() {
|
|||
// If the input comes from a TTY, assume it's armored, and buffer up
|
||||
// to the END line (or EOF/EOT) so that a password prompt or the
|
||||
// output don't get in the way of typing the input. See Issue 364.
|
||||
buf, err := bufferTerminalInput(in)
|
||||
buf, err := tui.BufferTerminalInput(in)
|
||||
if err != nil {
|
||||
errorf("failed to buffer terminal input: %v", err)
|
||||
tui.Errorf("failed to buffer terminal input: %v", err)
|
||||
}
|
||||
in = buf
|
||||
}
|
||||
|
|
@ -261,13 +262,13 @@ func main() {
|
|||
if name := outFlag; name != "" && name != "-" {
|
||||
for _, f := range inUseFiles {
|
||||
if f == absPath(name) {
|
||||
errorf("input and output file are the same: %q", name)
|
||||
tui.Errorf("input and output file are the same: %q", name)
|
||||
}
|
||||
}
|
||||
f := newLazyOpener(name)
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
errorf("failed to close output file %q: %v", name, err)
|
||||
tui.Errorf("failed to close output file %q: %v", name, err)
|
||||
}
|
||||
}()
|
||||
out = f
|
||||
|
|
@ -278,7 +279,7 @@ func main() {
|
|||
} else if !armorFlag {
|
||||
// If the output wouldn't be armored, refuse to send binary to
|
||||
// the terminal unless explicitly requested with "-o -".
|
||||
errorWithHint("refusing to output binary to the terminal",
|
||||
tui.ErrorWithHint("refusing to output binary to the terminal",
|
||||
"did you mean to use -a/--armor?",
|
||||
`force anyway with "-o -"`)
|
||||
}
|
||||
|
|
@ -305,7 +306,7 @@ func main() {
|
|||
}
|
||||
|
||||
func passphrasePromptForEncryption() (string, error) {
|
||||
pass, err := readSecret("Enter passphrase (leave empty to autogenerate a secure one):")
|
||||
pass, err := tui.ReadSecret("Enter passphrase (leave empty to autogenerate a secure one):")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
|
|
@ -316,12 +317,12 @@ func passphrasePromptForEncryption() (string, error) {
|
|||
words = append(words, randomWord())
|
||||
}
|
||||
p = strings.Join(words, "-")
|
||||
err := printfToTerminal("using autogenerated passphrase %q", p)
|
||||
err := tui.PrintfToTerminal("using autogenerated passphrase %q", p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not print passphrase: %v", err)
|
||||
}
|
||||
} else {
|
||||
confirm, err := readSecret("Confirm passphrase:")
|
||||
confirm, err := tui.ReadSecret("Confirm passphrase:")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
|
|
@ -337,19 +338,19 @@ func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader
|
|||
for _, arg := range recs {
|
||||
r, err := parseRecipient(arg)
|
||||
if err, ok := err.(gitHubRecipientError); ok {
|
||||
errorWithHint(err.Error(), "instead, use recipient files like",
|
||||
tui.ErrorWithHint(err.Error(), "instead, use recipient files like",
|
||||
" curl -O https://github.com/"+err.username+".keys",
|
||||
" age -R "+err.username+".keys")
|
||||
}
|
||||
if err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
recipients = append(recipients, r)
|
||||
}
|
||||
for _, name := range files {
|
||||
recs, err := parseRecipientsFile(name)
|
||||
if err != nil {
|
||||
errorf("failed to parse recipient file %q: %v", name, err)
|
||||
tui.Errorf("failed to parse recipient file %q: %v", name, err)
|
||||
}
|
||||
recipients = append(recipients, recs...)
|
||||
}
|
||||
|
|
@ -358,17 +359,17 @@ func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader
|
|||
case "i":
|
||||
ids, err := parseIdentitiesFile(f.Value)
|
||||
if err != nil {
|
||||
errorf("reading %q: %v", f.Value, err)
|
||||
tui.Errorf("reading %q: %v", f.Value, err)
|
||||
}
|
||||
r, err := identitiesToRecipients(ids)
|
||||
if err != nil {
|
||||
errorf("internal error processing %q: %v", f.Value, err)
|
||||
tui.Errorf("internal error processing %q: %v", f.Value, err)
|
||||
}
|
||||
recipients = append(recipients, r...)
|
||||
case "j":
|
||||
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
|
||||
id, err := plugin.NewIdentityWithoutData(f.Value, tui.PluginTerminalUI)
|
||||
if err != nil {
|
||||
errorf("initializing %q: %v", f.Value, err)
|
||||
tui.Errorf("initializing %q: %v", f.Value, err)
|
||||
}
|
||||
recipients = append(recipients, id.Recipient())
|
||||
}
|
||||
|
|
@ -379,12 +380,12 @@ func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader
|
|||
func encryptPass(in io.Reader, out io.Writer, armor bool) {
|
||||
pass, err := passphrasePromptForEncryption()
|
||||
if err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
|
||||
r, err := age.NewScryptRecipient(pass)
|
||||
if err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
testOnlyConfigureScryptIdentity(r)
|
||||
encrypt([]age.Recipient{r}, in, out, armor)
|
||||
|
|
@ -397,20 +398,20 @@ func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor
|
|||
a := armor.NewWriter(out)
|
||||
defer func() {
|
||||
if err := a.Close(); err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
}()
|
||||
out = a
|
||||
}
|
||||
w, err := age.Encrypt(out, recipients...)
|
||||
if err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
if _, err := io.Copy(w, in); err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -426,7 +427,7 @@ func (rejectScryptIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {
|
|||
if len(stanzas) != 1 || stanzas[0].Type != "scrypt" {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
errorWithHint("file is passphrase-encrypted but identities were specified with -i/--identity or -j",
|
||||
tui.ErrorWithHint("file is passphrase-encrypted but identities were specified with -i/--identity or -j",
|
||||
"remove all -i/--identity/-j flags to decrypt passphrase-encrypted files")
|
||||
panic("unreachable")
|
||||
}
|
||||
|
|
@ -439,13 +440,13 @@ func decryptNotPass(flags identityFlags, in io.Reader, out io.Writer) {
|
|||
case "i":
|
||||
ids, err := parseIdentitiesFile(f.Value)
|
||||
if err != nil {
|
||||
errorf("reading %q: %v", f.Value, err)
|
||||
tui.Errorf("reading %q: %v", f.Value, err)
|
||||
}
|
||||
identities = append(identities, ids...)
|
||||
case "j":
|
||||
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
|
||||
id, err := plugin.NewIdentityWithoutData(f.Value, tui.PluginTerminalUI)
|
||||
if err != nil {
|
||||
errorf("initializing %q: %v", f.Value, err)
|
||||
tui.Errorf("initializing %q: %v", f.Value, err)
|
||||
}
|
||||
identities = append(identities, id)
|
||||
}
|
||||
|
|
@ -468,7 +469,7 @@ func decrypt(identities []age.Identity, in io.Reader, out io.Writer) {
|
|||
rr := bufio.NewReader(in)
|
||||
if intro, _ := rr.Peek(len(crlfMangledIntro)); string(intro) == crlfMangledIntro ||
|
||||
string(intro) == utf16MangledIntro {
|
||||
errorWithHint("invalid header intro",
|
||||
tui.ErrorWithHint("invalid header intro",
|
||||
"it looks like this file was corrupted by PowerShell redirection",
|
||||
"consider using -o or -a to encrypt files in PowerShell")
|
||||
}
|
||||
|
|
@ -481,16 +482,16 @@ func decrypt(identities []age.Identity, in io.Reader, out io.Writer) {
|
|||
|
||||
r, err := age.Decrypt(in, identities...)
|
||||
if err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
out.Write(nil) // trigger the lazyOpener even if r is empty
|
||||
if _, err := io.Copy(out, r); err != nil {
|
||||
errorf("%v", err)
|
||||
tui.Errorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func passphrasePromptForDecryption() (string, error) {
|
||||
pass, err := readSecret("Enter passphrase:")
|
||||
pass, err := tui.ReadSecret("Enter passphrase:")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package main
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"filippo.io/age/tui"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
|
@ -16,9 +17,9 @@ import (
|
|||
func TestMain(m *testing.M) {
|
||||
os.Exit(testscript.RunMain(m, map[string]func() int{
|
||||
"age": func() (exitCode int) {
|
||||
testOnlyPanicInsteadOfExit = true
|
||||
tui.TestOnlyPanicInsteadOfExit = true
|
||||
defer func() {
|
||||
if testOnlyDidExit {
|
||||
if tui.TestOnlyDidExit {
|
||||
exitCode = recover().(int)
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"filippo.io/age/tui"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
|
@ -31,7 +32,7 @@ func (gitHubRecipientError) Error() string {
|
|||
func parseRecipient(arg string) (age.Recipient, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "age1") && strings.Count(arg, "1") > 1:
|
||||
return plugin.NewRecipient(arg, pluginTerminalUI)
|
||||
return plugin.NewRecipient(arg, tui.PluginTerminalUI)
|
||||
case strings.HasPrefix(arg, "age1"):
|
||||
return age.ParseX25519Recipient(arg)
|
||||
case strings.HasPrefix(arg, "ssh-"):
|
||||
|
|
@ -79,7 +80,7 @@ func parseRecipientsFile(name string) ([]age.Recipient, error) {
|
|||
if err != nil {
|
||||
if t, ok := sshKeyType(line); ok {
|
||||
// Skip unsupported but valid SSH public keys with a warning.
|
||||
warningf("recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
|
||||
tui.Warningf("recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
|
||||
continue
|
||||
}
|
||||
// Hide the error since it might unintentionally leak the contents
|
||||
|
|
@ -162,14 +163,14 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
|||
return []age.Identity{&EncryptedIdentity{
|
||||
Contents: contents,
|
||||
Passphrase: func() (string, error) {
|
||||
pass, err := readSecret(fmt.Sprintf("Enter passphrase for identity file %q:", name))
|
||||
pass, err := tui.ReadSecret(fmt.Sprintf("Enter passphrase for identity file %q:", name))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
return string(pass), nil
|
||||
},
|
||||
NoMatchWarning: func() {
|
||||
warningf("encrypted identity file %q didn't match file's recipients", name)
|
||||
tui.Warningf("encrypted identity file %q didn't match file's recipients", name)
|
||||
},
|
||||
}}, nil
|
||||
|
||||
|
|
@ -198,7 +199,7 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
|||
func parseIdentity(s string) (age.Identity, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "AGE-PLUGIN-"):
|
||||
return plugin.NewIdentity(s, pluginTerminalUI)
|
||||
return plugin.NewIdentity(s, tui.PluginTerminalUI)
|
||||
case strings.HasPrefix(s, "AGE-SECRET-KEY-1"):
|
||||
return age.ParseX25519Identity(s)
|
||||
default:
|
||||
|
|
@ -246,7 +247,7 @@ func parseSSHIdentity(name string, pemBytes []byte) ([]age.Identity, error) {
|
|||
}
|
||||
}
|
||||
passphrasePrompt := func() ([]byte, error) {
|
||||
pass, err := readSecret(fmt.Sprintf("Enter passphrase for %q:", name))
|
||||
pass, err := tui.ReadSecret(fmt.Sprintf("Enter passphrase for %q:", name))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read passphrase for %q: %v", name, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
package tui
|
||||
|
||||
// This file implements the terminal UI of cmd/age. The rules are:
|
||||
//
|
||||
|
|
@ -30,46 +30,47 @@ import (
|
|||
// l is a logger with no prefixes.
|
||||
var l = log.New(os.Stderr, "", 0)
|
||||
|
||||
func printf(format string, v ...interface{}) {
|
||||
func Printf(format string, v ...interface{}) {
|
||||
l.Printf("age: "+format, v...)
|
||||
}
|
||||
|
||||
func errorf(format string, v ...interface{}) {
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
l.Printf("age: error: "+format, v...)
|
||||
l.Printf("age: report unexpected or unhelpful errors at https://filippo.io/age/report")
|
||||
exit(1)
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func warningf(format string, v ...interface{}) {
|
||||
func Warningf(format string, v ...interface{}) {
|
||||
l.Printf("age: warning: "+format, v...)
|
||||
}
|
||||
|
||||
func errorWithHint(error string, hints ...string) {
|
||||
func ErrorWithHint(error string, hints ...string) {
|
||||
l.Printf("age: error: %s", error)
|
||||
for _, hint := range hints {
|
||||
l.Printf("age: hint: %s", hint)
|
||||
}
|
||||
l.Printf("age: report unexpected or unhelpful errors at https://filippo.io/age/report")
|
||||
exit(1)
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
// If testOnlyPanicInsteadOfExit is true, exit will set testOnlyDidExit and
|
||||
// If TestOnlyPanicInsteadOfExit is true, exit will set TestOnlyDidExit and
|
||||
// panic instead of calling os.Exit. This way, the wrapper in TestMain can
|
||||
// recover the panic and return the exit code only if it was originated in exit.
|
||||
var testOnlyPanicInsteadOfExit bool
|
||||
var testOnlyDidExit bool
|
||||
|
||||
func exit(code int) {
|
||||
if testOnlyPanicInsteadOfExit {
|
||||
testOnlyDidExit = true
|
||||
var TestOnlyPanicInsteadOfExit bool
|
||||
var TestOnlyDidExit bool
|
||||
|
||||
func Exit(code int) {
|
||||
if TestOnlyPanicInsteadOfExit {
|
||||
TestOnlyDidExit = true
|
||||
panic(code)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// clearLine clears the current line on the terminal, or opens a new line if
|
||||
// ClearLine clears the current line on the terminal, or opens a new line if
|
||||
// terminal escape codes don't work.
|
||||
func clearLine(out io.Writer) {
|
||||
func ClearLine(out io.Writer) {
|
||||
const (
|
||||
CUI = "\033[" // Control Sequence Introducer
|
||||
CPL = CUI + "F" // Cursor Previous Line
|
||||
|
|
@ -86,10 +87,10 @@ func clearLine(out io.Writer) {
|
|||
fmt.Fprintf(out, "\r\n"+CPL+EL)
|
||||
}
|
||||
|
||||
// withTerminal runs f with the terminal input and output files, if available.
|
||||
// withTerminal does not open a non-terminal stdin, so the caller does not need
|
||||
// WithTerminal runs f with the terminal input and output files, if available.
|
||||
// WithTerminal does not open a non-terminal stdin, so the caller does not need
|
||||
// to check stdinInUse.
|
||||
func withTerminal(f func(in, out *os.File) error) error {
|
||||
func WithTerminal(f func(in, out *os.File) error) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
in, err := os.OpenFile("CONIN$", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
|
|
@ -112,18 +113,18 @@ func withTerminal(f func(in, out *os.File) error) error {
|
|||
}
|
||||
}
|
||||
|
||||
func printfToTerminal(format string, v ...interface{}) error {
|
||||
return withTerminal(func(_, out *os.File) error {
|
||||
func PrintfToTerminal(format string, v ...interface{}) error {
|
||||
return WithTerminal(func(_, out *os.File) error {
|
||||
_, err := fmt.Fprintf(out, "age: "+format+"\n", v...)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// readSecret reads a value from the terminal with no echo. The prompt is ephemeral.
|
||||
func readSecret(prompt string) (s []byte, err error) {
|
||||
err = withTerminal(func(in, out *os.File) error {
|
||||
// ReadSecret reads a value from the terminal with no echo. The prompt is ephemeral.
|
||||
func ReadSecret(prompt string) (s []byte, err error) {
|
||||
err = WithTerminal(func(in, out *os.File) error {
|
||||
fmt.Fprintf(out, "%s ", prompt)
|
||||
defer clearLine(out)
|
||||
defer ClearLine(out)
|
||||
s, err = term.ReadPassword(int(in.Fd()))
|
||||
return err
|
||||
})
|
||||
|
|
@ -133,9 +134,9 @@ func readSecret(prompt string) (s []byte, err error) {
|
|||
// readCharacter reads a single character from the terminal with no echo. The
|
||||
// prompt is ephemeral.
|
||||
func readCharacter(prompt string) (c byte, err error) {
|
||||
err = withTerminal(func(in, out *os.File) error {
|
||||
err = WithTerminal(func(in, out *os.File) error {
|
||||
fmt.Fprintf(out, "%s ", prompt)
|
||||
defer clearLine(out)
|
||||
defer ClearLine(out)
|
||||
|
||||
oldState, err := term.MakeRaw(int(in.Fd()))
|
||||
if err != nil {
|
||||
|
|
@ -154,18 +155,18 @@ func readCharacter(prompt string) (c byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
var pluginTerminalUI = &plugin.ClientUI{
|
||||
var PluginTerminalUI = &plugin.ClientUI{
|
||||
DisplayMessage: func(name, message string) error {
|
||||
printf("%s plugin: %s", name, message)
|
||||
Printf("%s plugin: %s", name, message)
|
||||
return nil
|
||||
},
|
||||
RequestValue: func(name, message string, _ bool) (s string, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
warningf("could not read value for age-plugin-%s: %v", name, err)
|
||||
Warningf("could not read value for age-plugin-%s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
secret, err := readSecret(message)
|
||||
secret, err := ReadSecret(message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -174,12 +175,12 @@ var pluginTerminalUI = &plugin.ClientUI{
|
|||
Confirm: func(name, message, yes, no string) (choseYes bool, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
warningf("could not read value for age-plugin-%s: %v", name, err)
|
||||
Warningf("could not read value for age-plugin-%s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
if no == "" {
|
||||
message += fmt.Sprintf(" (press enter for %q)", yes)
|
||||
_, err := readSecret(message)
|
||||
_, err := ReadSecret(message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -199,16 +200,16 @@ var pluginTerminalUI = &plugin.ClientUI{
|
|||
case '\x03': // CTRL-C
|
||||
return false, errors.New("user cancelled prompt")
|
||||
default:
|
||||
warningf("reading value for age-plugin-%s: invalid selection %q", name, selection)
|
||||
Warningf("reading value for age-plugin-%s: invalid selection %q", name, selection)
|
||||
}
|
||||
}
|
||||
},
|
||||
WaitTimer: func(name string) {
|
||||
printf("waiting on %s plugin...", name)
|
||||
Printf("waiting on %s plugin...", name)
|
||||
},
|
||||
}
|
||||
|
||||
func bufferTerminalInput(in io.Reader) (io.Reader, error) {
|
||||
func BufferTerminalInput(in io.Reader) (io.Reader, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
if _, err := buf.ReadFrom(ReaderFunc(func(p []byte) (n int, err error) {
|
||||
if bytes.Contains(buf.Bytes(), []byte(armor.Footer+"\n")) {
|
||||
Loading…
Reference in a new issue