Skip to content

Commit

Permalink
add bool
Browse files Browse the repository at this point in the history
  • Loading branch information
shockerli committed Mar 9, 2021
1 parent 0b0c583 commit fad1814
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 0 deletions.
14 changes: 14 additions & 0 deletions cvt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cvt

// Bool convert an interface to a bool type, with default value
func Bool(v interface{}, def ...bool) bool {
if v, err := BoolE(v); err == nil {
return v
}

if len(def) > 0 {
return def[0]
}

return false
}
113 changes: 113 additions & 0 deletions cvt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package cvt_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/shockerli/cvt"
)

func TestBool_HasDefault(t *testing.T) {
tests := []struct {
input interface{}
def bool
expect bool
}{
// supported value, def is not used, def != expect
{0, true, false},
{float64(0.00), true, false},
{int(0.00), true, false},
{int64(0.00), true, false},
{uint(0.00), true, false},
{uint64(0.00), true, false},
{uint8(0.00), true, false},
{nil, true, false},
{"false", true, false},
{"FALSE", true, false},
{"False", true, false},
{"f", true, false},
{"F", true, false},
{false, true, false},
{"off", true, false},
{"Off", true, false},
{[]byte("Off"), true, false},
{aliasTypeInt_0, true, false},
{&aliasTypeInt_0, true, false},
{aliasTypeString_0, true, false},
{&aliasTypeString_0, true, false},
{aliasTypeString_off, true, false},
{&aliasTypeString_off, true, false},

{[]int{}, true, false},
{[]string{}, true, false},
{[...]string{}, true, false},
{map[int]int{}, true, false},
{map[string]string{}, true, false},

{"true", false, true},
{"TRUE", false, true},
{"True", false, true},
{"t", false, true},
{"T", false, true},
{1, false, true},
{true, false, true},
{-1, false, true},
{"on", false, true},
{"On", false, true},
{0.01, false, true},
{aliasTypeInt_1, false, true},
{&aliasTypeInt_1, false, true},
{aliasTypeString_1, false, true},
{&aliasTypeString_1, false, true},
{aliasTypeString_on, false, true},
{&aliasTypeString_on, false, true},

{[]int{1, 2, 3}, false, true},
{[]string{"a", "b", "c"}, false, true},
{[...]string{"a", "b", "c"}, false, true},
{map[int]int{1: 111, 2: 222}, false, true},
{map[string]string{"a": "aaa"}, false, true},

// unsupported value, def == expect
{"hello", true, true},
{"hello", false, false},
{testing.T{}, true, true},
{testing.T{}, false, false},
{&testing.T{}, true, true},
{&testing.T{}, false, false},
}

for i, tt := range tests {
msg := fmt.Sprintf("i = %d, input[%+v], def[%+v], expect[%+v]", i, tt.input, tt.def, tt.expect)

v := cvt.Bool(tt.input, tt.def)
assert.Equal(t, tt.expect, v, msg)
}
}

func TestBool_BaseLine(t *testing.T) {
tests := []struct {
input interface{}
expect bool
}{
{testing.T{}, false},
{&testing.T{}, false},
{[]int{}, false},
{[]int{1, 2, 3}, true},
{[]string{}, false},
{[]string{"a", "b", "c"}, true},
{[...]string{}, false},
{map[int]string{}, false},
{aliasTypeString_8d15_minus, true},
{&aliasTypeString_8d15_minus, true},
}

for i, tt := range tests {
msg := fmt.Sprintf("i = %d, input[%+v], expect[%+v]", i, tt.input, tt.expect)

v := cvt.Bool(tt.input)
assert.Equal(t, tt.expect, v, msg)
}
}
117 changes: 117 additions & 0 deletions cvte.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package cvt

import (
"fmt"
"reflect"
"strconv"
"strings"
)

// BoolE convert an interface to a bool type
func BoolE(val interface{}) (bool, error) {
v, rk, rv := Indirect(val)

switch vv := v.(type) {
case bool:
return vv, nil
case int, int8, int16, int32, int64:
return rv.Int() != 0, nil
case uint, uint8, uint16, uint32, uint64:
return rv.Uint() != 0, nil
case float32, float64:
return rv.Float() != 0, nil
case []byte:
return str2bool(string(vv))
case string:
return str2bool(vv)
case nil:
return false, nil
}

switch rk {
// by elem length
case reflect.Array, reflect.Slice, reflect.Map:
return rv.Len() > 0, nil
}

return false, err(val, "bool")
}

// returns the boolean value represented by the string
func str2bool(str string) (bool, error) {
if val, err := strconv.ParseBool(str); err == nil {
return val, nil
} else if val, err := strconv.ParseFloat(str, 64); err == nil {
return val != 0, nil
}

switch strings.ToLower(strings.TrimSpace(str)) {
case "on":
return true, nil
case "off":
return false, nil
default:
return false, err(str, "bool")
}
}

