Skip to content

Commit

Permalink
✨ feat(interact): add new interactive component input-collector
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Dec 9, 2022
1 parent 02e21bc commit f907351
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 39 deletions.
2 changes: 1 addition & 1 deletion gflag/gflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Config struct {
// DescNewline flag desc at new line on print help
DescNewline bool
// Alignment flag name align left or right. default is: left
Alignment uint8
Alignment strutil.PosFlag
// TagName on struct
TagName string
// TagRuleType for struct tag value. default is TagRuleNamed
Expand Down
39 changes: 6 additions & 33 deletions interact/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package interact
import (
"fmt"
"os"
"strings"

"github.com/gookit/color"
"github.com/gookit/goutil/structs"
Expand All @@ -31,6 +30,12 @@ type RunFace interface {
Run() *Value
}

// ItemFace for interact methods
type ItemFace interface {
Name() string
Value() string
}

/*************************************************************
* value for select
*************************************************************/
Expand Down Expand Up @@ -84,35 +89,3 @@ func exitWithMsg(exitCode int, messages ...any) {
fmt.Println(messages...)
os.Exit(exitCode)
}

func intsToMap(is []int) map[string]string {
ms := make(map[string]string, len(is))
for i, val := range is {
k := fmt.Sprint(i)
ms[k] = fmt.Sprint(val)
}

return ms
}

func stringToArr(str, sep string) (arr []string) {
str = strings.TrimSpace(str)
ss := strings.Split(str, sep)
for _, val := range ss {
if val = strings.TrimSpace(val); val != "" {
arr = append(arr, val)
}
}

return arr
}

func stringsToMap(ss []string) map[string]string {
ms := make(map[string]string, len(ss))
for i, val := range ss {
k := fmt.Sprint(i)
ms[k] = val
}

return ms
}
106 changes: 102 additions & 4 deletions interact/collector.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,115 @@
package interact

import "github.com/gookit/goutil/maputil"
import (
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/maputil"
"github.com/gookit/goutil/structs"
"github.com/gookit/goutil/strutil"
)

// InputParameter interface
type InputParameter interface {
Type() string
Name() string
Desc() string
Value() structs.Value
Set(v string) error
Run() error
}

// Collector information collector 信息收集者
// cli input values collector
type Collector struct {
qs []*Question
ans maputil.Data
// input parameters
ps map[string]InputParameter
ret maputil.Data
err error

ns []string
}

// NewCollector instance
func NewCollector() *Collector {
return &Collector{}
return &Collector{
ps: make(map[string]InputParameter),
ret: make(maputil.Data),
}
}

// AddParams definitions at once.
func (c *Collector) AddParams(ps ...InputParameter) error {
for _, p := range ps {
if err := c.AddParam(p); err != nil {
return err
}
}
return nil
}

// Param get from collector
func (c *Collector) Param(name string) (InputParameter, bool) {
p, ok := c.ps[name]
return p, ok
}

// MustParam get from collector
func (c *Collector) MustParam(name string) InputParameter {
p, ok := c.ps[name]
if !ok {
panic("not found the param: " + name)
}

return p
}

// AddParam to collector
func (c *Collector) AddParam(p InputParameter) error {
if strutil.IsBlank(p.Name()) {
return errorx.Raw("input parameter name cannot be empty")
}

name := p.Name()
if arrutil.Contains(c.ns, name) {
return errorx.Rawf("input parameter name %s has been exists", name)
}

c.ns = append(c.ns, name)
c.ps[name] = p

return nil
}

// Results for collector
func (c *Collector) Results() maputil.Data {
return c.ret
}

// Run collector
func (c *Collector) Run() error {
if len(c.ns) == 0 {
return errorx.Raw("empty params definitions")
}

for _, name := range c.ns {
p := c.ps[name]

// has input value
if c.ret.Has(name) {
err := p.Set(c.ret.Str(name))
if err != nil {
return err
}
continue
}

// require input value
if err := p.Run(); err != nil {
return err
}

c.ret.Set(name, p.Value().V)
}

return nil
}
19 changes: 19 additions & 0 deletions interact/collector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package interact_test

import (
"testing"

"github.com/gookit/gcli/v3/interact"
"github.com/gookit/gcli/v3/interact/cparam"
"github.com/gookit/goutil/testutil/assert"
)

func TestCollector_Run(t *testing.T) {
c := interact.NewCollector()
err := c.AddParams(
cparam.NewStringParam("title", "title name"),
cparam.NewChoiceParam("projects", "select projects"),
)

assert.NoErr(t, err)
}
1 change: 1 addition & 0 deletions interact/control/control.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package control
61 changes: 61 additions & 0 deletions interact/cparam/choices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cparam

import (
"github.com/gookit/gcli/v3/interact"
"github.com/gookit/goutil/errorx"
)

// ChoiceParam definition
type ChoiceParam struct {
InputParam
// Choices for select
Choices []string
selected []string
}

// NewChoiceParam instance
func NewChoiceParam(name, desc string) *ChoiceParam {
return &ChoiceParam{
InputParam: InputParam{
typ: TypeChoicesParam,
name: name,
desc: desc,
},
}
}

// WithChoices to param definition
func (p *ChoiceParam) WithChoices(Choices []string) *ChoiceParam {
p.Choices = Choices
return p
}

// Selected values get
func (p *ChoiceParam) Selected() []string {
return p.val.Strings()
}

// Set value
func (p *ChoiceParam) Set(v string) error {
if err := p.Valid(v); err != nil {
return err
}

p.selected = append(p.selected, v)
p.val.Set(p.selected)
return nil
}

// Run param and get user input
func (p *ChoiceParam) Run() (err error) {
if len(p.Choices) == 0 {
return errorx.Raw("must provide items for choices")
}

s := interact.NewSelect(p.Desc(), p.Choices)
s.EnableMulti()

sr := s.Run()
p.val.Set(sr.Val())
return
}
107 changes: 107 additions & 0 deletions interact/cparam/cparam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package cparam

import (
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/structs"
)

// params types
const (
TypeStringParam = "string"
TypeChoicesParam = "choices"
)

// RunFn func type
type RunFn func() (val string, err error)

// InputParam struct
type InputParam struct {
typ string
name string
desc string
// Default value
Default any
ValidFn func(val string) error
runFn func() (val string, err error)
// Value for input
val structs.Value
err error
}

// NewInputParam instance
func NewInputParam(typ, name, desc string) *InputParam {
return &InputParam{
typ: typ,
name: name,
desc: desc,
}
}

// Type name get
func (p *InputParam) Type() string {
return p.typ
}

// Name get
func (p *InputParam) Name() string {
return p.name
}

// Desc message
func (p *InputParam) Desc() string {
return p.desc
}

// Valid value validate
func (p *InputParam) Valid(v string) error {
if p.ValidFn != nil {
return p.ValidFn(v)
}
return nil
}

// Set value and with validate
func (p *InputParam) Set(v string) error {
if err := p.Valid(v); err != nil {
return err
}

p.val.Set(v)
return nil
}

// Value data get
func (p *InputParam) Value() structs.Value {
return p.val
}

// Value get
func (p *InputParam) String() string {
return p.val.String()
}

// SetFunc for run
func (p *InputParam) SetFunc(fn RunFn) {
p.runFn = fn
}

// SetValidFn for run
func (p *InputParam) SetValidFn(fn func(val string) error) {
p.ValidFn = fn
}

// Run param and get user input
func (p *InputParam) Run() (err error) {
if p.runFn != nil {
val, err := p.runFn()
if err != nil {
return err
}

err = p.Set(val)
} else {
err = errorx.Raw("please implement me")
}

return err
}
Loading

0 comments on commit f907351

Please sign in to comment.