Added simple INI file serializer (utini).
This commit is contained in:
parent
942bf31ab6
commit
2f614c777d
56
utini/file.go
Normal file
56
utini/file.go
Normal file
@ -0,0 +1,56 @@
|
||||
package utini
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Comment string
|
||||
|
||||
type File struct {
|
||||
Sections map[string]*Section
|
||||
}
|
||||
|
||||
func (f *File) CreateIfNotExists(name string) *Section {
|
||||
if f.Sections == nil {
|
||||
f.Sections = map[string]*Section{}
|
||||
}
|
||||
section := f.Sections[name]
|
||||
if section == nil {
|
||||
section = &Section{}
|
||||
f.Sections[name] = section
|
||||
}
|
||||
return section
|
||||
}
|
||||
|
||||
type Property struct {
|
||||
Key, Value string
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Properties []*Property
|
||||
}
|
||||
|
||||
func (s Section) PropertyByKey(key string) *Property {
|
||||
key = strings.ToLower(key)
|
||||
for _, p := range s.Properties {
|
||||
if strings.ToLower(p.Key) == key {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Section) Keys() []string {
|
||||
keys := map[string]struct{}{}
|
||||
for _, property := range s.Properties {
|
||||
keys[property.Key] = struct{}{}
|
||||
}
|
||||
|
||||
var sorted []string
|
||||
for key := range keys {
|
||||
sorted = append(sorted, key)
|
||||
}
|
||||
sort.Strings(sorted)
|
||||
return sorted
|
||||
}
|
214
utini/load.go
Normal file
214
utini/load.go
Normal file
@ -0,0 +1,214 @@
|
||||
package utini
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"opslag.de/schobers/ut/utio"
|
||||
)
|
||||
|
||||
var errStructPtrExpected = errors.New(`expected pointer to struct`)
|
||||
|
||||
var keyValuePairRE = regexp.MustCompile(`^\s*(\w+)\s*=\s*(.*)\s*$`)
|
||||
var sectionRE = regexp.MustCompile(`\[((?:\w+\.)*(\w+))\]`)
|
||||
var commentRE = regexp.MustCompile(`^\s*[;#].*$`)
|
||||
|
||||
func appendToPrefix(prefix, name string) string {
|
||||
if prefix == "" {
|
||||
return name
|
||||
}
|
||||
return fmt.Sprintf(`%s.%s`, prefix, name)
|
||||
}
|
||||
|
||||
// func fmtErrNoSection(name string) error { return fmt.Errorf(`no section with name %s in file`, name) }
|
||||
|
||||
func load(lines []string) (*File, error) {
|
||||
file := &File{}
|
||||
var section *Section
|
||||
for _, line := range lines {
|
||||
if commentRE.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
match := sectionRE.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
section = file.CreateIfNotExists(match[1])
|
||||
continue
|
||||
}
|
||||
|
||||
match = keyValuePairRE.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
if section == nil {
|
||||
section = file.CreateIfNotExists("")
|
||||
}
|
||||
section.Properties = append(section.Properties, &Property{
|
||||
Key: match[1],
|
||||
Value: match[2],
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func Load(r io.Reader) (*File, error) {
|
||||
lines := utio.Lines()
|
||||
if err := lines.Decode(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return load(lines.Lines)
|
||||
}
|
||||
|
||||
func LoadFile(path string) (*File, error) {
|
||||
var err error
|
||||
var file *File
|
||||
utio.ReadFile(path, func(r io.Reader) error {
|
||||
file, err = Load(r)
|
||||
return err
|
||||
})
|
||||
return file, err
|
||||
}
|
||||
|
||||
func loadMap(file *File, err error, v interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return MapFromFile(file, v)
|
||||
}
|
||||
|
||||
func LoadMap(r io.Reader, v interface{}) error {
|
||||
file, err := Load(r)
|
||||
return loadMap(file, err, v)
|
||||
}
|
||||
|
||||
func LoadMapFile(path string, v interface{}) error {
|
||||
file, err := LoadFile(path)
|
||||
return loadMap(file, err, v)
|
||||
}
|
||||
|
||||
func mapFileToStruct(ptr reflect.Value, file *File, prefix string) error {
|
||||
if ptr.Type().Kind() != reflect.Ptr {
|
||||
return errStructPtrExpected
|
||||
}
|
||||
value := ptr.Elem()
|
||||
if value.Type().Kind() != reflect.Struct {
|
||||
return errStructPtrExpected
|
||||
}
|
||||
|
||||
valueTyp := value.Type()
|
||||
n := valueTyp.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
field := valueTyp.Field(i)
|
||||
name := appendToPrefix(prefix, field.Name)
|
||||
kind := field.Type.Kind()
|
||||
fieldValue := value.FieldByName(field.Name)
|
||||
switch {
|
||||
case kind == reflect.Struct:
|
||||
if err := mapFileToStruct(fieldValue.Addr(), file, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.Bool:
|
||||
if err := mapPropertyToField(file, prefix, field, fieldValue, setBoolValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64:
|
||||
if err := mapPropertyToField(file, prefix, field, fieldValue, setIntValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64:
|
||||
if err := mapPropertyToField(file, prefix, field, fieldValue, setUintValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.String:
|
||||
if err := mapPropertyToField(file, prefix, field, fieldValue, setStringValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MapFromFile(file *File, v interface{}) error {
|
||||
ptr := reflect.ValueOf(v)
|
||||
return mapFileToStruct(ptr, file, "")
|
||||
}
|
||||
|
||||
func mapPropertyToField(file *File, prefix string, field reflect.StructField, value reflect.Value, set setValueFn) error {
|
||||
property := propertyValueForField(file, prefix, field)
|
||||
if property == "" {
|
||||
return nil
|
||||
}
|
||||
err := set(value, iniTag(field), property)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to map property "%s"; error: %v`, appendToPrefix(prefix, field.Name), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func propertyValueForField(file *File, prefix string, field reflect.StructField) string {
|
||||
sectionKey := camelCaseToSnakeCase(prefix)
|
||||
section := file.Sections[sectionKey]
|
||||
if section == nil {
|
||||
return defaultTagValue(iniTag(field))
|
||||
}
|
||||
key := camelCaseToSnakeCase(field.Name)
|
||||
property := section.PropertyByKey(key)
|
||||
if property == nil {
|
||||
return defaultTagValue(iniTag(field))
|
||||
}
|
||||
return property.Value
|
||||
}
|
||||
|
||||
func setBoolValue(value reflect.Value, tag string, property string) error {
|
||||
states := statesTagValue(tag)
|
||||
p := strings.ToLower(property)
|
||||
if p == states[0] {
|
||||
value.SetBool(false)
|
||||
} else if p == states[1] {
|
||||
value.SetBool(true)
|
||||
} else {
|
||||
return fmt.Errorf(`failed to convert value "%s" to its boolean value, expected either "%s" or "%s"`, property, states[0], states[1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setIntValue(value reflect.Value, _ string, property string) error {
|
||||
i, err := strconv.ParseInt(property, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to convert value "%s" to an integer`, property)
|
||||
}
|
||||
value.SetInt(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setStringValue(value reflect.Value, _ string, property string) error {
|
||||
n := len(property)
|
||||
if n > 1 {
|
||||
delimiter := property[0]
|
||||
if (delimiter == '"' || delimiter == '\'' || delimiter == '`') && property[n-1] == delimiter {
|
||||
property = property[1 : n-1]
|
||||
}
|
||||
}
|
||||
value.SetString(property)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUintValue(value reflect.Value, _ string, property string) error {
|
||||
i, err := strconv.ParseUint(property, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to convert value "%s" to an unsigned integer`, property)
|
||||
}
|
||||
value.SetUint(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
type setValueFn func(value reflect.Value, tag, property string) error
|
97
utini/load_test.go
Normal file
97
utini/load_test.go
Normal file
@ -0,0 +1,97 @@
|
||||
package utini
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"opslag.de/schobers/ut/utio"
|
||||
)
|
||||
|
||||
func AssertLoadMapString(t *testing.T, s string, v interface{}) {
|
||||
file := AssertLoadString(t, s)
|
||||
err := MapFromFile(file, v)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func AssertLoadString(t *testing.T, s string) *File {
|
||||
lines := utio.Lines()
|
||||
utio.DecodeFromString(s, lines)
|
||||
file, err := load(lines.Lines)
|
||||
assert.Nil(t, err)
|
||||
return file
|
||||
}
|
||||
|
||||
type testData struct {
|
||||
NoSection string
|
||||
|
||||
Primitives testDataSectionPrimitives
|
||||
OnlySub testDataSectionOnlySub
|
||||
|
||||
NoSection2 string
|
||||
}
|
||||
|
||||
type testDataSectionPrimitives struct {
|
||||
DelimitedString string
|
||||
Integer int
|
||||
SingleQuotes string
|
||||
UnsignedByte uint8
|
||||
}
|
||||
|
||||
type testDataSectionOnlySub struct {
|
||||
Sub testDataSectionSub
|
||||
}
|
||||
|
||||
type testDataSectionSub struct {
|
||||
Test string
|
||||
}
|
||||
|
||||
type testDataDefault struct {
|
||||
Integer int `ini:"default=12345678"`
|
||||
String string `ini:"default=default"`
|
||||
}
|
||||
|
||||
var testData1IniFile = `no_section = "no section"
|
||||
no_section2 = "no section 2"
|
||||
|
||||
[only_sub.sub]
|
||||
test = "value"
|
||||
|
||||
[primitives]
|
||||
delimited_string = "value"
|
||||
integer = 12345678
|
||||
single_quotes = 'single quote delimited"'
|
||||
unsigned_byte = 101
|
||||
`
|
||||
|
||||
var testData1Struct = testData{
|
||||
NoSection: "no section",
|
||||
NoSection2: "no section 2",
|
||||
Primitives: testDataSectionPrimitives{
|
||||
DelimitedString: "value",
|
||||
Integer: 12345678,
|
||||
SingleQuotes: "single quote delimited\"",
|
||||
UnsignedByte: 101,
|
||||
},
|
||||
OnlySub: testDataSectionOnlySub{
|
||||
Sub: testDataSectionSub{
|
||||
Test: "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMapFromFile(t *testing.T) {
|
||||
file := AssertLoadString(t, testData1IniFile)
|
||||
|
||||
data := &testData{}
|
||||
err := MapFromFile(file, data)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testData1Struct, *data)
|
||||
}
|
||||
|
||||
func TestLoadDefaultValues(t *testing.T) {
|
||||
data := &testDataDefault{}
|
||||
AssertLoadMapString(t, ``, data)
|
||||
|
||||
assert.Equal(t, 12345678, data.Integer)
|
||||
assert.Equal(t, `default`, data.String)
|
||||
}
|
86
utini/names.go
Normal file
86
utini/names.go
Normal file
@ -0,0 +1,86 @@
|
||||
package utini
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type characterCasing int
|
||||
|
||||
const (
|
||||
undefinedCase characterCasing = iota
|
||||
lowerCase
|
||||
upperCase
|
||||
)
|
||||
|
||||
func camelCaseToSnakeCase(name string) string {
|
||||
segments := strings.Split(name, ".")
|
||||
if len(segments) > 1 {
|
||||
for i, segment := range segments {
|
||||
segments[i] = camelCaseToSnakeCase(segment)
|
||||
}
|
||||
return strings.Join(segments, ".")
|
||||
}
|
||||
|
||||
var words []string
|
||||
runes := []rune(name)
|
||||
if len(runes) == 0 {
|
||||
return ""
|
||||
}
|
||||
n := len(runes)
|
||||
end := n
|
||||
casing := undefinedCase
|
||||
for i := range runes {
|
||||
j := n - i - 1
|
||||
r := runes[j]
|
||||
|
||||
if unicode.IsLower(r) {
|
||||
if casing == upperCase {
|
||||
words = append(words, strings.ToLower(string(runes[j+1:end])))
|
||||
end = j + 1
|
||||
casing = undefinedCase
|
||||
} else {
|
||||
casing = lowerCase
|
||||
}
|
||||
} else if r == '_' {
|
||||
if end-j > 1 {
|
||||
words = append(words, strings.ToLower(string(runes[j+1:end])))
|
||||
end = j
|
||||
}
|
||||
casing = undefinedCase
|
||||
} else if unicode.IsDigit(r) { // acts like either upper or lowercase depending on which character was following.
|
||||
} else {
|
||||
if casing == lowerCase {
|
||||
words = append(words, strings.ToLower(string(runes[j:end])))
|
||||
end = j
|
||||
casing = undefinedCase
|
||||
} else {
|
||||
casing = upperCase
|
||||
}
|
||||
}
|
||||
}
|
||||
if end > 0 {
|
||||
words = append(words, strings.ToLower(string(runes[:end])))
|
||||
}
|
||||
|
||||
// reverse order of words
|
||||
last := len(words) - 1
|
||||
for i := 0; i < len(words)/2; i++ {
|
||||
words[i], words[last-i] = words[last-i], words[i]
|
||||
}
|
||||
|
||||
return strings.Join(words, "_")
|
||||
}
|
||||
|
||||
func snakeCaseToCamelCase(name string) string {
|
||||
words := strings.Split(name, `_`)
|
||||
for i, word := range words {
|
||||
runes := []rune(word)
|
||||
first := strings.IndexFunc(word, unicode.IsLetter)
|
||||
if first > -1 {
|
||||
runes[first] = unicode.ToUpper(runes[first])
|
||||
}
|
||||
words[i] = string(runes)
|
||||
}
|
||||
return strings.Join(words, ``)
|
||||
}
|
173
utini/save.go
Normal file
173
utini/save.go
Normal file
@ -0,0 +1,173 @@
|
||||
package utini
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"opslag.de/schobers/ut/utio"
|
||||
)
|
||||
|
||||
var stringDelimiters = []byte{'"', '\'', '`'}
|
||||
|
||||
func getBoolValue(value reflect.Value, tag string) string {
|
||||
states := statesTagValue(tag)
|
||||
if value.Bool() {
|
||||
return states[1]
|
||||
}
|
||||
return states[0]
|
||||
}
|
||||
|
||||
func getIntValue(value reflect.Value, _ string) string {
|
||||
i := value.Int()
|
||||
return strconv.FormatInt(i, 10)
|
||||
}
|
||||
|
||||
func getStringValue(value reflect.Value, _ string) string {
|
||||
s := value.String()
|
||||
for _, delimiter := range stringDelimiters {
|
||||
if strings.Contains(s, string([]byte{delimiter})) {
|
||||
continue
|
||||
}
|
||||
return fmt.Sprintf("%c%s%c", delimiter, s, delimiter)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func getUintValue(value reflect.Value, _ string) string {
|
||||
i := value.Uint()
|
||||
return strconv.FormatUint(i, 10)
|
||||
}
|
||||
|
||||
type getValueFn func(reflect.Value, string) string
|
||||
|
||||
func mapFieldToProperty(file *File, prefix string, field reflect.StructField, value reflect.Value, get getValueFn) error {
|
||||
sectionKey := camelCaseToSnakeCase(prefix)
|
||||
section := file.CreateIfNotExists(sectionKey)
|
||||
key := camelCaseToSnakeCase(field.Name)
|
||||
section.Properties = append(section.Properties, &Property{
|
||||
Key: key,
|
||||
Value: get(value, iniTag(field)),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapStructToFile(file *File, ptr reflect.Value, prefix string) error {
|
||||
if ptr.Type().Kind() != reflect.Ptr {
|
||||
return errStructPtrExpected
|
||||
}
|
||||
value := ptr.Elem()
|
||||
if value.Type().Kind() != reflect.Struct {
|
||||
return errStructPtrExpected
|
||||
}
|
||||
|
||||
valueTyp := value.Type()
|
||||
n := valueTyp.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
field := valueTyp.Field(i)
|
||||
name := appendToPrefix(prefix, field.Name)
|
||||
kind := field.Type.Kind()
|
||||
fieldValue := value.FieldByName(field.Name)
|
||||
switch {
|
||||
case kind == reflect.Struct:
|
||||
if err := mapStructToFile(file, fieldValue.Addr(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.Bool:
|
||||
if err := mapFieldToProperty(file, prefix, field, fieldValue, getBoolValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64:
|
||||
if err := mapFieldToProperty(file, prefix, field, fieldValue, getIntValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64:
|
||||
if err := mapFieldToProperty(file, prefix, field, fieldValue, getUintValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case kind == reflect.String:
|
||||
if err := mapFieldToProperty(file, prefix, field, fieldValue, getStringValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MapToFile(v interface{}) (*File, error) {
|
||||
ptr := reflect.ValueOf(v)
|
||||
file := &File{}
|
||||
err := mapStructToFile(file, ptr, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func save(file *File) ([]string, error) {
|
||||
var lines []string
|
||||
|
||||
var keys []string
|
||||
for key := range file.Sections {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
if key != "" {
|
||||
lines = append(lines, fmt.Sprintf("[%s]", key))
|
||||
}
|
||||
section := file.Sections[key]
|
||||
propertyKeys := section.Keys()
|
||||
for _, key := range propertyKeys {
|
||||
for _, property := range section.Properties {
|
||||
if property.Key != key {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("%s = %s", property.Key, property.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func Save(w io.Writer, file *File) error {
|
||||
lines, err := save(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
liner := &utio.Liner{Lines: lines}
|
||||
return liner.Encode(w)
|
||||
}
|
||||
|
||||
func SaveMap(w io.Writer, v interface{}) error {
|
||||
file, err := MapToFile(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Save(w, file)
|
||||
}
|
||||
|
||||
func SaveFile(path string, file *File) error {
|
||||
return utio.WriteFile(path, func(w io.Writer) error {
|
||||
return Save(w, file)
|
||||
})
|
||||
}
|
||||
|
||||
func SaveFileMap(path string, v interface{}) error {
|
||||
file, err := MapToFile(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return SaveFile(path, file)
|
||||
}
|
38
utini/save_test.go
Normal file
38
utini/save_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package utini
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"opslag.de/schobers/ut/utio"
|
||||
)
|
||||
|
||||
func AssertSaveString(t *testing.T, file *File) string {
|
||||
lines, err := save(file)
|
||||
assert.Nil(t, err)
|
||||
s, _ := utio.EncodeToString(&utio.Liner{Lines: lines})
|
||||
return s
|
||||
}
|
||||
|
||||
func AssertSaveMapString(t *testing.T, v interface{}) string {
|
||||
file, err := MapToFile(v)
|
||||
assert.Nil(t, err)
|
||||
return AssertSaveString(t, file)
|
||||
}
|
||||
|
||||
func TestMapToFile(t *testing.T) {
|
||||
file, err := MapToFile(&testData1Struct)
|
||||
assert.Nil(t, err)
|
||||
s := AssertSaveString(t, file)
|
||||
|
||||
assert.Equal(t, testData1IniFile, s)
|
||||
}
|
||||
|
||||
// TestSaveDefaultValues tests that default values are NOT applied during saving.
|
||||
func TestSaveDefaultValues(t *testing.T) {
|
||||
s := AssertSaveMapString(t, &testDataDefault{})
|
||||
|
||||
assert.Equal(t, `integer = 0
|
||||
string = ""
|
||||
`, s)
|
||||
}
|
53
utini/tags.go
Normal file
53
utini/tags.go
Normal file
@ -0,0 +1,53 @@
|
||||
package utini
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func defaultTagValue(tag string) string {
|
||||
value, _ := tagValueByName(tag, `default`)
|
||||
return value
|
||||
}
|
||||
|
||||
func hasTag(tag, name string) bool {
|
||||
nameValuePrefix := fmt.Sprintf("%s=", name)
|
||||
values := strings.Split(tag, ",")
|
||||
for _, value := range values {
|
||||
if value == name {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(value, nameValuePrefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func iniTag(field reflect.StructField) string {
|
||||
return field.Tag.Get(`ini`)
|
||||
}
|
||||
|
||||
func statesTagValue(tag string) [2]string {
|
||||
if statesTag, ok := tagValueByName(tag, "states"); ok {
|
||||
s := strings.Split(statesTag, "|")
|
||||
if len(s) == 2 {
|
||||
return [2]string{s[0], s[1]}
|
||||
}
|
||||
}
|
||||
return [2]string{"false", "true"}
|
||||
}
|
||||
|
||||
func tagValueByName(tag, name string) (string, bool) {
|
||||
nameValuePrefix := fmt.Sprintf("%s=", name)
|
||||
values := strings.Split(tag, ",")
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if !strings.HasPrefix(value, nameValuePrefix) {
|
||||
continue
|
||||
}
|
||||
return value[len(nameValuePrefix):], true
|
||||
}
|
||||
return "", false
|
||||
}
|
Loading…
Reference in New Issue
Block a user