Compare commits
No commits in common. "2f614c777dc43cb1ed6b0e61b2edec2d18cb51d1" and "ffc7c1e89bf22db18a09a30942278e7e3efefd41" have entirely different histories.
2f614c777d
...
ffc7c1e89b
@ -1,56 +0,0 @@
|
|||||||
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
214
utini/load.go
@ -1,214 +0,0 @@
|
|||||||
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
|
|
@ -1,97 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
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
173
utini/save.go
@ -1,173 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -17,15 +17,6 @@ func EncodeToString(e Encoder) (string, error) {
|
|||||||
return WriteToString(e.Encode)
|
return WriteToString(e.Encode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustEncodeToString returns the encoded string value and panics if an error has occurred
|
|
||||||
func MustEncodeToString(e Encoder) string {
|
|
||||||
s, err := WriteToString(e.Encode)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToString returns the string value of the data written to fn.
|
// WriteToString returns the string value of the data written to fn.
|
||||||
func WriteToString(fn WriteFunc) (string, error) {
|
func WriteToString(fn WriteFunc) (string, error) {
|
||||||
var b = &bytes.Buffer{}
|
var b = &bytes.Buffer{}
|
||||||
|
Loading…
Reference in New Issue
Block a user