go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/lucicfg/graph/key.go (about)

     1  // Copyright 2018 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package graph
    16  
    17  import (
    18  	"strconv"
    19  	"strings"
    20  
    21  	"go.starlark.net/starlark"
    22  )
    23  
    24  // Key is a unique identifier of a node in the graph.
    25  //
    26  // It is constructed from a series of (kind: string, id: string) pairs, and once
    27  // constructed it acts as an opaque label, not examinable through Starlark.
    28  //
    29  // From the Starlark side it looks like a ref-like hashable object: keys can be
    30  // compared to each other via == and !=, and can be used in dicts and sets.
    31  //
    32  // Keys live in a KeySet, which represents a universe of all keys and it is also
    33  // responsible for their construction. Keys from different key sets (even if
    34  // they represent same path) are considered not equal and shouldn't be used
    35  // together.
    36  type Key struct {
    37  	set   *KeySet  // the parent key set that created this key
    38  	pairs []string // original list of (kind1, id1, kind2, id2, ...) strings
    39  	idx   int      // index of this key in the KeySet
    40  	cmp   string   // composite string to use to compare keys lexicographically
    41  }
    42  
    43  // Container returns a key with all (kind, id) pairs of this key, except the
    44  // last one, or nil if this key has only one (kind, id) pair.
    45  //
    46  // Usually it represents a container that holds an object represented by the
    47  // this key.
    48  func (k *Key) Container() *Key {
    49  	if len(k.pairs) == 2 {
    50  		return nil
    51  	}
    52  	cont, err := k.set.Key(k.pairs[:len(k.pairs)-2]...)
    53  	if err != nil {
    54  		panic(err) // 'k' has been validated, all its components are thus also valid
    55  	}
    56  	return cont
    57  }
    58  
    59  // ID returns id of the last (kind, id) pair in the key, which usually holds
    60  // a user-friendly name of an object this key represents.
    61  func (k *Key) ID() string {
    62  	return k.pairs[len(k.pairs)-1]
    63  }
    64  
    65  // Kind returns kind of the last (kind, id) pair in the key, which usually
    66  // defines what sort of an object this key represents.
    67  func (k *Key) Kind() string {
    68  	return k.pairs[len(k.pairs)-2]
    69  }
    70  
    71  // Root returns a key with the first (kind, id) pair of this key.
    72  func (k *Key) Root() *Key {
    73  	r, err := k.set.Key(k.pairs[:2]...)
    74  	if err != nil {
    75  		panic(err) // 'k' has been validated, all its components are thus also valid
    76  	}
    77  	return r
    78  }
    79  
    80  // Less returns true if this key is lexicographically before another key.
    81  func (k *Key) Less(an *Key) bool {
    82  	return k.cmp < an.cmp
    83  }
    84  
    85  // String is part of starlark.Value interface.
    86  //
    87  // Returns [kind1("id1"), kind2("id2"), ...]. Must not be parsed, only for
    88  // logging.
    89  func (k *Key) String() string {
    90  	if len(k.pairs)%2 != 0 {
    91  		panic("odd") // never happens, validated by KeySet.Key(...) constructor
    92  	}
    93  	sb := strings.Builder{}
    94  	sb.WriteRune('[')
    95  	for i := 0; i < len(k.pairs)/2; i++ {
    96  		kind, id := k.pairs[i*2], k.pairs[i*2+1]
    97  		if i != 0 {
    98  			sb.WriteString(", ")
    99  		}
   100  		sb.WriteString(kind)
   101  		sb.WriteRune('(')
   102  		sb.WriteString(strconv.Quote(id))
   103  		sb.WriteRune(')')
   104  	}
   105  	sb.WriteRune(']')
   106  	return sb.String()
   107  }
   108  
   109  // Type is a part of starlark.Value interface.
   110  func (k *Key) Type() string { return "graph.key" }
   111  
   112  // Freeze is a part of starlark.Value interface.
   113  func (k *Key) Freeze() {}
   114  
   115  // Truth is a part of starlark.Value interface.
   116  func (k *Key) Truth() starlark.Bool { return starlark.True }
   117  
   118  // Hash is a part of starlark.Value interface.
   119  func (k *Key) Hash() (uint32, error) { return uint32(k.idx), nil }
   120  
   121  // AttrNames is a part of starlark.HasAttrs interface.
   122  func (k *Key) AttrNames() []string {
   123  	return []string{
   124  		"container", // graph.key(...) of the owning container or None
   125  		"id",        // key ID string
   126  		"kind",      // key Kind string
   127  		"root",      // graph.key(...) of the outermost owning container (or self)
   128  	}
   129  }
   130  
   131  // Attr is a part of starlark.HasAttrs interface.
   132  func (k *Key) Attr(name string) (starlark.Value, error) {
   133  	switch name {
   134  	case "container":
   135  		if c := k.Container(); c != nil {
   136  			return c, nil
   137  		}
   138  		return starlark.None, nil
   139  	case "id":
   140  		return starlark.String(k.ID()), nil
   141  	case "kind":
   142  		return starlark.String(k.Kind()), nil
   143  	case "root":
   144  		return k.Root(), nil
   145  	default:
   146  		return nil, nil // per Attr(...) contract
   147  	}
   148  }