ekristen/libnuke

View on GitHub
pkg/types/properties.go

Summary

Maintainability
D
2 days
Test Coverage
A
100%
package types

import (
    "fmt"
    "reflect"
    "strings"
    "time"
)

// Properties is a map of key-value pairs.
type Properties map[string]string

// NewProperties creates a new Properties map.
func NewProperties() Properties {
    props := make(Properties)
    props.SetTagPrefix("tag")
    return props
}

// NewPropertiesFromStruct creates a new Properties map from a struct.
func NewPropertiesFromStruct(data interface{}) Properties {
    return NewProperties().SetFromStruct(data)
}

func (p Properties) SetTagPrefix(prefix string) Properties {
    p["_tagPrefix"] = prefix
    return p
}

// String returns a string representation of the Properties map.
func (p Properties) String() string {
    var parts []string
    for k, v := range p {
        if strings.HasPrefix(k, "_") {
            continue
        }

        parts = append(parts, fmt.Sprintf(`%s: "%v"`, k, v))
    }

    return fmt.Sprintf("[%s]", strings.Join(parts, ", "))
}

// Get returns the value of a key in the Properties map.
func (p Properties) Get(key string) string {
    value, ok := p[key]
    if !ok {
        return ""
    }

    return value
}

// Set sets a key-value pair in the Properties map.
func (p Properties) Set(key string, value interface{}) Properties { //nolint:gocyclo
    if value == nil {
        return p
    }

    switch v := value.(type) {
    case *string:
        if v == nil {
            return p
        }
        p[key] = *v
    case string:
        p[key] = v
    case []byte:
        p[key] = string(v)
    case *bool:
        if v == nil {
            return p
        }
        p[key] = fmt.Sprint(*v)
    case bool:
        p[key] = fmt.Sprint(v)
    case *int64:
        if v == nil {
            return p
        }
        p[key] = fmt.Sprint(*v)
    case int64:
        p[key] = fmt.Sprint(v)
    case *int:
        if v == nil {
            return p
        }
        p[key] = fmt.Sprint(*v)
    case int:
        p[key] = fmt.Sprint(v)
    case time.Time:
        p[key] = v.Format(time.RFC3339)
    default:
        // Fallback to Stringer interface. This produces gibberish on pointers,
        // but is the only way to avoid reflection.
        p[key] = fmt.Sprint(value)
    }

    return p
}

// SetWithPrefix sets a key-value pair in the Properties map with a prefix.
func (p Properties) SetWithPrefix(prefix, key string, value interface{}) Properties {
    key = strings.TrimSpace(key)
    prefix = strings.TrimSpace(prefix)

    if key == "" {
        return p
    }

    if prefix != "" {
        key = fmt.Sprintf("%s:%s", prefix, key)
    }

    return p.Set(key, value)
}

// SetTag sets a tag key-value pair in the Properties map.
func (p Properties) SetTag(tagKey *string, tagValue interface{}) Properties {
    return p.SetTagWithPrefix("", tagKey, tagValue)
}

// SetTagWithPrefix sets a tag key-value pair in the Properties map with a prefix.
func (p Properties) SetTagWithPrefix(prefix string, tagKey *string, tagValue interface{}) Properties {
    if tagKey == nil {
        return p
    }

    keyStr := strings.TrimSpace(*tagKey)
    prefix = strings.TrimSpace(prefix)

    if keyStr == "" {
        return p
    }

    if prefix != "" {
        keyStr = fmt.Sprintf("%s:%s", prefix, keyStr)
    }

    keyStr = fmt.Sprintf("%s:%s", p.Get("_tagPrefix"), keyStr)

    return p.Set(keyStr, tagValue)
}

// Equals compares two Properties maps.
func (p Properties) Equals(o Properties) bool {
    if p == nil && o == nil {
        return true
    }

    if p == nil || o == nil {
        return false
    }

    if len(p) != len(o) {
        return false
    }

    for k, pv := range p {
        ov, ok := o[k]
        if !ok {
            return false
        }

        if pv != ov {
            return false
        }
    }

    return true
}

// SetFromStruct sets the Properties map from a struct by reading the structs fields
func (p Properties) SetFromStruct(data interface{}) Properties { //nolint:funlen,gocyclo
    v := reflect.ValueOf(data)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    t := v.Type()

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)

        if !field.IsExported() {
            continue
        }

        isSet := false

        switch value.Kind() {
        case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface, reflect.Chan:
            isSet = !value.IsNil()
        default:
            isSet = value.Interface() != reflect.Zero(value.Type()).Interface()
        }

        if !isSet {
            continue
        }

        propertyTag := field.Tag.Get("property")
        options := strings.Split(propertyTag, ",")
        name := field.Name
        prefix := ""
        tagPrefix := ""
        keyFieldName := ""
        valueFieldName := ""
        inline := false

        if options[0] == "-" {
            continue
        }

        if len(options) == 2 && options[1] == "inline" {
            inline = true
        }

        for _, option := range options {
            parts := strings.Split(option, "=")
            if len(parts) != 2 {
                continue
            }
            switch parts[0] {
            case "name":
                name = parts[1]
            case "prefix":
                prefix = parts[1]
            case "tagPrefix":
                tagPrefix = parts[1]
            case "keyField":
                keyFieldName = parts[1]
            case "valueField":
                valueFieldName = parts[1]
            }
        }

        if inline {
            p.SetFromStruct(value.Interface())
            continue
        }

        if tagPrefix != "" {
            p.SetTagPrefix(tagPrefix)
        }

        if value.Kind() == reflect.Ptr {
            value = value.Elem()
        }

        switch value.Kind() {
        case reflect.Struct:
            if value.Type().String() == "time.Time" {
                p.SetWithPrefix(prefix, name, value.Interface())
            }
        case reflect.Map:
            for _, key := range value.MapKeys() {
                val := value.MapIndex(key)
                if key.Kind() == reflect.Ptr {
                    key = key.Elem()
                }
                if val.Kind() == reflect.Ptr {
                    val = val.Elem()
                }
                name = key.String()
                p.SetTagWithPrefix(prefix, &name, val.Interface())
            }
        case reflect.Slice:
            for j := 0; j < value.Len(); j++ {
                sliceValue := value.Index(j)
                if sliceValue.Kind() == reflect.Ptr {
                    sliceValue = sliceValue.Elem()
                }
                if sliceValue.Kind() == reflect.Struct {
                    sliceValueV := reflect.ValueOf(sliceValue.Interface())

                    var keyField, valueField reflect.Value
                    if keyFieldName != "" {
                        keyField = sliceValueV.FieldByName(keyFieldName)
                    } else {
                        keyField = sliceValueV.FieldByName("Key")
                        if !keyField.IsValid() {
                            keyField = sliceValueV.FieldByName("TagKey")
                        }
                    }

                    if valueFieldName != "" {
                        valueField = sliceValueV.FieldByName(valueFieldName)
                    } else {
                        valueField = sliceValueV.FieldByName("Value")
                        if !valueField.IsValid() {
                            valueField = sliceValueV.FieldByName("TagValue")
                        }
                    }

                    if keyField.Kind() == reflect.Ptr {
                        keyField = keyField.Elem()
                    }
                    if valueField.Kind() == reflect.Ptr {
                        valueField = valueField.Elem()
                    }

                    if keyField.IsValid() && valueField.IsValid() {
                        p.SetTagWithPrefix(prefix, &[]string{keyField.Interface().(string)}[0], valueField.Interface())
                    }
                }
            }
        default:
            p.SetWithPrefix(prefix, name, value.Interface())
        }
    }

    return p
}