mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
e31ff547d2
Closes #15228
1056 lines
23 KiB
Go
1056 lines
23 KiB
Go
// Copyright 2025 The Hugo Authors. All rights reserved.
|
|
//
|
|
// 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 sitesmatrix
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
"maps"
|
|
"slices"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/gohugoio/hashstructure"
|
|
"github.com/gohugoio/hugo/common/hashing"
|
|
"github.com/gohugoio/hugo/common/hmaps"
|
|
"github.com/gohugoio/hugo/common/predicate"
|
|
"github.com/gohugoio/hugo/common/types"
|
|
"github.com/gohugoio/hugo/hugofs/hglob"
|
|
)
|
|
|
|
var (
|
|
_ VectorStore = &IntSets{}
|
|
_ VectorStore = &vectorStoreMap{}
|
|
_ hashstructure.Hashable = &IntSets{}
|
|
_ hashstructure.Hashable = &vectorStoreMap{}
|
|
)
|
|
|
|
func newVectorStoreMap(cap int) *vectorStoreMap {
|
|
return &vectorStoreMap{
|
|
sets: make(Vectors, cap),
|
|
h: &hashOnce{},
|
|
}
|
|
}
|
|
|
|
func newVectorStoreMapFromVectors(v Vectors) *vectorStoreMap {
|
|
return &vectorStoreMap{
|
|
sets: v,
|
|
h: &hashOnce{},
|
|
}
|
|
}
|
|
|
|
// A vector store backed by a map.
|
|
type vectorStoreMap struct {
|
|
sets Vectors
|
|
h *hashOnce
|
|
}
|
|
|
|
func (s *vectorStoreMap) initHash() {
|
|
s.h.once.Do(func() {
|
|
var err error
|
|
s.h.hash, err = hashing.Hash(s.sets)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to calculate hash for MapVectorStore: %w", err))
|
|
}
|
|
})
|
|
}
|
|
|
|
func (s *vectorStoreMap) setVector(vec Vector) {
|
|
s.sets[vec] = struct{}{}
|
|
}
|
|
|
|
func (s *vectorStoreMap) KeysSorted() ([]int, []int, []int) {
|
|
var k0, k1, k2 []int
|
|
for v := range s.sets {
|
|
k0 = append(k0, v.Language())
|
|
k1 = append(k1, v.Version())
|
|
k2 = append(k2, v.Role())
|
|
}
|
|
sort.Ints(k0)
|
|
sort.Ints(k1)
|
|
sort.Ints(k2)
|
|
k0 = slices.Compact(k0)
|
|
k1 = slices.Compact(k1)
|
|
k2 = slices.Compact(k2)
|
|
|
|
return k0, k1, k2
|
|
}
|
|
|
|
func (s *vectorStoreMap) Hash() (uint64, error) {
|
|
s.initHash()
|
|
return s.h.hash, nil
|
|
}
|
|
|
|
func (s *vectorStoreMap) MustHash() uint64 {
|
|
i, _ := s.Hash()
|
|
return i
|
|
}
|
|
|
|
func (s *vectorStoreMap) HasVector(v Vector) bool {
|
|
if _, ok := s.sets[v]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *vectorStoreMap) HasAnyVector(v VectorProvider) bool {
|
|
if v == nil || s.LenVectors() == 0 || v.LenVectors() == 0 {
|
|
return false
|
|
}
|
|
|
|
return !v.ForEachVector(func(vec Vector) bool {
|
|
if s.HasVector(vec) {
|
|
return false // stop iteration
|
|
}
|
|
return true // continue iteration
|
|
})
|
|
}
|
|
|
|
func (s *vectorStoreMap) LenVectors() int {
|
|
return len(s.sets)
|
|
}
|
|
|
|
// Complement returns a new VectorStore that contains all vectors in s that are not in any of ss.
|
|
func (s *vectorStoreMap) Complement(ss ...VectorProvider) VectorStore {
|
|
if len(ss) == 0 || (len(ss) == 1 && ss[0] == s) {
|
|
return nil
|
|
}
|
|
|
|
for _, v := range ss {
|
|
if v == s {
|
|
var s *vectorStoreMap
|
|
return s
|
|
}
|
|
}
|
|
|
|
result := newVectorStoreMap(36)
|
|
|
|
s.ForEachVector(func(vec Vector) bool {
|
|
var found bool
|
|
for _, v := range ss {
|
|
if v.HasVector(vec) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
result.setVector(vec)
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *vectorStoreMap) EqualsVector(other VectorProvider) bool {
|
|
if other == nil {
|
|
return false
|
|
}
|
|
if s == other {
|
|
return true
|
|
}
|
|
if s.LenVectors() != other.LenVectors() {
|
|
return false
|
|
}
|
|
return other.ForEachVector(func(v Vector) bool {
|
|
_, ok := s.sets[v]
|
|
return ok
|
|
})
|
|
}
|
|
|
|
func (s *vectorStoreMap) VectorSample() Vector {
|
|
if len(s.sets) == 0 {
|
|
panic("no vectors available")
|
|
}
|
|
for v := range s.sets {
|
|
return v
|
|
}
|
|
panic("unreachable")
|
|
}
|
|
|
|
func (s *vectorStoreMap) ForEachVector(yield func(v Vector) bool) bool {
|
|
if len(s.sets) == 0 {
|
|
return true
|
|
}
|
|
for v := range s.sets {
|
|
if !yield(v) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *vectorStoreMap) Vectors() []Vector {
|
|
if s == nil || len(s.sets) == 0 {
|
|
return nil
|
|
}
|
|
var vectors []Vector
|
|
for v := range s.sets {
|
|
vectors = append(vectors, v)
|
|
}
|
|
sort.Slice(vectors, func(i, j int) bool {
|
|
v1, v2 := vectors[i], vectors[j]
|
|
return v1.Compare(v2) < 0
|
|
})
|
|
return vectors
|
|
}
|
|
|
|
func (s *vectorStoreMap) WithLanguageIndices(i ...int) VectorStore {
|
|
c := s.clone()
|
|
|
|
for v := range c.sets {
|
|
for _, i := range i {
|
|
nv := v
|
|
nv[Language] = i
|
|
c.sets[nv] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
func (s *vectorStoreMap) WithVersionIndices(indices ...int) VectorStore {
|
|
old := s.clone()
|
|
c := newVectorStoreMap(len(old.sets) * len(indices))
|
|
for v := range old.sets {
|
|
for _, i := range indices {
|
|
nv := v
|
|
nv[Version] = i
|
|
c.sets[nv] = struct{}{}
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (s *vectorStoreMap) WithRoleIndices(indices ...int) VectorStore {
|
|
old := s.clone()
|
|
c := newVectorStoreMap(len(old.sets) * len(indices))
|
|
for v := range old.sets {
|
|
for _, i := range indices {
|
|
nv := v
|
|
nv[Role] = i
|
|
c.sets[nv] = struct{}{}
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (s *vectorStoreMap) HasLanguage(i int) bool {
|
|
for v := range s.sets {
|
|
if v.Language() == i {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *vectorStoreMap) HasVersion(i int) bool {
|
|
for v := range s.sets {
|
|
if v.Version() == i {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *vectorStoreMap) HasRole(i int) bool {
|
|
for v := range s.sets {
|
|
if v.Role() == i {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *vectorStoreMap) clone() *vectorStoreMap {
|
|
c := *s
|
|
c.h = &hashOnce{}
|
|
c.sets = maps.Clone(s.sets)
|
|
return &c
|
|
}
|
|
|
|
func NewIntSetsBuilder(cfg *ConfiguredDimensions) *IntSetsBuilder {
|
|
if cfg == nil {
|
|
panic("cfg is required")
|
|
}
|
|
return &IntSetsBuilder{cfg: cfg, s: &IntSets{h: &hashOnce{}}}
|
|
}
|
|
|
|
type ConfiguredDimension interface {
|
|
predicate.IndexMatcher
|
|
IndexDefault() int
|
|
ResolveIndex(string) int
|
|
ResolveName(int) string
|
|
ForEachIndex() iter.Seq[int]
|
|
Len() int
|
|
}
|
|
|
|
// ConfiguredDimensions holds the configured dimensions for the site matrix.
|
|
type ConfiguredDimensions struct {
|
|
ConfiguredLanguages ConfiguredDimension
|
|
ConfiguredVersions ConfiguredDimension
|
|
ConfiguredRoles ConfiguredDimension
|
|
CommonSitesMatrix CommonSitestMatrix
|
|
|
|
singleVectorStoreCache *hmaps.Cache[Vector, *IntSets]
|
|
}
|
|
|
|
func (c *ConfiguredDimensions) IsSingleVector() bool {
|
|
return c.ConfiguredLanguages.Len() == 1 && c.ConfiguredRoles.Len() == 1 && c.ConfiguredVersions.Len() == 1
|
|
}
|
|
|
|
// GetOrCreateSingleVectorStore returns a VectorStore for the given vector.
|
|
func (c *ConfiguredDimensions) GetOrCreateSingleVectorStore(vec Vector) *IntSets {
|
|
store, _ := c.singleVectorStoreCache.GetOrCreate(vec, func() (*IntSets, error) {
|
|
is := &IntSets{}
|
|
is.setValuesInNilSets(vec, true, true, true)
|
|
return is, nil
|
|
})
|
|
return store
|
|
}
|
|
|
|
func (c *ConfiguredDimensions) Init() error {
|
|
c.singleVectorStoreCache = hmaps.NewCache[Vector, *IntSets]()
|
|
b := NewIntSetsBuilder(c).WithDefaultsIfNotSet().Build()
|
|
defaultVec := b.VectorSample()
|
|
c.singleVectorStoreCache.Set(defaultVec, b)
|
|
c.CommonSitesMatrix.DefaultSite = b
|
|
|
|
return nil
|
|
}
|
|
|
|
type CommonSitestMatrix struct {
|
|
DefaultSite VectorStore
|
|
}
|
|
|
|
func (c *ConfiguredDimensions) ResolveNames(v Vector) types.Strings3 {
|
|
return types.Strings3{
|
|
c.ConfiguredLanguages.ResolveName(v.Language()),
|
|
c.ConfiguredVersions.ResolveName(v.Version()),
|
|
c.ConfiguredRoles.ResolveName(v.Role()),
|
|
}
|
|
}
|
|
|
|
func (c *ConfiguredDimensions) ResolveVector(names types.Strings3) Vector {
|
|
var vec Vector
|
|
if s := names[0]; s != "" {
|
|
vec[0] = c.ConfiguredLanguages.ResolveIndex(s)
|
|
} else {
|
|
vec[0] = c.ConfiguredLanguages.IndexDefault()
|
|
}
|
|
if s := names[1]; s != "" {
|
|
vec[1] = c.ConfiguredVersions.ResolveIndex(s)
|
|
} else {
|
|
vec[1] = c.ConfiguredVersions.IndexDefault()
|
|
}
|
|
if s := names[2]; s != "" {
|
|
vec[2] = c.ConfiguredRoles.ResolveIndex(s)
|
|
} else {
|
|
vec[2] = c.ConfiguredRoles.IndexDefault()
|
|
}
|
|
return vec
|
|
}
|
|
|
|
// IntSets holds the ordered sets of integers for the dimensions,
|
|
// which is used for fast membership testing of files, resources and pages.
|
|
type IntSets struct {
|
|
languages *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
|
|
versions *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
|
|
roles *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
|
|
|
|
h *hashOnce
|
|
}
|
|
|
|
var NilStore *IntSets = nil
|
|
|
|
type hashOnce struct {
|
|
once sync.Once
|
|
hash uint64
|
|
}
|
|
|
|
func (s *IntSets) ToVectorStoreMap() *vectorStoreMap {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
result := newVectorStoreMap(36)
|
|
|
|
s.ForEachVector(func(vec Vector) bool {
|
|
result.setVector(vec)
|
|
return true
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *IntSets) IsSuperSet(other *IntSets) bool {
|
|
return s.languages.IsSuperSet(other.languages) &&
|
|
s.versions.IsSuperSet(other.versions) &&
|
|
s.roles.IsSuperSet(other.roles)
|
|
}
|
|
|
|
func (s *IntSets) DifferenceCardinality(other *IntSets) Vector {
|
|
return Vector{
|
|
int(s.languages.Values().DifferenceCardinality(other.languages.Values())),
|
|
int(s.versions.Values().DifferenceCardinality(other.versions.Values())),
|
|
int(s.roles.Values().DifferenceCardinality(other.roles.Values())),
|
|
}
|
|
}
|
|
|
|
func (s *IntSets) Intersects(other *IntSets) bool {
|
|
if s == nil || other == nil {
|
|
return false
|
|
}
|
|
return s.languages.Values().IntersectionCardinality(other.languages.Values()) > 0 &&
|
|
s.versions.Values().IntersectionCardinality(other.versions.Values()) > 0 &&
|
|
s.roles.Values().IntersectionCardinality(other.roles.Values()) > 0
|
|
}
|
|
|
|
// Complement returns a new VectorStore that contains all vectors in s that are not in any of ss.
|
|
func (s *IntSets) Complement(ss ...VectorProvider) VectorStore {
|
|
if len(ss) == 0 || (len(ss) == 1 && ss[0] == s) {
|
|
return NilStore
|
|
}
|
|
|
|
for _, v := range ss {
|
|
vv, ok := v.(*IntSets)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if vv.IsSuperSet(s) {
|
|
return NilStore
|
|
}
|
|
}
|
|
|
|
result := newVectorStoreMap(36)
|
|
|
|
s.ForEachVector(func(vec Vector) bool {
|
|
var found bool
|
|
for _, v := range ss {
|
|
if v.HasVector(vec) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
result.setVector(vec)
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *IntSets) EqualsVector(other VectorProvider) bool {
|
|
if s == nil && other == nil {
|
|
return true
|
|
}
|
|
if s == nil || other == nil {
|
|
return false
|
|
}
|
|
if s == other {
|
|
return true
|
|
}
|
|
if s.LenVectors() != other.LenVectors() {
|
|
return false
|
|
}
|
|
|
|
return other.ForEachVector(func(v Vector) bool {
|
|
return s.HasVector(v)
|
|
})
|
|
}
|
|
|
|
func (s *IntSets) VectorSample() Vector {
|
|
if s.LenVectors() == 0 {
|
|
panic("no vectors available")
|
|
}
|
|
|
|
return Vector{
|
|
s.languages.Next(0),
|
|
s.versions.Next(0),
|
|
s.roles.Next(0),
|
|
}
|
|
}
|
|
|
|
// The reason we don't use iter.Seq is https://github.com/golang/go/issues/69015
|
|
// This is 60% faster and allocation free.
|
|
// The yield function should return false to stop iteration.
|
|
func (s *IntSets) ForEachVector(yield func(v Vector) bool) bool {
|
|
if s.LenVectors() == 0 {
|
|
return true
|
|
}
|
|
|
|
b := s.languages.ForEachKey(func(lang int) bool {
|
|
return s.versions.ForEachKey(func(ver int) bool {
|
|
return s.roles.ForEachKey(func(role int) bool {
|
|
return yield(Vector{lang, ver, role})
|
|
})
|
|
})
|
|
})
|
|
|
|
return b
|
|
}
|
|
|
|
func (s *IntSets) Vectors() []Vector {
|
|
if s.LenVectors() == 0 {
|
|
return nil
|
|
}
|
|
|
|
var vectors []Vector
|
|
s.ForEachVector(func(v Vector) bool {
|
|
vectors = append(vectors, v)
|
|
return true
|
|
})
|
|
|
|
sort.Slice(vectors, func(i, j int) bool {
|
|
v1, v2 := vectors[i], vectors[j]
|
|
return v1.Compare(v2) < 0
|
|
})
|
|
|
|
return vectors
|
|
}
|
|
|
|
func (s *IntSets) KeysSorted() ([]int, []int, []int) {
|
|
if s == nil {
|
|
return nil, nil, nil
|
|
}
|
|
languages := s.languages.KeysSorted()
|
|
versions := s.versions.KeysSorted()
|
|
roles := s.roles.KeysSorted()
|
|
return languages, versions, roles
|
|
}
|
|
|
|
func (s *IntSets) HasLanguage(lang int) bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
return s.languages.Has(lang)
|
|
}
|
|
|
|
// LenVectors returns the total number of vectors represented by the IntSets.
|
|
// This is the Cartesian product of the lengths of the individual sets.
|
|
// This will be 0 if s is nil or any of the sets is empty.
|
|
func (s *IntSets) LenVectors() int {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
return s.languages.Len() * s.versions.Len() * s.roles.Len()
|
|
}
|
|
|
|
func (s *IntSets) HasRole(role int) bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
return s.roles.Has(role)
|
|
}
|
|
|
|
func (s *IntSets) String() string {
|
|
return fmt.Sprintf("Languages: %v, Versions: %v, Roles: %v", s.languages, s.versions, s.roles)
|
|
}
|
|
|
|
// HasVector checks if the given vector is contained in the sets.
|
|
func (s *IntSets) HasVector(v Vector) bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
if !s.languages.Has(v.Language()) {
|
|
return false
|
|
}
|
|
if !s.versions.Has(v.Version()) {
|
|
return false
|
|
}
|
|
if !s.roles.Has(v.Role()) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *IntSets) HasAnyVector(v VectorProvider) bool {
|
|
if s == nil || v == nil {
|
|
return false
|
|
}
|
|
if s.LenVectors() == 0 || v.LenVectors() == 0 {
|
|
return false
|
|
}
|
|
if v.LenVectors() == 1 {
|
|
// Fast path.
|
|
return s.HasVector(v.VectorSample())
|
|
}
|
|
|
|
if vs, ok := v.(*IntSets); ok {
|
|
// Fast path.
|
|
return s.Intersects(vs)
|
|
}
|
|
|
|
return !v.ForEachVector(func(vec Vector) bool {
|
|
if s.HasVector(vec) {
|
|
return false // stop iteration
|
|
}
|
|
return true // continue iteration
|
|
})
|
|
}
|
|
|
|
func (s *IntSets) HasVersion(ver int) bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
return s.versions.Has(ver)
|
|
}
|
|
|
|
func (s IntSets) shallowClone() *IntSets {
|
|
s.h = &hashOnce{}
|
|
return &s
|
|
}
|
|
|
|
// WithLanguageIndices replaces the current language set with the given language indices.
|
|
func (s *IntSets) WithLanguageIndices(indices ...int) VectorStore {
|
|
c := s.shallowClone()
|
|
c.languages = hmaps.NewOrderedIntSet(indices...)
|
|
return c.init()
|
|
}
|
|
|
|
// WithVersionIndices replaces the current version set with the given version indices.
|
|
func (s *IntSets) WithVersionIndices(indices ...int) VectorStore {
|
|
c := s.shallowClone()
|
|
c.versions = hmaps.NewOrderedIntSet(indices...)
|
|
return c.init()
|
|
}
|
|
|
|
// WithRoleIndices replaces the current role set with the given role indices.
|
|
func (s *IntSets) WithRoleIndices(indices ...int) VectorStore {
|
|
c := s.shallowClone()
|
|
c.roles = hmaps.NewOrderedIntSet(indices...)
|
|
return c.init()
|
|
}
|
|
|
|
func (s *IntSets) Hash() (uint64, error) {
|
|
s.initHash()
|
|
return s.h.hash, nil
|
|
}
|
|
|
|
func (s *IntSets) MustHash() uint64 {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
hash, err := s.Hash()
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to calculate hash for IntSets: %w", err))
|
|
}
|
|
return hash
|
|
}
|
|
|
|
// setDefaultsIfNotSet applies default values to the IntSets if they are not already set.
|
|
func (s *IntSets) setDefaultsIfNotSet(cfg *ConfiguredDimensions) {
|
|
if s.languages == nil {
|
|
s.languages = hmaps.NewOrderedIntSet()
|
|
s.languages.Set(cfg.ConfiguredLanguages.IndexDefault())
|
|
}
|
|
if s.versions == nil {
|
|
s.versions = hmaps.NewOrderedIntSet()
|
|
s.versions.Set(cfg.ConfiguredVersions.IndexDefault())
|
|
}
|
|
if s.roles == nil {
|
|
s.roles = hmaps.NewOrderedIntSet()
|
|
s.roles.Set(cfg.ConfiguredRoles.IndexDefault())
|
|
}
|
|
}
|
|
|
|
func (s *IntSets) setDefaultsAndAllLAnguagesIfNotSet(cfg *ConfiguredDimensions) {
|
|
if s.languages == nil {
|
|
s.languages = hmaps.NewOrderedIntSet()
|
|
for i := range cfg.ConfiguredLanguages.ForEachIndex() {
|
|
s.languages.Set(i)
|
|
}
|
|
}
|
|
s.setDefaultsIfNotSet(cfg)
|
|
}
|
|
|
|
func (s *IntSets) setAllIfNotSet(cfg *ConfiguredDimensions) {
|
|
if s.languages == nil {
|
|
s.languages = hmaps.NewOrderedIntSet()
|
|
for i := range cfg.ConfiguredLanguages.ForEachIndex() {
|
|
s.languages.Set(i)
|
|
}
|
|
}
|
|
if s.versions == nil {
|
|
s.versions = hmaps.NewOrderedIntSet()
|
|
for i := range cfg.ConfiguredVersions.ForEachIndex() {
|
|
s.versions.Set(i)
|
|
}
|
|
}
|
|
if s.roles == nil {
|
|
s.roles = hmaps.NewOrderedIntSet()
|
|
for i := range cfg.ConfiguredRoles.ForEachIndex() {
|
|
s.roles.Set(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *IntSets) initHash() {
|
|
s.h.once.Do(func() {
|
|
var err error
|
|
s.h.hash, err = hashing.Hash(s.languages.Words(), s.versions.Words(), s.roles.Words())
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to calculate hash for IntSets: %w", err))
|
|
}
|
|
})
|
|
}
|
|
|
|
func (s *IntSets) init() *IntSets {
|
|
return s
|
|
}
|
|
|
|
func (s *IntSets) setDimensionsFromOtherIfNotSet(other VectorIterator) {
|
|
if other == nil {
|
|
return
|
|
}
|
|
setLang := s.languages == nil
|
|
setVer := s.versions == nil
|
|
setRole := s.roles == nil
|
|
|
|
if !(setLang || setVer || setRole) {
|
|
return
|
|
}
|
|
|
|
other.ForEachVector(func(v Vector) bool {
|
|
if !s.HasVector(v) {
|
|
s.setValuesInNilSets(v, setLang, setVer, setRole)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (s *IntSets) setValuesInNilSets(vec Vector, setLang, setVer, setRole bool) {
|
|
if setLang {
|
|
if s.languages == nil {
|
|
s.languages = hmaps.NewOrderedIntSet()
|
|
}
|
|
s.languages.Set(vec.Language())
|
|
}
|
|
if setVer {
|
|
if s.versions == nil {
|
|
s.versions = hmaps.NewOrderedIntSet()
|
|
}
|
|
s.versions.Set(vec.Version())
|
|
}
|
|
|
|
if setRole {
|
|
if s.roles == nil {
|
|
s.roles = hmaps.NewOrderedIntSet()
|
|
}
|
|
s.roles.Set(vec.Role())
|
|
}
|
|
}
|
|
|
|
type IntSetsBuilder struct {
|
|
cfg *ConfiguredDimensions
|
|
s *IntSets
|
|
|
|
// Set when a Glob (e.g. "en") filter is provided but no matches are found.
|
|
GlobFilterMisses Bools
|
|
}
|
|
|
|
func (b *IntSetsBuilder) Build() *IntSets {
|
|
b.s.init()
|
|
|
|
if b.s.LenVectors() == 1 {
|
|
// Cache it or use the existing cached version, which will allow b.s to be GCed.
|
|
bb, _ := b.cfg.singleVectorStoreCache.GetOrCreate(b.s.VectorSample(), func() (*IntSets, error) {
|
|
return b.s, nil
|
|
})
|
|
return bb
|
|
}
|
|
return b.s
|
|
}
|
|
|
|
func (b *IntSetsBuilder) WithConfig(cfg IntSetsConfig) *IntSetsBuilder {
|
|
applyFilter := func(what string, values []string, matcher ConfiguredDimension) *hmaps.OrderedIntSet {
|
|
var result *hmaps.OrderedIntSet
|
|
if len(values) == 0 {
|
|
|
|
if cfg.ApplyDefaults > 0 {
|
|
result = hmaps.NewOrderedIntSet()
|
|
}
|
|
switch cfg.ApplyDefaults {
|
|
case IntSetsConfigApplyDefaultsIfNotSet:
|
|
result.Set(matcher.IndexDefault())
|
|
case IntSetsConfigApplyDefaultsAndAllLanguagesIfNotSet:
|
|
if what == "languages" {
|
|
for i := range matcher.ForEachIndex() {
|
|
result.Set(i)
|
|
}
|
|
} else {
|
|
result.Set(matcher.IndexDefault())
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
filter, err := predicate.NewIndexStringPredicateFromGlobsAndRanges(values, matcher.ResolveIndex, hglob.GetGlobDot)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to create filter for %s: %w", what, err))
|
|
}
|
|
iter, err := matcher.IndexMatch(filter)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to match %s %q: %w", what, values, err))
|
|
}
|
|
for i := range iter {
|
|
if result == nil {
|
|
result = hmaps.NewOrderedIntSet()
|
|
}
|
|
result.Set(i)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
l := applyFilter("languages", cfg.Globs.Languages, b.cfg.ConfiguredLanguages)
|
|
v := applyFilter("versions", cfg.Globs.Versions, b.cfg.ConfiguredVersions)
|
|
r := applyFilter("roles", cfg.Globs.Roles, b.cfg.ConfiguredRoles)
|
|
|
|
b.GlobFilterMisses = Bools{
|
|
len(cfg.Globs.Languages) > 0 && l == nil,
|
|
len(cfg.Globs.Versions) > 0 && v == nil,
|
|
len(cfg.Globs.Roles) > 0 && r == nil,
|
|
}
|
|
|
|
b.s.languages = l
|
|
b.s.versions = v
|
|
b.s.roles = r
|
|
|
|
return b
|
|
}
|
|
|
|
func (s *IntSetsBuilder) WithLanguageIndices(idxs ...int) *IntSetsBuilder {
|
|
if len(idxs) == 0 {
|
|
return s
|
|
}
|
|
if s.s.languages == nil {
|
|
s.s.languages = hmaps.NewOrderedIntSet()
|
|
}
|
|
for _, i := range idxs {
|
|
s.s.languages.Set(i)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *IntSetsBuilder) WithVersionIndices(idxs ...int) *IntSetsBuilder {
|
|
if len(idxs) == 0 {
|
|
return s
|
|
}
|
|
if s.s.versions == nil {
|
|
s.s.versions = hmaps.NewOrderedIntSet()
|
|
}
|
|
for _, i := range idxs {
|
|
s.s.versions.Set(i)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *IntSetsBuilder) WithRoleIndices(idxs ...int) *IntSetsBuilder {
|
|
if len(idxs) == 0 {
|
|
return s
|
|
}
|
|
if s.s.roles == nil {
|
|
s.s.roles = hmaps.NewOrderedIntSet()
|
|
}
|
|
for _, i := range idxs {
|
|
s.s.roles.Set(i)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *IntSetsBuilder) WithDefaultsAndAllLanguagesIfNotSet() *IntSetsBuilder {
|
|
s.s.setDefaultsAndAllLAnguagesIfNotSet(s.cfg)
|
|
return s
|
|
}
|
|
|
|
func (s *IntSetsBuilder) WithAllIfNotSet() *IntSetsBuilder {
|
|
s.s.setAllIfNotSet(s.cfg)
|
|
return s
|
|
}
|
|
|
|
func (s *IntSetsBuilder) WithDefaultsIfNotSet() *IntSetsBuilder {
|
|
s.s.setDefaultsIfNotSet(s.cfg)
|
|
return s
|
|
}
|
|
|
|
func (s *IntSetsBuilder) WithDimensionsFromOtherIfNotSet(other VectorIterator) *IntSetsBuilder {
|
|
s.s.setDimensionsFromOtherIfNotSet(other)
|
|
return s
|
|
}
|
|
|
|
func (b *IntSetsBuilder) WithSets(languages, versions, roles *hmaps.OrderedIntSet) *IntSetsBuilder {
|
|
b.s.languages = languages
|
|
b.s.versions = versions
|
|
b.s.roles = roles
|
|
return b
|
|
}
|
|
|
|
type IntSetsConfigApplyDefaults int
|
|
|
|
const (
|
|
IntSetsConfigApplyDefaultsNone IntSetsConfigApplyDefaults = iota
|
|
IntSetsConfigApplyDefaultsIfNotSet
|
|
IntSetsConfigApplyDefaultsAndAllLanguagesIfNotSet
|
|
)
|
|
|
|
type IntSetsConfig struct {
|
|
ApplyDefaults IntSetsConfigApplyDefaults
|
|
Globs StringSlices
|
|
}
|
|
|
|
// Sites holds configuration about which sites a file/content/page/resource belongs to.
|
|
type Sites struct {
|
|
// Matrix defines what sites to build this content for.
|
|
Matrix StringSlices `mapstructure:"matrix" json:"matrix"`
|
|
// Complements defines what sites to complement with this content.
|
|
Complements StringSlices `mapstructure:"complements" json:"complements"`
|
|
}
|
|
|
|
func (s *Sites) Equal(other Sites) bool {
|
|
return s.Matrix.Equal(other.Matrix) && s.Complements.Equal(other.Complements)
|
|
}
|
|
|
|
func (s *Sites) SetFromParamsIfNotSet(params hmaps.Params) {
|
|
const (
|
|
matrixKey = "matrix"
|
|
complementsKey = "complements"
|
|
)
|
|
|
|
if m, ok := params[matrixKey]; ok {
|
|
s.Matrix.SetFromParamsIfNotSet(m.(hmaps.Params))
|
|
}
|
|
if f, ok := params[complementsKey]; ok {
|
|
s.Complements.SetFromParamsIfNotSet(f.(hmaps.Params))
|
|
}
|
|
}
|
|
|
|
// IsZero returns true if all slices are empty.
|
|
func (s Sites) IsZero() bool {
|
|
return s.Matrix.IsZero() && s.Complements.IsZero()
|
|
}
|
|
|
|
// StringSlices holds slices of Glob patterns for languages, versions, and roles.
|
|
type StringSlices struct {
|
|
Languages []string `mapstructure:"languages" json:"languages"`
|
|
Versions []string `mapstructure:"versions" json:"versions"`
|
|
Roles []string `mapstructure:"roles" json:"roles"`
|
|
}
|
|
|
|
func (d StringSlices) Equal(other StringSlices) bool {
|
|
return slices.Equal(d.Languages, other.Languages) &&
|
|
slices.Equal(d.Versions, other.Versions) &&
|
|
slices.Equal(d.Roles, other.Roles)
|
|
}
|
|
|
|
func (d *StringSlices) SetFromParamsIfNotSet(params hmaps.Params) {
|
|
const (
|
|
languagesKey = "languages"
|
|
versionsKey = "versions"
|
|
rolesKey = "roles"
|
|
)
|
|
|
|
if len(d.Languages) == 0 {
|
|
if v, ok := params[languagesKey]; ok {
|
|
d.Languages = types.ToStringSlicePreserveString(v)
|
|
}
|
|
}
|
|
|
|
if len(d.Versions) == 0 {
|
|
if v, ok := params[versionsKey]; ok {
|
|
d.Versions = types.ToStringSlicePreserveString(v)
|
|
}
|
|
}
|
|
|
|
if len(d.Roles) == 0 {
|
|
if v, ok := params[rolesKey]; ok {
|
|
d.Roles = types.ToStringSlicePreserveString(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d StringSlices) IsZero() bool {
|
|
return len(d.Languages) == 0 && len(d.Versions) == 0 && len(d.Roles) == 0
|
|
}
|
|
|
|
// Used in tests.
|
|
type testDimension struct {
|
|
names []string
|
|
}
|
|
|
|
func (m testDimension) Len() int {
|
|
return len(m.names)
|
|
}
|
|
|
|
func (m testDimension) IndexDefault() int {
|
|
return 0
|
|
}
|
|
|
|
func (m *testDimension) ResolveIndex(name string) int {
|
|
for i, n := range m.names {
|
|
if n == name {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (m *testDimension) ResolveName(i int) string {
|
|
if i < 0 || i >= len(m.names) {
|
|
return ""
|
|
}
|
|
return m.names[i]
|
|
}
|
|
|
|
func (m *testDimension) ForEachIndex() iter.Seq[int] {
|
|
return func(yield func(i int) bool) {
|
|
for i := range m.names {
|
|
if !yield(i) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *testDimension) IndexMatch(match predicate.P[predicate.IndexString]) (iter.Seq[int], error) {
|
|
return func(yield func(i int) bool) {
|
|
for i, n := range m.names {
|
|
if match(predicate.IndexString{Index: i, String: n}) {
|
|
if !yield(i) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}, nil
|
|
}
|
|
|
|
// NewTestingDimensions creates a new ConfiguredDimensions for testing.
|
|
func NewTestingDimensions(languages, versions, roles []string) *ConfiguredDimensions {
|
|
c := &ConfiguredDimensions{
|
|
ConfiguredLanguages: &testDimension{names: languages},
|
|
ConfiguredVersions: &testDimension{names: versions},
|
|
ConfiguredRoles: &testDimension{names: roles},
|
|
}
|
|
if err := c.Init(); err != nil {
|
|
panic(err)
|
|
}
|
|
return c
|
|
}
|