mirror of
https://github.com/restic/restic.git
synced 2024-08-21 20:03:23 +02:00
183 lines
5.0 KiB
Go
183 lines
5.0 KiB
Go
// Copyright 2017, The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE.md file.
|
|
|
|
package cmpopts
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
)
|
|
|
|
// filterField returns a new Option where opt is only evaluated on paths that
|
|
// include a specific exported field on a single struct type.
|
|
// The struct type is specified by passing in a value of that type.
|
|
//
|
|
// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
|
|
// specific sub-field that is embedded or nested within the parent struct.
|
|
func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
|
|
// TODO: This is currently unexported over concerns of how helper filters
|
|
// can be composed together easily.
|
|
// TODO: Add tests for FilterField.
|
|
|
|
sf := newStructFilter(typ, name)
|
|
return cmp.FilterPath(sf.filter, opt)
|
|
}
|
|
|
|
type structFilter struct {
|
|
t reflect.Type // The root struct type to match on
|
|
ft fieldTree // Tree of fields to match on
|
|
}
|
|
|
|
func newStructFilter(typ interface{}, names ...string) structFilter {
|
|
// TODO: Perhaps allow * as a special identifier to allow ignoring any
|
|
// number of path steps until the next field match?
|
|
// This could be useful when a concrete struct gets transformed into
|
|
// an anonymous struct where it is not possible to specify that by type,
|
|
// but the transformer happens to provide guarantees about the names of
|
|
// the transformed fields.
|
|
|
|
t := reflect.TypeOf(typ)
|
|
if t == nil || t.Kind() != reflect.Struct {
|
|
panic(fmt.Sprintf("%T must be a struct", typ))
|
|
}
|
|
var ft fieldTree
|
|
for _, name := range names {
|
|
cname, err := canonicalName(t, name)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
|
|
}
|
|
ft.insert(cname)
|
|
}
|
|
return structFilter{t, ft}
|
|
}
|
|
|
|
func (sf structFilter) filter(p cmp.Path) bool {
|
|
for i, ps := range p {
|
|
if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// fieldTree represents a set of dot-separated identifiers.
|
|
//
|
|
// For example, inserting the following selectors:
|
|
// Foo
|
|
// Foo.Bar.Baz
|
|
// Foo.Buzz
|
|
// Nuka.Cola.Quantum
|
|
//
|
|
// Results in a tree of the form:
|
|
// {sub: {
|
|
// "Foo": {ok: true, sub: {
|
|
// "Bar": {sub: {
|
|
// "Baz": {ok: true},
|
|
// }},
|
|
// "Buzz": {ok: true},
|
|
// }},
|
|
// "Nuka": {sub: {
|
|
// "Cola": {sub: {
|
|
// "Quantum": {ok: true},
|
|
// }},
|
|
// }},
|
|
// }}
|
|
type fieldTree struct {
|
|
ok bool // Whether this is a specified node
|
|
sub map[string]fieldTree // The sub-tree of fields under this node
|
|
}
|
|
|
|
// insert inserts a sequence of field accesses into the tree.
|
|
func (ft *fieldTree) insert(cname []string) {
|
|
if ft.sub == nil {
|
|
ft.sub = make(map[string]fieldTree)
|
|
}
|
|
if len(cname) == 0 {
|
|
ft.ok = true
|
|
return
|
|
}
|
|
sub := ft.sub[cname[0]]
|
|
sub.insert(cname[1:])
|
|
ft.sub[cname[0]] = sub
|
|
}
|
|
|
|
// matchPrefix reports whether any selector in the fieldTree matches
|
|
// the start of path p.
|
|
func (ft fieldTree) matchPrefix(p cmp.Path) bool {
|
|
for _, ps := range p {
|
|
switch ps := ps.(type) {
|
|
case cmp.StructField:
|
|
ft = ft.sub[ps.Name()]
|
|
if ft.ok {
|
|
return true
|
|
}
|
|
if len(ft.sub) == 0 {
|
|
return false
|
|
}
|
|
case cmp.Indirect:
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// canonicalName returns a list of identifiers where any struct field access
|
|
// through an embedded field is expanded to include the names of the embedded
|
|
// types themselves.
|
|
//
|
|
// For example, suppose field "Foo" is not directly in the parent struct,
|
|
// but actually from an embedded struct of type "Bar". Then, the canonical name
|
|
// of "Foo" is actually "Bar.Foo".
|
|
//
|
|
// Suppose field "Foo" is not directly in the parent struct, but actually
|
|
// a field in two different embedded structs of types "Bar" and "Baz".
|
|
// Then the selector "Foo" causes a panic since it is ambiguous which one it
|
|
// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
|
|
func canonicalName(t reflect.Type, sel string) ([]string, error) {
|
|
var name string
|
|
sel = strings.TrimPrefix(sel, ".")
|
|
if sel == "" {
|
|
return nil, fmt.Errorf("name must not be empty")
|
|
}
|
|
if i := strings.IndexByte(sel, '.'); i < 0 {
|
|
name, sel = sel, ""
|
|
} else {
|
|
name, sel = sel[:i], sel[i:]
|
|
}
|
|
|
|
// Type must be a struct or pointer to struct.
|
|
if t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
}
|
|
if t.Kind() != reflect.Struct {
|
|
return nil, fmt.Errorf("%v must be a struct", t)
|
|
}
|
|
|
|
// Find the canonical name for this current field name.
|
|
// If the field exists in an embedded struct, then it will be expanded.
|
|
if !isExported(name) {
|
|
// Disallow unexported fields:
|
|
// * To discourage people from actually touching unexported fields
|
|
// * FieldByName is buggy (https://golang.org/issue/4876)
|
|
return []string{name}, fmt.Errorf("name must be exported")
|
|
}
|
|
sf, ok := t.FieldByName(name)
|
|
if !ok {
|
|
return []string{name}, fmt.Errorf("does not exist")
|
|
}
|
|
var ss []string
|
|
for i := range sf.Index {
|
|
ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
|
|
}
|
|
if sel == "" {
|
|
return ss, nil
|
|
}
|
|
ssPost, err := canonicalName(sf.Type, sel)
|
|
return append(ss, ssPost...), err
|
|
}
|