// Indirect returns the value with base type
func Indirect(a interface{}) (val interface{}, k reflect.Kind, v reflect.Value) {
if a == nil {
return
}

t := reflect.TypeOf(a)
v = reflect.ValueOf(a)
k = t.Kind()

switch t.Kind() {
case reflect.Ptr:
for v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return Indirect(v.Interface())
case reflect.Bool:
val = v.Bool()
case reflect.Int:
val = int(v.Int())
case reflect.Int8:
val = int8(v.Int())
case reflect.Int16:
val = int16(v.Int())
case reflect.Int32:
val = int32(v.Int())
case reflect.Int64:
val = v.Int()
case reflect.Uint:
val = uint(v.Uint())
case reflect.Uint8:
val = uint8(v.Uint())
case reflect.Uint16:
val = uint16(v.Uint())
case reflect.Uint32:
val = uint32(v.Uint())
case reflect.Uint64:
val = v.Uint()
case reflect.Uintptr:
val = uintptr(v.Uint())
case reflect.Float32:
val = float32(v.Float())
case reflect.Float64:
val = v.Float()
case reflect.Complex64:
val = complex64(v.Complex())
case reflect.Complex128:
val = v.Complex()
case reflect.String:
val = v.String()
default:
val = v.Interface()
}

return
}

func err(val interface{}, t string) error {
return fmt.Errorf("unable to convert %#v of type %T to %s", val, val, t)
}
121 changes: 121 additions & 0 deletions cvte_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cvt_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/shockerli/cvt"
)

// alias type: int
type AliasTypeInt int

var (
aliasTypeInt_0 AliasTypeInt = 0
aliasTypeInt_1 AliasTypeInt = 1
)

// alias type: string
type AliasTypeString string

var (
aliasTypeString_0 AliasTypeString = "0"
aliasTypeString_1 AliasTypeString = "1"
aliasTypeString_8d15 AliasTypeString = "8.15"
aliasTypeString_8d15_minus AliasTypeString = "-8.15"
aliasTypeString_on AliasTypeString = "on"
aliasTypeString_off AliasTypeString = "off"
)

func TestBoolE(t *testing.T) {
tests := []struct {
input interface{}
expect bool
isErr bool
}{
// true/scale
{0, false, false},
{float64(0.00), false, false},
{int(0.00), false, false},
{int64(0.00), false, false},
{uint(0.00), false, false},
{uint64(0.00), false, false},
{uint8(0.00), false, false},
{nil, false, false},
{"false", false, false},
{"FALSE", false, false},
{"False", false, false},
{"f", false, false},
{"F", false, false},
{false, false, false},
{"off", false, false},
{"Off", false, false},
{"0", false, false},
{"0.00", false, false},
{[]byte("Off"), false, false},
{aliasTypeInt_0, false, false},
{&aliasTypeInt_0, false, false},
{aliasTypeString_0, false, false},
{&aliasTypeString_0, false, false},
{aliasTypeString_off, false, false},
{&aliasTypeString_off, false, false},

// false/slice/array/map
{[]int{}, false, false},
{[]string{}, false, false},
{[...]string{}, false, false},
{map[int]int{}, false, false},
{map[string]string{}, false, false},

// true/scale
{"true", true, false},
{"TRUE", true, false},
{"True", true, false},
{"t", true, false},
{"T", true, false},
{1, true, false},
{true, true, false},
{-1, true, false},
{"on", true, false},
{"On", true, false},
{0.01, true, false},
{"0.01", true, false},
{aliasTypeInt_1, true, false},
{&aliasTypeInt_1, true, false},
{aliasTypeString_1, true, false},
{&aliasTypeString_1, true, false},
{aliasTypeString_on, true, false},
{&aliasTypeString_on, true, false},

// true/slice/array/map
{[]int{1, 2, 3}, true, false},
{[]string{"a", "b", "c"}, true, false},
{[...]string{"a", "b", "c"}, true, false},
{map[int]int{1: 111, 2: 222}, true, false},
{map[string]string{"a": "aaa"}, true, false},

// errors
{"hello", false, true},
{testing.T{}, false, true},
{&testing.T{}, false, true},
}

for i, tt := range tests {
msg := fmt.Sprintf("i = %d, input[%+v], expect[%+v], isErr[%+v]", i, tt.input, tt.expect, tt.isErr)

v, err := cvt.BoolE(tt.input)
if tt.isErr {
assert.Error(t, err, msg)
continue
}

assert.NoError(t, err, msg)
assert.Equal(t, tt.expect, v, msg)

// Non-E test with no default value:
v = cvt.Bool(tt.input)
assert.Equal(t, tt.expect, v, msg)
}
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/shockerli/cvt

go 1.15

require (
github.com/davecgh/go-spew v1.1.1
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit fad1814

Please sign in to comment.