Skip to content

Commit e1bc29a

Browse files
committed
Refactor and add further unit tests
1 parent c5f2783 commit e1bc29a

File tree

7 files changed

+224
-83
lines changed

7 files changed

+224
-83
lines changed

formatters/bash.go

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package formatters
22

33
import (
44
"fmt"
5-
"reflect"
65
"strings"
76

87
"github.com/stilvoid/please/util"
@@ -25,25 +24,14 @@ func formatBashInternal(in interface{}) string {
2524
return ""
2625
}
2726

28-
val := reflect.ValueOf(in)
27+
switch v := in.(type) {
28+
case map[string]interface{}:
29+
keys := util.SortedKeys(v)
2930

30-
switch val.Kind() {
31-
case reflect.Map:
32-
parts := make([]string, val.Len())
31+
parts := make([]string, len(v))
3332

34-
for i, key := range val.MapKeys() {
35-
value := val.MapIndex(key).Interface()
36-
parts[i] = fmt.Sprintf("[%s]=%s", key.String(), wrapObj(value))
37-
}
38-
39-
return fmt.Sprintf("(%s)", strings.Join(parts, " "))
40-
case reflect.Array, reflect.Slice:
41-
parts := make([]string, val.Len())
42-
43-
for i := 0; i < val.Len(); i++ {
44-
value := val.Index(i).Interface()
45-
46-
parts[i] = fmt.Sprintf("[%d]=%s", i, wrapObj(value))
33+
for i, key := range keys {
34+
parts[i] = fmt.Sprintf("[%s]=%s", key, wrapObj(v[key.(string)]))
4735
}
4836

4937
return fmt.Sprintf("(%s)", strings.Join(parts, " "))
@@ -53,6 +41,7 @@ func formatBashInternal(in interface{}) string {
5341
}
5442

5543
func formatBash(in interface{}) (string, error) {
44+
in = util.ArraysToMaps(in)
5645
in = util.ForceStringKeys(in)
5746

5847
return formatBashInternal(in), nil

formatters/bash_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package formatters
2+
3+
import "testing"
4+
5+
func TestBash(t *testing.T) {
6+
expecteds := []string{
7+
"123",
8+
"456.789",
9+
"abc",
10+
"true",
11+
"false",
12+
"",
13+
"([0]=\"123\" [1]=\"abc\")",
14+
"([foo]=\"bar\")",
15+
"([123]=\"([0]=\\\"baz\\\" [1]=\\\"quux\\\")\")",
16+
"([true]=\"([null]=\\\"\\\")\")",
17+
"([0]=\"456\" [1]=\"def\" [2]=\"([3]=\\\"4\\\")\")",
18+
}
19+
20+
if len(expecteds) != len(testCases) {
21+
t.Fatalf("insufficient test cases implemented")
22+
}
23+
24+
for i, expected := range expecteds {
25+
testCase := testCases[i]
26+
27+
actual, err := formatBash(testCase)
28+
29+
if err != nil {
30+
t.Errorf("unexpected error: %v", err)
31+
}
32+
33+
if actual != expected {
34+
t.Errorf("unexpected '%v', want '%v'", actual, expected)
35+
}
36+
}
37+
}

formatters/query.go

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,42 @@ package formatters
33
import (
44
"fmt"
55
"net/url"
6-
"reflect"
76

87
"github.com/stilvoid/please/util"
98
)
109

11-
// TODO: Tidy this up; it's horrid.
12-
func formatQuery(in interface{}) (string, error) {
13-
in = util.ForceStringKeys(in)
10+
func formatQueryInternal(in interface{}) string {
11+
if in == nil {
12+
return ""
13+
}
1414

1515
inMap, ok := in.(map[string]interface{})
1616

1717
if !ok {
18-
return "", fmt.Errorf("query formatter expects a map as input")
18+
return fmt.Sprint(in)
1919
}
2020

2121
var output url.Values = make(map[string][]string)
2222

2323
for key, value := range inMap {
24-
val := reflect.ValueOf(value)
25-
26-
switch val.Kind() {
27-
case reflect.Map:
28-
return "", fmt.Errorf("query formatter cannot deal with nested values")
29-
case reflect.Array, reflect.Slice:
30-
for i := 0; i < val.Len(); i++ {
31-
iVal := val.Index(i)
32-
33-
switch iVal.Kind() {
34-
case reflect.Map, reflect.Array, reflect.Slice:
35-
return "", fmt.Errorf("query formatter cannot deal with nested values")
36-
default:
37-
output.Add(key, fmt.Sprint(iVal.Interface()))
38-
}
39-
}
24+
switch value.(type) {
25+
case map[string]interface{}:
26+
result := formatQueryInternal(value)
27+
28+
output.Add(key, result)
29+
case nil:
30+
output.Add(key, "")
4031
default:
4132
output.Add(key, fmt.Sprint(value))
4233
}
4334
}
4435

45-
return output.Encode(), nil
36+
return output.Encode()
37+
}
38+
39+
func formatQuery(in interface{}) (string, error) {
40+
in = util.ArraysToMaps(in)
41+
in = util.ForceStringKeys(in)
42+
43+
return formatQueryInternal(in), nil
4644
}

formatters/query_test.go

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,36 @@
11
package formatters
22

3-
import (
4-
"reflect"
5-
"testing"
6-
)
3+
import "testing"
74

85
func TestQuery(t *testing.T) {
9-
inputs := []interface{}{
10-
map[string]string{
11-
"foo": "bar",
12-
},
13-
map[string][]string{
14-
"foo": {"bar", "baz"},
15-
},
16-
map[string]int{
17-
"foo": 123,
18-
},
19-
map[int]int{
20-
123: 456,
21-
},
22-
map[string]string{
23-
"foo": "bar",
24-
"baz": "quux",
25-
},
26-
}
27-
286
expecteds := []string{
7+
"123",
8+
"456.789",
9+
"abc",
10+
"true",
11+
"false",
12+
"",
13+
"0=123&1=abc",
2914
"foo=bar",
30-
"foo=bar&foo=baz",
31-
"foo=123",
32-
"123=456",
33-
"baz=quux&foo=bar",
15+
"123=0%3Dbaz%261%3Dquux",
16+
"true=null%3D",
17+
"0=456&1=def&2=3%3D4",
18+
}
19+
20+
if len(expecteds) != len(testCases) {
21+
t.Fatalf("insufficient test cases implemented")
3422
}
3523

36-
for i, input := range inputs {
37-
actual, err := formatQuery(input)
24+
for i, expected := range expecteds {
25+
testCase := testCases[i]
26+
27+
actual, err := formatQuery(testCase)
3828

3929
if err != nil {
4030
t.Errorf("unexpected error: %v", err)
4131
}
4232

43-
expected := expecteds[i]
44-
45-
if !reflect.DeepEqual(actual, expected) {
33+
if actual != expected {
4634
t.Errorf("unexpected '%v', want '%v'", actual, expected)
4735
}
4836
}

formatters/xml_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestXML(t *testing.T) {
1818
}
1919

2020
if len(expecteds) != len(testCases) {
21-
//t.Fatalf("insufficient test cases implemented")
21+
t.Fatalf("insufficient test cases implemented")
2222
}
2323

2424
for i, expected := range expecteds {

util/map.go

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,32 @@ package util
33
import (
44
"fmt"
55
"reflect"
6+
"sort"
67
)
78

9+
// toString wraps fmt.Sprint except that it converts nil to "null"
10+
func toString(in interface{}) string {
11+
if in == nil {
12+
return "null"
13+
}
14+
15+
return fmt.Sprint(in)
16+
}
17+
818
// ForceStringKeys creates a copy of the provided interface{}, with all maps changed to have string keys for use by serialisers that expect string keys
9-
// This is particularly useful for formatters where the target serialisation format only allows string keys
19+
// This is useful for formatters where the target serialisation format only allows string keys
1020
func ForceStringKeys(in interface{}) interface{} {
1121
val := reflect.ValueOf(in)
1222

1323
switch val.Kind() {
1424
case reflect.Map:
1525
newMap := make(map[string]interface{}, val.Len())
1626

17-
var stringKey string
18-
19-
for _, key := range val.MapKeys() {
20-
if reflect.TypeOf(key.Interface()) == nil {
21-
stringKey = "null"
22-
} else {
23-
stringKey = fmt.Sprint(key.Interface())
24-
}
27+
for _, keyVal := range val.MapKeys() {
28+
key := toString(keyVal.Interface())
29+
value := val.MapIndex(keyVal).Interface()
2530

26-
newMap[stringKey] = ForceStringKeys(val.MapIndex(key).Interface())
31+
newMap[key] = ForceStringKeys(value)
2732
}
2833

2934
return newMap
@@ -40,3 +45,63 @@ func ForceStringKeys(in interface{}) interface{} {
4045
return in
4146
}
4247
}
48+
49+
// ArraysToMaps creates a copy of the provided interface{}, with all arrays converted into maps where the keys are the array indices, starting at 0.
50+
// This is useful for formatters where the target serialisation format does not have a means of representing arrays
51+
func ArraysToMaps(in interface{}) interface{} {
52+
val := reflect.ValueOf(in)
53+
54+
switch val.Kind() {
55+
case reflect.Map:
56+
newMap := make(map[interface{}]interface{}, val.Len())
57+
58+
for _, key := range val.MapKeys() {
59+
value := val.MapIndex(key).Interface()
60+
61+
newMap[key.Interface()] = ArraysToMaps(value)
62+
}
63+
64+
return newMap
65+
case reflect.Array, reflect.Slice:
66+
newMap := make(map[interface{}]interface{}, val.Len())
67+
68+
for i := 0; i < val.Len(); i++ {
69+
value := val.Index(i).Interface()
70+
71+
newMap[interface{}(i)] = ArraysToMaps(value)
72+
}
73+
74+
return newMap
75+
default:
76+
return in
77+
}
78+
}
79+
80+
func SortedKeys(in interface{}) []interface{} {
81+
val := reflect.ValueOf(in)
82+
83+
if val.Kind() != reflect.Map {
84+
panic("SortedKeys only works on maps")
85+
}
86+
87+
stringKeys := make([]string, val.Len())
88+
stringKeysMap := make(map[string]interface{}, val.Len())
89+
90+
for i, key := range val.MapKeys() {
91+
stringKey := toString(key.Interface())
92+
93+
stringKeys[i] = stringKey
94+
95+
stringKeysMap[stringKey] = key.Interface()
96+
}
97+
98+
sort.Strings(stringKeys)
99+
100+
outKeys := make([]interface{}, val.Len())
101+
102+
for i, key := range stringKeys {
103+
outKeys[i] = stringKeysMap[key]
104+
}
105+
106+
return outKeys
107+
}

0 commit comments

Comments
 (0)