You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
6.5 KiB

// ------------------------------------------------------------------------
// Project atila
// Active Thing (activething.com) git.activething.com/go
//
// File name rRFields_type.go
// Created by DEV
// Modified 26/01/2024
//
// Copyright 2024 activething.com
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
package packs
import (
"bytes"
"reflect"
"sort"
"strings"
"unicode"
)
type (
RTypeFields struct {
Fields RFields
Index map[string]int
}
)
func NewRTypeFields (t reflect.Type) *RTypeFields {
// Anonymous Fields to explore at the current level and the next.
current := RFields{}
next := []RField{{RType: t}}
// Count of queued names for current level and the next.
var count, nextCount map[reflect.Type]int
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fs []RField
// Buffer to run HTMLEscape on RField names.
var nameEscBuf bytes.Buffer
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.RType] {
continue
}
visited[f.RType] = true
// Scan f.RType for Fields to include.
for i := 0; i < f.RType.NumField(); i++ {
var flags RFieldFlags
sf := f.RType.Field(i)
if sf.Anonymous {
t := sf.Type
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if !sf.IsExported() && t.Kind() != reflect.Struct {
// Ignore embedded Fields of unexported non-struct types.
continue
}
// Do not ignore embedded Fields of unexported struct types
// since they may have exported Fields.
} else if !sf.IsExported() {
// Ignore unexported non-embedded Fields.
continue
}
tag := sf.Tag.Get("json")
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if !isValidTag(name) {
name = ""
}
index := make([]int, len(f.Index)+1)
copy(index, f.Index)
index[len(f.Index)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Pointer {
// Follow pointer.
ft = ft.Elem()
}
// Only strings, floats, integers, and booleans can be quoted.
if opts.Contains("string") {
switch ft.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64,
reflect.String:
flags|=RFieldQuoted
}
}
// Record found RField and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
if name != "" {
flags |= RFieldFlagTagged
}
if name == "" {
name = sf.Name
}
rf := RField{
Name : name,
Index: index,
RType: ft,
}
/*
if opts.Contains("omitempty") {
rf.Flags|=RFieldOmitEmpty
}
*/
rf.BName = []byte(rf.Name)
//rf.EqualFold = foldFunc(rf.BName)
// Build nameEscHTML and nameNonEsc ahead of time.
nameEscBuf.Reset()
nameEscBuf.WriteString(`"`)
//HTMLEscape(&nameEscBuf, rf.BName)
nameEscBuf.WriteString(`":`)
rf.HName = nameEscBuf.String()
//rf. = `"` + rf.Name + `":`
fs = append(fs, rf)
if count[f.RType] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fs = append(fs, fs[len(fs)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, RField{Name: ft.Name(), Index: index, RType: ft})
}
}
}
}
sort.Slice(fs, func(i, j int) bool {
x := fs
// sort RField by name, breaking ties with depth, then
// breaking ties with "name came from json tag", then
// breaking ties with index sequence.
if x[i].Name != x[j].Name {
return x[i].Name < x[j].Name
}
if len(x[i].Index) != len(x[j].Index) {
return len(x[i].Index) < len(x[j].Index)
}
if x[i].Tagged() != x[j].Tagged() {
return x[i].Tagged()
}
return RFieldsByIndex(x).Less(i, j)
})
// Delete all Fields that are hidden by the Go rules for embedded Fields,
// except that Fields with JSON tags are promoted.
// The Fields are sorted in primary order of name, secondary order
// of RField index length. Loop over names; for each name, delete
// hidden Fields by choosing the one dominant RField that survives.
out := fs[:0]
for advance, i := 0, 0; i < len(fs); i += advance {
// One iteration per name.
// Find the sequence of Fields with the name of this first RField.
fi := fs[i]
name := fi.Name
for advance = 1; i+advance < len(fs); advance++ {
fj := fs[i+advance]
if fj.Name != name {
break
}
}
if advance == 1 { // Only one RField with this name
out = append(out, fi)
continue
}
dominant, ok := RFields(fs[i : i+advance]).Dominant()
if ok {
out = append(out, dominant)
}
}
fs = out
sort.Sort(RFieldsByIndex(fs))
for i := range fs {
//f := &fs[i]
//f.encoder = typeEncoder(typeByIndex(t, f.index))
}
nameIndex := make(map[string]int, len(fs))
for i, f := range fs {
nameIndex[f.Name] = i
}
return &RTypeFields{
Fields: fs,
Index : nameIndex,
}
}
func parseTag(tag string) (string, tagOptions) {
tag, opt, _ := strings.Cut(tag, ",")
return tag, tagOptions(opt)
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
case !unicode.IsLetter(c) && !unicode.IsDigit(c):
return false
}
}
return true
}

Powered by TurnKey Linux.