package rui import ( "fmt" "iter" "os" "reflect" "slices" "strconv" "strings" ) // DataObject interface of a data object type DataObject interface { DataValue // Tag returns data object tag Tag() string // Properties() returns an iterator to access the properties Properties() iter.Seq2[string, DataValue] // TextProperties() returns an iterator to access the text properties. Objects and Arrays are skipped. TextProperties() iter.Seq2[string, string] // PropertyCount returns properties count PropertyCount() int // PropertyTags() returns an iterator to access the property tags PropertyTags() iter.Seq[string] // SetSortedTags specifies whether properties will be sorted by tag SetSortedTags(sorted bool) // Property returns a data node corresponding to the specified chain of tags. // If such a property does not exist, then nil is returned. // // A chain of tags is specified by the "tag" and "tags" arguments. // For example // // p := object.Property("tag1", "tag2", "tag2") // // equivalent to the following code // // var p DataValue // p = nil // if p1 := object.Property("tag1"); p1 != nil { // if o1 := p1.Object(); o1 != nil { // if p2 := object.Property("tag2"); p2 != nil { // if o2 := p2.Object(); o2 != nil { // p = object.Property("tag3") // } // } // } // } Property(tag string, tags ...string) DataValue // PropertyValue returns a string value of a property with the specified chain of tags (see Property method). PropertyText(tag string, tags ...string) (string, bool) // PropertyObject returns an object value of a property with the specified chain of tags (see Property method). // If such a property does not exist or the value is not a DataObject, then nil is returned. PropertyObject(tag string, tags ...string) DataObject // PropertyObject returns an array value of a property with the specified chain of tags (see Property method). // If such a property does not exist or the value is not a DataArray, then nil is returned. PropertyArray(tag string, tags ...string) DataArray // SetProperty sets a value of a property with a specific tag SetProperty(tag string, value DataValue) // SetPropertyText sets a string value of a property with a specific tag SetPropertyText(tag, value string) // PropertyByTag removes a data node corresponding to a property tag and returns it RemoveProperty(tag string) DataValue // ToParams create a params(map) representation of a data object ToParams() Params // ToStruct fills the struct fields with property values of the DataObject. // A pointer to the struct to be filled is passed as an argument. ToStruct(data any) // WriteToFile converts the data object to text and write it to the named file, creating it if necessary. // If the file does not exist, WriteToFile creates it with permissions perm (before umask); // otherwise WriteFile truncates it before writing, without changing permissions. WriteToFile(path string, perm os.FileMode) error } type dataObjectProperty struct { tag string value DataValue } type dataObject struct { tag string sorted bool property []dataObjectProperty } func (value *dataObject) String() string { buffer := allocStringBuilder() defer freeStringBuilder(buffer) value.writeToBuffer(buffer, "") return buffer.String() } func (object *dataObject) writeToBuffer(buffer *strings.Builder, indent string) { if len(object.tag) > 0 { writeRuiText(buffer, object.tag, true) } if len(object.property) == 0 { buffer.WriteString("{}") return } indent2 := indent + "\t" buffer.WriteString("{\n") for _, property := range object.property { buffer.WriteString(indent2) writeRuiText(buffer, property.tag, true) buffer.WriteString(" = ") property.value.writeToBuffer(buffer, indent2) buffer.WriteString(",\n") } buffer.WriteString(indent) buffer.WriteString("}") } func (object *dataObject) Type() DataValueType { return ObjectDataValue } func (object *dataObject) Object() DataObject { return object } func (value *dataObject) Array() DataArray { return nil } func (object *dataObject) Tag() string { return object.tag } func (object *dataObject) Properties() iter.Seq2[string, DataValue] { return func(yield func(string, DataValue) bool) { for _, property := range object.property { if !yield(property.tag, property.value) { return } } } } func (object *dataObject) TextProperties() iter.Seq2[string, string] { return func(yield func(string, string) bool) { for _, property := range object.property { if property.value.Type() == TextDataValue { if !yield(property.tag, property.value.String()) { return } } } } } func (object *dataObject) PropertyCount() int { return len(object.property) } func (object *dataObject) PropertyTags() iter.Seq[string] { return func(yield func(string) bool) { for _, property := range object.property { if !yield(property.tag) { return } } } } func (object *dataObject) SetSortedTags(sorted bool) { object.sorted = sorted if sorted { slices.SortFunc(object.property, func(e1, e2 dataObjectProperty) int { return strings.Compare(e1.tag, e2.tag) }) } } func (object *dataObject) indexByTag(tag string) int { if object.property != nil { return slices.IndexFunc(object.property, func(property dataObjectProperty) bool { return property.tag == tag }) } return -1 } func (object *dataObject) Property(tag string, tags ...string) DataValue { if index := object.indexByTag(tag); index >= 0 { property := object.property[index].value if len(tags) > 0 { for _, t := range tags { obj := property.Object() if obj == nil { return nil } property = obj.Property(t) if property == nil { return nil } } } return property } return nil } func (object *dataObject) RemoveProperty(tag string) DataValue { if object.property != nil { if index := object.indexByTag(tag); index >= 0 { node := object.property[index] switch index { case 0: object.property = object.property[1:] case len(object.property) - 1: object.property = object.property[:len(object.property)-1] default: object.property = append(object.property[:index], object.property[index+1:]...) } return node.value } } return nil } func (object *dataObject) PropertyText(tag string, tags ...string) (string, bool) { if node := object.Property(tag, tag); node != nil && node.Type() == TextDataValue { return node.String(), true } return "", false } func (object *dataObject) PropertyObject(tag string, tags ...string) DataObject { if node := object.Property(tag); node != nil { return node.Object() } return nil } func (object *dataObject) PropertyArray(tag string, tags ...string) DataArray { if node := object.Property(tag); node != nil { return node.Array() } return nil } func (object *dataObject) SetProperty(tag string, value DataValue) { if value == nil { object.RemoveProperty(tag) return } if index := object.indexByTag(tag); index >= 0 { object.property[index].value = value } else { object.property = append(object.property, dataObjectProperty{ tag: tag, value: value, }) if object.sorted { slices.SortFunc(object.property, func(e1, e2 dataObjectProperty) int { return strings.Compare(e1.tag, e2.tag) }) } } } // SetPropertyValue - set a string property with tag by value func (object *dataObject) SetPropertyText(tag, text string) { object.SetProperty(tag, NewTextData(text)) } func (object *dataObject) ToParams() Params { params := Params{} for _, node := range object.property { switch node.value.Type() { case TextDataValue: if text := node.value.String(); text != "" { params[PropertyName(node.tag)] = text } case ObjectDataValue: if obj := node.value.Object(); obj != nil { params[PropertyName(node.tag)] = obj } case ArrayDataValue: if nodeArray := node.value.Array(); nodeArray != nil { array := make([]any, 0, nodeArray.Size()) for data := range nodeArray.Elements() { if data != nil { switch data.Type() { case TextDataValue: if text := data.String(); text != "" { array = append(array, text) } case ArrayDataValue: if dataArray := data.Array(); dataArray != nil && dataArray.Size() > 0 { array = append(array, dataArray) } default: array = append(array, data) } } } if len(array) > 0 { params[PropertyName(node.tag)] = array } } } } return params } func copyToReflectSlice(array DataArray, value reflect.Value) { val := reflect.New(value.Type()) val = val.Elem() switch val.Type().Elem().Kind() { case reflect.Pointer: for data := range array.Elements() { s := reflect.New(val.Type().Elem()).Elem() copyToReflectValue(data, s) val = reflect.Append(val, s) } case reflect.String: for text := range array.TextElements() { val = reflect.Append(val, reflect.ValueOf(text)) } case reflect.Int: for text := range array.TextElements() { if n, err := strconv.ParseInt(text, 10, 64); err == nil { val = reflect.Append(val, reflect.ValueOf(int(n))) } } case reflect.Int8: for text := range array.TextElements() { if n, err := strconv.ParseInt(text, 10, 8); err == nil { val = reflect.Append(val, reflect.ValueOf(int8(n))) } } case reflect.Int16: for text := range array.TextElements() { if n, err := strconv.ParseInt(text, 10, 16); err == nil { val = reflect.Append(val, reflect.ValueOf(int16(n))) } } case reflect.Int32: for text := range array.TextElements() { if n, err := strconv.ParseInt(text, 10, 32); err == nil { val = reflect.Append(val, reflect.ValueOf(int32(n))) } } case reflect.Int64: for text := range array.TextElements() { if n, err := strconv.ParseInt(text, 10, 64); err == nil { val = reflect.Append(val, reflect.ValueOf(n)) } } case reflect.Uint: for text := range array.TextElements() { if n, err := strconv.ParseUint(text, 10, 64); err == nil { val = reflect.Append(val, reflect.ValueOf(uint(n))) } } case reflect.Uint8: for text := range array.TextElements() { if n, err := strconv.ParseUint(text, 10, 8); err == nil { val = reflect.Append(val, reflect.ValueOf(uint8(n))) } } case reflect.Uint16: for text := range array.TextElements() { if n, err := strconv.ParseUint(text, 10, 16); err == nil { val = reflect.Append(val, reflect.ValueOf(uint16(n))) } } case reflect.Uint32: for text := range array.TextElements() { if n, err := strconv.ParseUint(text, 10, 32); err == nil { val = reflect.Append(val, reflect.ValueOf(uint32(n))) } } case reflect.Uint64: for text := range array.TextElements() { if n, err := strconv.ParseUint(text, 10, 64); err == nil { val = reflect.Append(val, reflect.ValueOf(n)) } } case reflect.Float32: for text := range array.TextElements() { if n, err := strconv.ParseFloat(text, 32); err == nil { val = reflect.Append(val, reflect.ValueOf(float32(n))) } } case reflect.Float64: for text := range array.TextElements() { if n, err := strconv.ParseFloat(text, 64); err == nil { val = reflect.Append(val, reflect.ValueOf(n)) } } case reflect.Bool: for text := range array.TextElements() { switch text { case "true", "1": val = reflect.Append(val, reflect.ValueOf(true)) case "false", "0": val = reflect.Append(val, reflect.ValueOf(false)) } } case reflect.Struct: for data := range array.Elements() { if obj := data.Object(); obj != nil { s := reflect.New(val.Type().Elem()) if s.Kind() == reflect.Pointer { s = s.Elem() } copyToReflectStruct(obj, s) val = reflect.Append(val, s) } } case reflect.Slice: for data := range array.Elements() { if array := data.Array(); array != nil { s := reflect.New(val.Type().Elem()) if s.Kind() == reflect.Pointer { s = s.Elem() } copyToReflectSlice(array, s) val = reflect.Append(val, s) } } case reflect.Array: for data := range array.Elements() { if array := data.Array(); array != nil { s := reflect.New(val.Type().Elem()) if s.Kind() == reflect.Pointer { s = s.Elem() } copyToReflectArray(array, s) val = reflect.Append(val, s) } } case reflect.Map: for data := range array.Elements() { if obj := data.Object(); obj != nil { s := reflect.MakeMap(val.Type().Elem()) copyToReflectMap(obj, s) val = reflect.Append(val, s) } } default: ErrorLog("unsupported slice element type") } value.Set(val) } func copyToReflectArray(array DataArray, val reflect.Value) { i := 0 maxSize := val.Len() switch val.Type().Elem().Kind() { case reflect.Pointer: for data := range array.Elements() { if i >= maxSize { break } value := val.Index(i) copyToReflectValue(data, value) i++ } case reflect.String: for text := range array.TextElements() { if i >= maxSize { break } value := val.Index(i) value.SetString(text) i++ } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: for text := range array.TextElements() { if n, err := strconv.ParseInt(text, 10, 64); err == nil { if i >= maxSize { break } value := val.Index(i) value.SetInt(n) i++ } } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: for text := range array.TextElements() { if n, err := strconv.ParseUint(text, 10, 64); err == nil { if i >= maxSize { break } value := val.Index(i) value.SetUint(n) i++ } } case reflect.Float32, reflect.Float64: for text := range array.TextElements() { if n, err := strconv.ParseFloat(text, 64); err == nil { if i >= maxSize { break } value := val.Index(i) value.SetFloat(n) i++ } } case reflect.Bool: for text := range array.TextElements() { if i >= maxSize { break } switch text { case "true", "1": value := val.Index(i) value.SetBool(true) i++ case "false", "0": value := val.Index(i) value.SetBool(false) i++ } } case reflect.Struct: for data := range array.Elements() { if obj := data.Object(); obj != nil { if i >= maxSize { break } value := val.Index(i) copyToReflectStruct(obj, value) i++ } } case reflect.Map: for data := range array.Elements() { if obj := data.Object(); obj != nil { if i >= maxSize { break } value := val.Index(i) copyToReflectMap(obj, value) i++ } } case reflect.Slice: for data := range array.Elements() { if array := data.Array(); array != nil { if i >= maxSize { break } value := val.Index(i) copyToReflectSlice(array, value) i++ } } case reflect.Array: for data := range array.Elements() { if array := data.Array(); array != nil { if i >= maxSize { break } value := val.Index(i) copyToReflectArray(array, value) i++ } } default: ErrorLog("unsupported array element type") } } func copyToReflectMap(object DataObject, val reflect.Value) { mapValue := reflect.MakeMap(val.Type()) for tag, data := range object.Properties() { key := reflect.New(mapValue.Type().Key()) if key.Kind() == reflect.Pointer { key = key.Elem() } switch key.Kind() { case reflect.String: key.SetString(tag) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if n, err := strconv.ParseInt(tag, 10, 64); err == nil { key.SetInt(n) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if n, err := strconv.ParseUint(tag, 10, 64); err == nil { key.SetUint(n) } case reflect.Float32, reflect.Float64: if n, err := strconv.ParseFloat(tag, 64); err == nil { key.SetFloat(n) } case reflect.Bool: switch tag { case "true", "1": key.SetBool(true) case "false", "0": key.SetBool(false) } default: ErrorLog("unsupported map key type") return } value := reflect.New(mapValue.Type().Elem()).Elem() copyToReflectValue(data, value) mapValue.SetMapIndex(key, value) } val.Set(mapValue) } func copyToReflectValue(data DataValue, value reflect.Value) { if value.Kind() == reflect.Pointer { v := reflect.New(value.Type().Elem()) value.Set(v) value = v.Elem() } switch value.Kind() { case reflect.Struct: if obj := data.Object(); obj != nil { copyToReflectStruct(obj, value) } case reflect.String: if data.Type() == TextDataValue { value.SetString(data.String()) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if data.Type() == TextDataValue { if n, err := strconv.ParseInt(data.String(), 10, 64); err == nil { value.SetInt(n) } } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if data.Type() == TextDataValue { if n, err := strconv.ParseUint(data.String(), 10, 64); err == nil { value.SetUint(n) } } case reflect.Float32, reflect.Float64: if data.Type() == TextDataValue { if n, err := strconv.ParseFloat(data.String(), 64); err == nil { value.SetFloat(n) } } case reflect.Bool: if data.Type() == TextDataValue { switch data.String() { case "true", "1": value.SetBool(true) case "false", "0": value.SetBool(false) } } case reflect.Slice: if array := data.Array(); array != nil { copyToReflectSlice(array, value) } case reflect.Array: if array := data.Array(); array != nil { copyToReflectArray(array, value) } case reflect.Map: if obj := data.Object(); obj != nil { copyToReflectMap(obj, value) } default: ErrorLog("unsupported element type") } } func copyToReflectStruct(object DataObject, val reflect.Value) { for i := range val.NumField() { value := val.Field(i) field := val.Type().Field(i) fieldName := field.Name if tag := field.Tag.Get("rui"); tag != "" { params := parseFieldTag(tag) if name, ok := params["id"]; ok && name != "" { fieldName = name } } if value.CanSet() { if data := object.Property(fieldName); data != nil { copyToReflectValue(data, value) } } else { DebugLogF("field %s can not be set", fieldName) } } } func (object *dataObject) ToStruct(data any) { if data != nil { value := reflect.ValueOf(data) if value.Kind() == reflect.Pointer { value = value.Elem() } if value.Kind() == reflect.Struct { copyToReflectStruct(object, value) } } } func (object *dataObject) WriteToFile(path string, perm os.FileMode) error { text := object.String() return os.WriteFile(path, []byte(text), perm) } func reflectArrayToDataValue(value reflect.Value) DataValue { count := value.Len() if count == 0 { ErrorLog("empty array") return nil } kind := value.Type().Elem().Kind() if kind == reflect.Pointer { kind = value.Type().Elem().Elem().Kind() } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.String, reflect.Float32, reflect.Float64, reflect.Bool: array := make([]DataValue, 0, count) for i := range count { if e := reflectValueToDataValue(value.Index(i)); e != nil { array = append(array, e) } } result := NewDataArray(array) if count < 10 { result.SetSingleLineString(true) } return result case reflect.Struct, reflect.Array, reflect.Slice: array := make([]DataValue, 0, count) for i := range count { if e := reflectValueToDataValue(value.Index(i)); e != nil { array = append(array, e) } } return NewDataArray(array) } ErrorLog("unsupported array element type") return nil } func reflectMapToDataValue(value reflect.Value) DataValue { keys := value.MapKeys() count := len(keys) if count == 0 { return nil } obj := new(dataObject) obj.tag = "_" obj.property = make([]dataObjectProperty, 0, count) for _, key := range keys { tag := "" switch key.Kind() { case reflect.String: tag = key.String() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: tag = strconv.FormatInt(key.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: tag = strconv.FormatUint(key.Uint(), 10) case reflect.Float32, reflect.Float64: tag = fmt.Sprintf("%g", key.Float()) case reflect.Bool: tag = fmt.Sprintf("%t", key.Bool()) default: ErrorLog("unsupported map key type") return nil } if tag != "" { if data := reflectValueToDataValue(value.MapIndex(key)); data != nil { obj.property = append(obj.property, dataObjectProperty{tag: tag, value: data}) } } else { DebugLog("empty map key") } } if len(obj.property) == 0 { DebugLog("empty map") return nil } return obj } func reflectValueToDataValue(value reflect.Value) DataValue { for value.Kind() == reflect.Pointer && !value.IsNil() { value = value.Elem() } switch value.Kind() { case reflect.Struct: return reflectStructToDataObject(value) case reflect.String: return NewTextData(value.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return NewTextData(value.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return NewTextData(value.Uint()) case reflect.Float32, reflect.Float64: return NewTextData(value.Float()) case reflect.Bool: return NewTextData(value.Bool()) case reflect.Slice, reflect.Array: return reflectArrayToDataValue(value) case reflect.Map: return reflectMapToDataValue(value) } ErrorLog("unsupported element type") return nil } func parseFieldTag(tag string) map[string]string { result := map[string]string{} for line := range strings.SplitSeq(tag, ";") { pair := strings.SplitN(line, "=", 2) if len(pair) == 2 { key := strings.Trim(pair[0], " \t") value := strings.Trim(pair[1], " \t") if key != "" && value != "" { result[key] = value } } } return result } func reflectStructToDataObject(val reflect.Value) DataObject { obj := new(dataObject) obj.tag = val.Type().Name() obj.property = []dataObjectProperty{} for field, value := range val.Fields() { if data := reflectValueToDataValue(value); data != nil { fieldName := field.Name if tag := field.Tag.Get("rui"); tag != "" { params := parseFieldTag(tag) if name, ok := params["id"]; ok && name != "" { fieldName = name } } obj.property = append(obj.property, dataObjectProperty{tag: fieldName, value: data}) } } return obj } // NewDataObject create new DataObject with the tag and empty property list func NewDataObject(tag string) DataObject { obj := new(dataObject) obj.tag = tag obj.property = []dataObjectProperty{} return obj } // NewDataObjectFromStruct converts a struct to DataObject. // // A struct or a pointer to a struct is passed as a function argument. // If other types of data are passed, the function returns nil. // // Depending on the type, the following transformation of structure fields is performed: // - string, int, int8...int64, uint, uint8...uint64, float32, float64, and bool are converted into DataText; // - struct and map are converted into DataObject; // - array and slice are converted into DataArray. // // The names of the corresponding structure fields are used as names. // A name can be redefined using a tag `rui:"id=your_name"`. Example: // // type A struct { // X int `rui:"id=y"` // } func NewDataObjectFromStruct(data any) DataObject { if data == nil { return nil } value := reflect.ValueOf(data) if value.Kind() == reflect.Pointer { value = value.Elem() } if value.Kind() == reflect.Struct { return reflectStructToDataObject(value) } return nil }