diff --git a/conf/file.go b/conf/file.go index c305fdd..ad736e5 100644 --- a/conf/file.go +++ b/conf/file.go @@ -81,7 +81,7 @@ func (f *File) ParseFrontMatter() error { return err } f.Metadata = &r - if f.Metadata.Variables != nil && len(f.Metadata.Variables.s) > 0 { + if f.Metadata.Variables != nil && len(*f.Metadata.Variables) > 0 { utils.OkPrintln("Variables for single file", color.YellowString(f.Path)) f.Metadata.Variables.Prompt() } diff --git a/conf/root.go b/conf/root.go index 4fdad2b..33e9903 100644 --- a/conf/root.go +++ b/conf/root.go @@ -43,7 +43,7 @@ func (r Root) ExecuteCommands(dir string) { for _, cmd := range r.After { if cmd.Cmd != "" { if cmd.If != "" && r.Variables != nil { - if v, ok := r.Variables.m[cmd.If]; ok && v.True() { + if v := r.Variables.FindNamed(cmd.If); v != nil && v.True() { cmd.Run() } } else { diff --git a/conf/variables.go b/conf/variables.go index 78389fa..8975172 100644 --- a/conf/variables.go +++ b/conf/variables.go @@ -9,10 +9,18 @@ import ( yaml "gopkg.in/yaml.v2" ) -// Variables represents a map of variable -type Variables struct { - m map[string]*Variable - s []*Variable +// Variables is a slice of pointer to a single variable +type Variables []*Variable + +// FindNamed will find a variable by name in the global variables. Returns nil +// if not found +func (vv Variables) FindNamed(s string) *Variable { + for _, v := range vv { + if v.Name == s { + return v + } + } + return nil } // FromMapSlice fills in the Variables struct with the data stored in a @@ -21,20 +29,14 @@ func (vv *Variables) FromMapSlice(in yaml.MapSlice) { for _, i := range in { inv := &Variable{} inv.FromMapItem(i) - - k := i.Key.(string) - inv.Name = k - vv.m[k] = inv - vv.s = append(vv.s, inv) + *vv = append(*vv, inv) } } // UnmarshalYAML defines a custom way to unmarshal to the Variables type. // Specifically this allows to conserve the key order func (vv *Variables) UnmarshalYAML(unmarshal func(interface{}) error) error { - variables := Variables{ - m: make(map[string]*Variable), - } + var variables Variables n := yaml.MapSlice{} err := unmarshal(&n) if err != nil { @@ -47,7 +49,7 @@ func (vv *Variables) UnmarshalYAML(unmarshal func(interface{}) error) error { // Prompt will prompt func (vv Variables) Prompt() { - for _, v := range vv.s { + for _, v := range vv { v.Prompt() } } @@ -55,7 +57,7 @@ func (vv Variables) Prompt() { // Ctx generates the context from the variables func (vv Variables) Ctx() map[string]interface{} { ctx := make(map[string]interface{}) - for _, v := range vv.s { + for _, v := range vv { if v != nil { if v.Confirm != nil { ctx[v.Name] = *v.Confirm @@ -101,8 +103,8 @@ type Variable struct { // Confirm is used both for default variable and to store the result. // If this field isn't nil, then a confirmation survey is used. - Confirm *bool `yaml:"confirm,omitempty"` - Variables *Variables `yaml:"variables,omitempty"` + Confirm *bool `yaml:"confirm,omitempty"` + Variables Variables `yaml:"variables,omitempty"` Result string Name string @@ -111,6 +113,7 @@ type Variable struct { // FromMapItem will fill the variable with the data stored in the input // yaml.MapItem. Used to recursively parse nested variables. func (v *Variable) FromMapItem(i yaml.MapItem) { + v.Name = i.Key.(string) for _, data := range i.Value.(yaml.MapSlice) { switch data.Key.(string) { case "default": @@ -129,9 +132,7 @@ func (v *Variable) FromMapItem(i yaml.MapItem) { b := data.Value.(bool) v.Confirm = &b case "variables": - vv := &Variables{ - m: make(map[string]*Variable), - } + var vv Variables vv.FromMapSlice(data.Value.(yaml.MapSlice)) v.Variables = vv } diff --git a/conf/variables_test.go b/conf/variables_test.go new file mode 100644 index 0000000..66baf3f --- /dev/null +++ b/conf/variables_test.go @@ -0,0 +1,145 @@ +package conf + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + tbool = true + empty = &Variable{Name: "empty"} + hasvalue = &Variable{Name: "value", Result: "result"} + hasbool = &Variable{Name: "bool", Confirm: &tbool} + subvar = &Variable{Name: "sub", Result: "ok"} + parentvar = &Variable{Name: "parent", Variables: Variables{subvar}} +) + +func TestVariables_Ctx(t *testing.T) { + tests := []struct { + name string + fields Variables + want map[string]interface{} + }{ + {"should get one", Variables{empty}, + map[string]interface{}{empty.Name: ""}}, + {"should get two vars", Variables{empty, hasvalue}, + map[string]interface{}{empty.Name: "", hasvalue.Name: hasvalue.Result}}, + {"should get bool", Variables{hasbool}, + map[string]interface{}{hasbool.Name: true}}, + {"should get all vars", Variables{empty, hasvalue, hasbool}, + map[string]interface{}{empty.Name: "", hasvalue.Name: hasvalue.Result, hasbool.Name: true}}, + {"should get even sub vars", Variables{parentvar}, + map[string]interface{}{parentvar.Name: "", parentvar.Name + "_" + subvar.Name: "ok"}}, + {"should get even all with sub vars", Variables{empty, hasvalue, hasbool, parentvar}, + map[string]interface{}{empty.Name: "", hasvalue.Name: hasvalue.Result, hasbool.Name: true, parentvar.Name: "", parentvar.Name + "_" + subvar.Name: "ok"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fields.Ctx(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Variables.Ctx() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVariables_AddToCtx(t *testing.T) { + basectx := map[string]interface{}{"one": "one", "two": true} + type args struct { + prefix string + ctx map[string]interface{} + } + tests := []struct { + name string + fields Variables + args args + expects map[string]interface{} + }{ + {"should add empty to context", Variables{empty}, args{"", basectx}, + map[string]interface{}{empty.Name: "", "one": "one", "two": true}}, + {"should add boolean to context", Variables{hasbool}, args{"", basectx}, + map[string]interface{}{hasbool.Name: true, "one": "one", "two": true}}, + {"should add boolean with key to context", Variables{hasbool}, args{"sub", basectx}, + map[string]interface{}{"sub_" + hasbool.Name: true, "one": "one", "two": true}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cp := make(map[string]interface{}) + for k, v := range tt.args.ctx { + cp[k] = v + } + tt.fields.AddToCtx(tt.args.prefix, cp) + assert.Equal(t, tt.expects, cp) + }) + } +} + +func TestVariables_FindNamed(t *testing.T) { + vv := Variables{hasbool, hasvalue, empty} + type args struct { + s string + } + tests := []struct { + name string + vv Variables + args args + want *Variable + }{ + {"should find even empty", vv, args{hasvalue.Name}, hasvalue}, + {"should find bool", vv, args{hasbool.Name}, hasbool}, + {"should find value", vv, args{hasvalue.Name}, hasvalue}, + {"shouldn't find", vv, args{"random.jpg"}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.vv.FindNamed(tt.args.s) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Variables.FindNamed() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVariable_True(t *testing.T) { + var fbool bool + type fields struct { + Default string + CustomPrompt string + Values []string + Help string + Required bool + Confirm *bool + Variables Variables + Result string + Name string + } + tests := []struct { + name string + fields fields + want bool + }{ + {"should be false when confirm is false", fields{Confirm: &fbool}, false}, + {"should be false when result is empty", fields{Result: ""}, false}, + {"should be true when confirm is true", fields{Confirm: &tbool}, true}, + {"should be true when result is true", fields{Result: "toto"}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &Variable{ + Default: tt.fields.Default, + CustomPrompt: tt.fields.CustomPrompt, + Values: tt.fields.Values, + Help: tt.fields.Help, + Required: tt.fields.Required, + Confirm: tt.fields.Confirm, + Variables: tt.fields.Variables, + Result: tt.fields.Result, + Name: tt.fields.Name, + } + if got := v.True(); got != tt.want { + t.Errorf("Variable.True() = %v, want %v", got, tt.want) + } + }) + } +}