// Below gets created on demand.
initOnce sync.Once
- sitesMatrixCache *maps.Cache[string, sitesmatrix.VectorStore] // Maps language code to sites matrix vector store.
+ sitesMatrixCache *maps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
}
func (pp *PathParser) init() {
ConfiguredRoles: c.Base.Roles.Config,
}
+ if err := c.ConfiguredDimensions.Init(); err != nil {
+ return err
+ }
+
intSetsCfg := sitesmatrix.IntSetsConfig{
ApplyDefaults: sitesmatrix.IntSetsConfigApplyDefaultsIfNotSet,
}
func() error {
if i := len(missingVectorsForHomeOrRootSection); i > 0 {
// Pick one, the rest will be created later.
- vec := missingVectorsForHomeOrRootSection.Sample()
+ vec := missingVectorsForHomeOrRootSection.VectorSample()
kind := kinds.KindSection
if s == "" {
}
func (n contentNodesMap) siteVectors() sitesmatrix.VectorIterator {
- return sitesmatrix.VectorIteratorFunc(func(yield func(v sitesmatrix.Vector) bool) bool {
- for k := range n {
- if !yield(k) {
- return false
- }
+ return n
+}
+
+func (n contentNodesMap) ForEachVector(yield func(v sitesmatrix.Vector) bool) bool {
+ for v := range n {
+ if !yield(v) {
+ return false
}
- return true
- })
+ }
+ return true
+}
+
+func (n contentNodesMap) LenVectors() int {
+ return len(n)
+}
+
+func (n contentNodesMap) VectorSample() sitesmatrix.Vector {
+ for v := range n {
+ return v
+ }
+ panic("no vectors")
}
Sorted []RoleInternal
}
+func (r RolesInternal) Len() int {
+ return len(r.Sorted)
+}
+
func (r RolesInternal) IndexDefault() int {
for i, role := range r.Sorted {
if role.Default {
}
func (v1 Vector) HasAnyVector(vp VectorProvider) bool {
- if vp.LenVectors() == 0 {
+ n := vp.LenVectors()
+ if n == 0 {
return false
}
+ if n == 1 {
+ return v1 == vp.VectorSample()
+ }
return !vp.ForEachVector(func(v2 Vector) bool {
if v1 == v2 {
return true
}
+func (vs Vectors) LenVectors() int {
+ return len(vs)
+}
+
func (vs Vectors) ToVectorStore() VectorStore {
return newVectorStoreMapFromVectors(vs)
}
-// Sample returns one of the vectors in the set.
-func (vs Vectors) Sample() Vector {
+// VectorSample returns one of the vectors in the set.
+func (vs Vectors) VectorSample() Vector {
for v := range vs {
return v
}
// ForEachVector iterates over all vectors in the provider.
// It returns false if the iteration was stopped early.
ForEachVector(func(v Vector) bool) bool
- }
-)
-type VectorIteratorFunc func(func(v Vector) bool) bool
+ // LenVectors returns the number of vectors in the provider.
+ LenVectors() int
-func (f VectorIteratorFunc) ForEachVector(yield func(v Vector) bool) bool {
- return f(yield)
-}
+ // VectorSample returns one of the vectors in the provider, usually the first or the only one.
+ // This will panic if the provider is empty.
+ VectorSample() Vector
+ }
+)
// Bools holds boolean values for each dimension in the Hugo build matrix.
type Bools [3]bool
// HasAnyVector returns true if any of the vectors in the provider matches any of the vectors in v.
HasAnyVector(v VectorProvider) bool
- // LenVectors returns the number of vectors in the provider.
- LenVectors() int
-
- // VectorSample returns one of the vectors in the provider, usually the first or the only one.
- // This will panic if the provider is empty.
- VectorSample() Vector
-
// Equals returns true if this provider is equal to the other provider.
EqualsVector(other VectorProvider) bool
}
ToVectorStore() VectorStore
}
+func VectorIteratorToStore(vi VectorIterator) VectorStore {
+ switch v := vi.(type) {
+ case VectorStore:
+ return v
+ case ToVectorStoreProvider:
+ return v.ToVectorStore()
+ }
+
+ vectors := make(Vectors)
+ vi.ForEachVector(func(v Vector) bool {
+ vectors[v] = struct{}{}
+ return true
+ })
+ return vectors.ToVectorStore()
+}
+
type weightedVectorStore struct {
VectorStore
weight int
s.sets[vec] = struct{}{}
}
-func (s *vectorStoreMap) Ordinal() int {
- return 0
-}
-
func (s *vectorStoreMap) KeysSorted() ([]int, []int, []int) {
var k0, k1, k2 []int
for v := range s.sets {
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 *maps.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 = maps.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 {
h *hashOnce
}
+var NilStore *IntSets = nil
+
type hashOnce struct {
once sync.Once
hash uint64
// 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 nil
+ return NilStore
}
for _, v := range ss {
continue
}
if vv.IsSuperSet(s) {
- var s *IntSets
- return s
+ return NilStore
}
}
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
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.
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
}
names []string
}
+func (m testDimension) Len() int {
+ return len(m.names)
+}
+
func (m testDimension) IndexDefault() int {
return 0
}
// NewTestingDimensions creates a new ConfiguredDimensions for testing.
func NewTestingDimensions(languages, versions, roles []string) *ConfiguredDimensions {
- return &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
}
package sitesmatrix_test
import (
- "fmt"
"testing"
- "github.com/bits-and-blooms/bitset"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
)
-var testDims = sitesmatrix.NewTestingDimensions([]string{"en", "no"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
+func newTestDims() *sitesmatrix.ConfiguredDimensions {
+ return sitesmatrix.NewTestingDimensions([]string{"en", "no"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
+}
func TestIntSets(t *testing.T) {
c := qt.New(t)
+ testDims := newTestDims()
sets := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
c.Assert(allCount, qt.Equals, 18)
}
-func TestBitSetExperiments(t *testing.T) {
- c := qt.New(t)
-
- a, b, d := bitset.New(10), bitset.New(10), bitset.New(10)
-
- ints := func(v *bitset.BitSet) []uint {
- var ints []uint
- for i := range v.EachSet() {
- ints = append(ints, i)
- }
- return ints
- }
-
- a.Set(3).Set(4)
- b.Set(4).Set(5).Set(6).Set(7)
- d.Set(3).Set(4)
-
- c.Assert(b.Test(5), qt.Equals, true)
-
- fmt.Println("a:", ints(a), "==>", ints(b), "=> Intersection:", ints(a.Intersection(b)), "=> Symdiff:", ints(a.SymmetricDifference(b)))
- fmt.Println("===>", ints(a.Difference(b)))
- fmt.Println("===>", ints(b.Difference(a)))
- fmt.Println("===> d", d.Difference(a).Count())
-}
-
func TestIntSetsComplement(t *testing.T) {
c := qt.New(t)
runOne := func(c *qt.C, test test) {
c.Helper()
+ testDims := newTestDims()
+
self := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(test.left.v0...),
maps.NewOrderedIntSet(test.left.v1...),
func TestIntSetsComplementOfComplement(t *testing.T) {
c := qt.New(t)
+ testDims := newTestDims()
+
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1),
}
func BenchmarkIntSetsComplement(b *testing.B) {
+ testDims := newTestDims()
+
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
}
func BenchmarkSets(b *testing.B) {
+ testDims := newTestDims()
+
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
func TestVectorStoreMap(t *testing.T) {
c := qt.New(t)
+ testDims := newTestDims()
c.Run("Complement", func(c *qt.C) {
v1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
})
})
}
+
+func BenchmarkHasAnyVectorSingle(b *testing.B) {
+ testDims := newTestDims()
+ set := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
+ maps.NewOrderedIntSet(0),
+ maps.NewOrderedIntSet(0),
+ maps.NewOrderedIntSet(0),
+ ).Build()
+
+ v := sitesmatrix.Vector{0, 0, 0}
+
+ for b.Loop() {
+ _ = set.HasAnyVector(v)
+ }
+}
Sorted []VersionInternal
}
+func (r VersionsInternal) Len() int {
+ return len(r.Sorted)
+}
+
func (r VersionsInternal) IndexDefault() int {
for i, version := range r.Sorted {
if version.Default {
panic(fmt.Sprintf("no language found for name %q", name))
}
+func (ls LanguagesInternal) Len() int {
+ return len(ls.Sorted)
+}
+
// IndexMatch returns an iterator for the roles that match the filter.
func (ls LanguagesInternal) IndexMatch(match predicate.P[string]) (iter.Seq[int], error) {
return func(yield func(i int) bool) {
conf config.AllProvider,
fim *hugofs.FileMeta,
sitesConfig sitesmatrix.Sites,
-) *sitesmatrix.IntSets {
+) sitesmatrix.VectorStore {
+ if sitesConfig.Complements.IsZero() {
+ if fim != nil && fim.SitesComplements != nil {
+ return fim.SitesComplements
+ }
+ return sitesmatrix.NilStore
+ }
intsetsCfg := sitesmatrix.IntSetsConfig{
Globs: sitesConfig.Complements,
}
return sitesComplements.Build()
}
-func buildSitesMatrixFromVectorIterator(
- sitesMatrix sitesmatrix.VectorIterator,
-) sitesmatrix.VectorStore {
- switch v := sitesMatrix.(type) {
- case sitesmatrix.VectorStore:
- return v
- case sitesmatrix.ToVectorStoreProvider:
- return v.ToVectorStore()
- default:
- if v == nil {
- return nil
- }
- panic(fmt.Sprintf("unsupported type %T", v))
- }
-}
-
func buildSitesMatrixFromSitesConfig(
conf config.AllProvider,
sitesMatrixBase sitesmatrix.VectorIterator,
sitesConfig sitesmatrix.Sites,
-) *sitesmatrix.IntSets {
+) sitesmatrix.VectorStore {
+ if sitesConfig.Matrix.IsZero() && sitesMatrixBase != nil {
+ if sitesMatrixBase.LenVectors() == 1 {
+ return conf.ConfiguredDimensions().GetOrCreateSingleVectorStore(sitesMatrixBase.VectorSample())
+ }
+ return sitesmatrix.VectorIteratorToStore(sitesMatrixBase)
+ }
+
+ if conf.ConfiguredDimensions().IsSingleVector() {
+ return conf.ConfiguredDimensions().CommonSitesMatrix.DefaultSite
+ }
+
intsetsCfg := sitesmatrix.IntSetsConfig{
Globs: sitesConfig.Matrix,
}
}
if sitesMatrixBaseOnly {
- p.SitesMatrix = buildSitesMatrixFromVectorIterator(sitesMatrixBase)
+ p.SitesMatrix = sitesmatrix.VectorIteratorToStore(sitesMatrixBase)
} else {
p.SitesMatrix = buildSitesMatrixFromSitesConfig(
conf,