implement new 'zrepl status'
Primary goals: - Scrollable output ( fixes #245 ) - Sending job signals from status view - Filtering of output by filesystem Implementation: - original TUI framework: github.com/rivo/tview - but: tview is quasi-unmaintained, didn't support some features - => use fork https://gitlab.com/tslocum/cview - however, don't buy into either too much to avoid lock-in - instead: **port over the existing status UI drawing code and adjust it to produce strings instead of directly drawing into the termbox buffer** Co-authored-by: Calistoc <calistoc@protonmail.com> Co-authored-by: InsanePrawn <insane.prawny@gmail.com> fixes #245 fixes #220
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// Package choice implements a flag.Value type that accepts a set of choices.
|
||||
//
|
||||
// See test cases or grep the code base for usage hints.
|
||||
package choices
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Choices struct {
|
||||
choices map[string]interface{}
|
||||
typeString string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
var _ flag.Value = (*Choices)(nil)
|
||||
|
||||
func new(pairs ...interface{}) Choices {
|
||||
if (len(pairs) % 2) != 0 {
|
||||
panic("must provide a sequence of key value pairs")
|
||||
}
|
||||
c := Choices{
|
||||
choices: make(map[string]interface{}, len(pairs)/2),
|
||||
value: nil,
|
||||
}
|
||||
for i := 0; i < len(pairs); {
|
||||
key, ok := pairs[i].(string)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("argument %d is %T but should be a string, value: %#v", i, pairs[i], pairs[i]))
|
||||
}
|
||||
c.choices[key] = pairs[i+1]
|
||||
i += 2
|
||||
}
|
||||
c.typeString = strings.Join(c.choicesList(true), ",") // overrideable by setter
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Choices) Init(pairs ...interface{}) {
|
||||
*c = new(pairs...)
|
||||
}
|
||||
|
||||
func (c Choices) choicesList(escaped bool) []string {
|
||||
keys := make([]string, len(c.choices))
|
||||
i := 0
|
||||
for k := range c.choices {
|
||||
e := k
|
||||
if escaped {
|
||||
e = fmt.Sprintf("%q", k)
|
||||
}
|
||||
keys[i] = e
|
||||
i += 1
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (c Choices) Usage() string {
|
||||
return fmt.Sprintf("one of %s", strings.Join(c.choicesList(true), ","))
|
||||
}
|
||||
|
||||
func (c Choices) InputForChoice(v interface{}) (string, error) {
|
||||
for input, choice := range c.choices {
|
||||
if choice == v {
|
||||
return input, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("choice not registered at .Init(): %v", v)
|
||||
}
|
||||
|
||||
func (c *Choices) SetDefaultValue(v interface{}) {
|
||||
c.value = v
|
||||
}
|
||||
|
||||
func (c Choices) Value() interface{} {
|
||||
return c.value
|
||||
}
|
||||
|
||||
func (c *Choices) Set(input string) error {
|
||||
v, ok := c.choices[input]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid value %q: must be one of %s", input, c.Usage())
|
||||
}
|
||||
c.value = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Choices) String() string {
|
||||
return "" // c.value.(fmt.Stringer).String()
|
||||
}
|
||||
|
||||
func (c *Choices) SetTypeString(ts string) {
|
||||
c.typeString = ts
|
||||
}
|
||||
|
||||
func (c *Choices) Type() string {
|
||||
return c.typeString
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package choices_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zrepl/zrepl/util/choices"
|
||||
)
|
||||
|
||||
func TestChoices(t *testing.T) {
|
||||
|
||||
var c choices.Choices
|
||||
|
||||
fs := flag.NewFlagSet("testset", flag.ContinueOnError)
|
||||
c.Init("append", os.O_APPEND, "overwrite", os.O_TRUNC|os.O_CREATE)
|
||||
fs.Var(&c, "mode", c.Usage())
|
||||
var o bytes.Buffer
|
||||
fs.SetOutput(&o)
|
||||
|
||||
fs.Usage()
|
||||
usage := o.String()
|
||||
o.Reset()
|
||||
|
||||
t.Logf("usage:\n%s", usage)
|
||||
require.Contains(t, usage, "\"append\"")
|
||||
require.Contains(t, usage, "\"overwrite\"")
|
||||
|
||||
err := fs.Parse([]string{"-mode", "append"})
|
||||
require.NoError(t, err)
|
||||
o.Reset()
|
||||
require.Equal(t, os.O_APPEND, c.Value())
|
||||
|
||||
c.SetDefaultValue(nil)
|
||||
err = fs.Parse([]string{})
|
||||
require.NoError(t, err)
|
||||
o.Reset()
|
||||
require.Nil(t, c.Value())
|
||||
|
||||
// a little whitebox testing: this is allowed ATM, we don't check that the default value was specified as a choice in init
|
||||
c.SetDefaultValue(os.O_RDWR)
|
||||
err = fs.Parse([]string{})
|
||||
require.NoError(t, err)
|
||||
o.Reset()
|
||||
require.Equal(t, os.O_RDWR, c.Value())
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user