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) }