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.4 KiB
258 lines
6.4 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 reflects
|
|
|
|
|
|
|
|
type (
|
|
|
|
RStruct struct {
|
|
Fields RFields
|
|
NameIndex map[string]int
|
|
}
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
func NewRStruct (t reflect.Type) *RStruct {
|
|
// 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 &RStruct{
|
|
Fields: fs,
|
|
FieldsIndex: 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
|
|
}
|
|
|
|
*/ |