Skip to content

Commit 6f88a60

Browse files
committed
Delegees
1 parent 7c681b5 commit 6f88a60

13 files changed

+252
-107
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/bep/overlayfs v0.10.0
2323
github.com/bep/simplecobra v0.6.0
2424
github.com/bep/tmc v0.5.1
25+
github.com/bits-and-blooms/bitset v1.22.0
2526
github.com/cespare/xxhash/v2 v2.3.0
2627
github.com/clbanning/mxj/v2 v2.7.0
2728
github.com/disintegration/gift v1.2.1
@@ -118,7 +119,6 @@ require (
118119
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
119120
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
120121
github.com/aws/smithy-go v1.22.2 // indirect
121-
github.com/bits-and-blooms/bitset v1.22.0 // indirect
122122
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
123123
github.com/dlclark/regexp2 v1.11.5 // indirect
124124
github.com/felixge/httpsnoop v1.0.4 // indirect

hugolib/content_map.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (r resourceSource) clone() *resourceSource {
7171
return &r
7272
}
7373

74-
func (r *resourceSource) Dim() doctree.Dimensions {
74+
func (r *resourceSource) Dims() doctree.Dimensions {
7575
return r.dims
7676
}
7777

@@ -97,6 +97,10 @@ func (r *resourceSource) GetIdentity() identity.Identity {
9797
return r.path
9898
}
9999

100+
func (p *resourceSource) matchDirectOrInDelegees(doctree.Dimensions) (contentNodeI, doctree.Dimensions) {
101+
panic("not implemented")
102+
}
103+
100104
func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) bool {
101105
return f(r.GetIdentity())
102106
}
@@ -137,6 +141,10 @@ func (n resourceSources) resetBuildState() {
137141
}
138142
}
139143

144+
func (n resourceSources) matchDirectOrInDelegees(doctree.Dimensions) (contentNodeI, doctree.Dimensions) {
145+
panic("not implemented")
146+
}
147+
140148
func (n resourceSources) GetIdentity() identity.Identity {
141149
for _, r := range n {
142150
if r != nil {

hugolib/content_map_page.go

+58-21
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"context"
1818
"fmt"
1919
"io"
20+
"iter"
2021
"path"
2122
"sort"
2223
"strconv"
@@ -367,8 +368,9 @@ func (m *pageMap) getPagesInSection(q pageMapQueryPagesInSection) page.Pages {
367368
}
368369

369370
w := &doctree.NodeShiftTreeWalker[contentNodeI]{
370-
Tree: m.treePages,
371-
Prefix: prefix,
371+
Tree: m.treePages,
372+
Prefix: prefix,
373+
DelegeeFallback: true,
372374
}
373375

374376
w.Handle = func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
@@ -649,6 +651,7 @@ type contentNodeI interface {
649651
identity.ForEeachIdentityProvider
650652
Path() string
651653
isContentNodeBranch() bool
654+
matchDirectOrInDelegees(doctree.Dimensions) (contentNodeI, doctree.Dimensions)
652655
buildStateReseter
653656
resource.StaleMarker
654657
}
@@ -672,6 +675,10 @@ func (n contentNodeIs) isContentNodeBranch() bool {
672675
return n.one().isContentNodeBranch()
673676
}
674677

678+
func (p contentNodeIs) matchDirectOrInDelegees(doctree.Dimensions) (contentNodeI, doctree.Dimensions) {
679+
panic("not implemented")
680+
}
681+
675682
func (n contentNodeIs) GetIdentity() identity.Identity {
676683
return n.one().GetIdentity()
677684
}
@@ -705,13 +712,13 @@ type contentNodeShifter struct {
705712
numLanguages int
706713
}
707714

708-
func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimensions) (contentNodeI, bool, bool) {
715+
func (s *contentNodeShifter) Delete(n contentNodeI, dims doctree.Dimensions) (contentNodeI, bool, bool) {
709716
switch v := n.(type) {
710717
case contentNodeIs:
711-
deleted := v[dimension]
718+
deleted := v[dims]
712719
resource.MarkStale(deleted)
713720
wasDeleted := deleted != nil
714-
v[dimension] = nil
721+
v[dims] = nil
715722
isEmpty := true
716723
for _, vv := range v {
717724
if vv != nil {
@@ -721,10 +728,10 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimensions
721728
}
722729
return deleted, wasDeleted, isEmpty
723730
case resourceSources:
724-
deleted := v[dimension]
731+
deleted := v[dims]
725732
resource.MarkStale(deleted)
726733
wasDeleted := deleted != nil
727-
v[dimension] = nil
734+
v[dims] = nil
728735
isEmpty := true
729736
for _, vv := range v {
730737
if vv != nil {
@@ -734,13 +741,13 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimensions
734741
}
735742
return deleted, wasDeleted, isEmpty
736743
case *resourceSource:
737-
if dimension != v.Dim() {
744+
if dims != v.Dims() {
738745
return nil, false, false
739746
}
740747
resource.MarkStale(v)
741748
return v, true, true
742749
case *pageState:
743-
if dimension != v.s.dims {
750+
if dims != v.s.dims {
744751
return nil, false, false
745752
}
746753
resource.MarkStale(v)
@@ -750,8 +757,25 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimensions
750757
}
751758
}
752759

753-
func (s *contentNodeShifter) Shift(n contentNodeI, dims doctree.Dimensions, exact bool) (contentNodeI, bool, doctree.DimensionFlag) {
754-
// How accurate is the match.
760+
func (s *contentNodeShifter) findDelegee(q doctree.Dimensions, candidates iter.Seq[contentNodeI]) contentNodeI {
761+
var (
762+
best contentNodeI = nil
763+
bestDims doctree.Dimensions
764+
)
765+
for n := range candidates {
766+
if nn, dims := n.matchDirectOrInDelegees(q); nn != nil {
767+
if best == nil || dims.Compare(bestDims) < 0 {
768+
best = nn
769+
bestDims = dims
770+
}
771+
}
772+
}
773+
return best
774+
}
775+
776+
func (s *contentNodeShifter) Shift(n contentNodeI, dims doctree.Dimensions, exact, delegeeFallback bool) (contentNodeI, bool, doctree.DimensionFlag) {
777+
// dims: language, version and role
778+
// How accurate is the match. TODO1 revise this.
755779
accuracy := doctree.DimensionLanguage
756780
switch v := n.(type) {
757781
case contentNodeIs:
@@ -762,6 +786,19 @@ func (s *contentNodeShifter) Shift(n contentNodeI, dims doctree.Dimensions, exac
762786
if vv != nil {
763787
return vv, true, accuracy
764788
}
789+
if !delegeeFallback {
790+
return nil, false, 0
791+
}
792+
iter := func(yield func(n contentNodeI) bool) {
793+
for _, nn := range v {
794+
if !yield(nn) {
795+
return
796+
}
797+
}
798+
}
799+
if vv = s.findDelegee(dims, iter); vv != nil {
800+
return vv, true, accuracy
801+
}
765802
return nil, false, 0
766803
case resourceSources:
767804
vv := v[dims]
@@ -781,7 +818,7 @@ func (s *contentNodeShifter) Shift(n contentNodeI, dims doctree.Dimensions, exac
781818
}
782819
}
783820
case *resourceSource:
784-
if v.Dim() == dims {
821+
if v.Dims() == dims {
785822
return v, true, doctree.DimensionLanguage // TODO1
786823
}
787824
if !v.isPage() && !exact {
@@ -843,11 +880,11 @@ func (s *contentNodeShifter) InsertInto(old, new contentNodeI, dimension doctree
843880
if !ok {
844881
panic(fmt.Sprintf("unknown type %T", new))
845882
}
846-
if vv.Dim() == newp.Dim() && newp.Dim() == dimension {
883+
if vv.Dims() == newp.Dims() && newp.Dims() == dimension {
847884
return new, vv, true
848885
}
849886
rs := make(resourceSources, s.numLanguages)
850-
rs[vv.Dim()] = vv
887+
rs[vv.Dims()] = vv
851888
rs[dimension] = newp
852889
return rs, vv, false
853890

@@ -889,26 +926,26 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) (contentNodeI, conten
889926
if !ok {
890927
panic(fmt.Sprintf("unknown type %T", new))
891928
}
892-
if vv.Dim() == newp.Dim() {
929+
if vv.Dims() == newp.Dims() {
893930
if vv != newp {
894931
resource.MarkStale(vv)
895932
}
896933
return new, vv, true
897934
}
898935
rs := make(resourceSources, s.numLanguages)
899-
rs[newp.Dim()] = newp
900-
rs[vv.Dim()] = vv
936+
rs[newp.Dims()] = newp
937+
rs[vv.Dims()] = vv
901938
return rs, vv, false
902939
case resourceSources:
903940
newp, ok := new.(*resourceSource)
904941
if !ok {
905942
panic(fmt.Sprintf("unknown type %T", new))
906943
}
907-
oldp := vv[newp.Dim()]
944+
oldp := vv[newp.Dims()]
908945
if oldp != newp {
909946
resource.MarkStale(oldp)
910947
}
911-
vv[newp.Dim()] = newp
948+
vv[newp.Dims()] = newp
912949
return vv, oldp, oldp != nil
913950
default:
914951
panic(fmt.Sprintf("unknown type %T", old))
@@ -1050,7 +1087,7 @@ func (m *pageMap) debugPrint(prefix string, maxLevel int, w io.Writer) {
10501087

10511088
resourceWalker.Handle = func(ss string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
10521089
if isBranch {
1053-
ownerKey, _ := pageWalker.Tree.LongestPrefix(ss, true, nil)
1090+
ownerKey, _ := pageWalker.Tree.LongestPrefix(ss, true, false, nil)
10541091
if ownerKey != keyPage {
10551092
// Stop walking downwards, someone else owns this resource.
10561093
pageWalker.SkipPrefix(ownerKey + "/")
@@ -1500,7 +1537,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
15001537

15011538
rw.Handle = func(resourceKey string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
15021539
if isBranch {
1503-
ownerKey, _ := pw.Tree.LongestPrefix(resourceKey, true, nil)
1540+
ownerKey, _ := pw.Tree.LongestPrefix(resourceKey, true, false, nil)
15041541
if ownerKey != keyPage {
15051542
// Stop walking downwards, someone else owns this resource.
15061543
rw.SkipPrefix(ownerKey + "/")

hugolib/doctree/dimensions.go

+24
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,30 @@ const (
2323
// Dimensions is a row in the Hugo build matrix which currently has three values: language, version and role, in that order.
2424
type Dimensions [3]int
2525

26+
// Compare returns -1 if this dimension is less than the given dimension, 0 if they are equal, and 1 if this dimension is greater than the given dimension.
27+
// This adds a impicit weighting to the dimensions, where the first dimension is the most important,
28+
// but this is just used for sorting to get stable output.
29+
func (d Dimensions) Compare(e Dimensions) int {
30+
// note that a and b will never be equal.
31+
minusOneOrOne := func(a, b int) int {
32+
if a < b {
33+
return -1
34+
}
35+
return 1
36+
}
37+
if d[0] != e[0] {
38+
return minusOneOrOne(d[0], e[0])
39+
}
40+
if d[1] != e[1] {
41+
return minusOneOrOne(d[1], e[1])
42+
}
43+
if d[2] != e[2] {
44+
return minusOneOrOne(d[2], e[2])
45+
}
46+
// They are equal.
47+
return 0
48+
}
49+
2650
// Language returns the language dimension.
2751
func (d Dimensions) Language() int {
2852
return d[DimensionLanguage.Index()]

hugolib/doctree/dimensions_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,19 @@ func TestDimensionsIndex(t *testing.T) {
4242
c.Assert(DimensionVersion.Index(), qt.Equals, 1)
4343
c.Assert(DimensionRole.Index(), qt.Equals, 2)
4444
}
45+
46+
func TestDimensionsCompare(t *testing.T) {
47+
c := qt.New(t)
48+
49+
c.Assert(Dimensions{1, 2, 3}.Compare(Dimensions{1, 2, 8}), qt.Equals, -1)
50+
c.Assert(Dimensions{1, 2, 3}.Compare(Dimensions{1, 2, 3}), qt.Equals, 0)
51+
c.Assert(Dimensions{1, 2, 3}.Compare(Dimensions{1, 2, 0}), qt.Equals, 1)
52+
c.Assert(Dimensions{1, 2, 3}.Compare(Dimensions{1, 0, 3}), qt.Equals, 1)
53+
c.Assert(Dimensions{1, 2, 3}.Compare(Dimensions{0, 3, 2}), qt.Equals, 1)
54+
c.Assert(Dimensions{1, 2, 3}.Compare(Dimensions{0, 0, 0}), qt.Equals, 1)
55+
c.Assert(Dimensions{0, 0, 0}.Compare(Dimensions{1, 2, 3}), qt.Equals, -1)
56+
c.Assert(Dimensions{0, 0, 0}.Compare(Dimensions{0, 0, 0}), qt.Equals, 0)
57+
c.Assert(Dimensions{0, 0, 0}.Compare(Dimensions{1, 0, 0}), qt.Equals, -1)
58+
c.Assert(Dimensions{0, 0, 0}.Compare(Dimensions{0, 1, 0}), qt.Equals, -1)
59+
c.Assert(Dimensions{0, 0, 0}.Compare(Dimensions{0, 0, 1}), qt.Equals, -1)
60+
}

hugolib/doctree/nodeshiftree_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestTree(t *testing.T) {
5252
zeroZero.InsertIntoValuesDimension("/a/b", ab)
5353

5454
c.Assert(zeroZero.Get("/a"), eq, &testValue{ID: "/a", Lang: 0})
55-
s, v := zeroZero.LongestPrefix("/a/b/c", true, nil)
55+
s, v := zeroZero.LongestPrefix("/a/b/c", true, false, nil)
5656
c.Assert(v, eq, ab)
5757
c.Assert(s, eq, "/a/b")
5858

@@ -251,7 +251,7 @@ func (s *testShifter) Delete(n *testValue, dimension doctree.Dimensions) (*testV
251251
return nil, true, true
252252
}
253253

254-
func (s *testShifter) Shift(n *testValue, dimension doctree.Dimensions, exact bool) (*testValue, bool, doctree.DimensionFlag) {
254+
func (s *testShifter) Shift(n *testValue, dimension doctree.Dimensions, exact, matchDelegees bool) (*testValue, bool, doctree.DimensionFlag) {
255255
if s.echo {
256256
return n, true, doctree.DimensionLanguage
257257
}

hugolib/doctree/nodeshifttree.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type (
5454
// Shift shifts T into the given dimension
5555
// and returns the shifted T and a bool indicating if the shift was successful and
5656
// how accurate a match T is according to its dimensions.
57-
Shift(v T, dimension Dimensions, exact bool) (T, bool, DimensionFlag)
57+
Shift(v T, dimension Dimensions, exact, delegeeFallback bool) (T, bool, DimensionFlag)
5858
}
5959
)
6060

@@ -226,12 +226,12 @@ func (t *NodeShiftTree[T]) Lock(writable bool) (commit func()) {
226226

227227
// LongestPrefix finds the longest prefix of s that exists in the tree that also matches the predicate (if set).
228228
// Set exact to true to only match exact in the current dimension (e.g. language).
229-
func (r *NodeShiftTree[T]) LongestPrefix(s string, exact bool, predicate func(v T) bool) (string, T) {
229+
func (r *NodeShiftTree[T]) LongestPrefix(s string, exact, delegeeFallback bool, predicate func(v T) bool) (string, T) {
230230
for {
231231
longestPrefix, v, found := r.tree.LongestPrefix(s)
232232

233233
if found {
234-
if t, ok, _ := r.shift(v.(T), exact); ok && (predicate == nil || predicate(t)) {
234+
if t, ok, _ := r.shift(v.(T), exact, delegeeFallback); ok && (predicate == nil || predicate(t)) {
235235
return longestPrefix, t
236236
}
237237
}
@@ -317,6 +317,9 @@ type NodeShiftTreeWalker[T any] struct {
317317
// Don't fall back to alternative dimensions (e.g. language).
318318
Exact bool
319319

320+
// TODO1 document this and check vs exact.
321+
DelegeeFallback bool
322+
320323
// Used in development only.
321324
Debug bool
322325

@@ -408,7 +411,7 @@ func (r *NodeShiftTreeWalker[T]) toT(tree *NodeShiftTree[T], v any) (t T, ok boo
408411
t = v.(T)
409412
ok = true
410413
} else {
411-
t, ok, exact = tree.shift(v.(T), r.Exact)
414+
t, ok, exact = tree.shift(v.(T), r.Exact, r.DelegeeFallback)
412415
}
413416
return
414417
}
@@ -422,10 +425,10 @@ func (t NodeShiftTree[T]) clone() *NodeShiftTree[T] {
422425
return &t
423426
}
424427

425-
func (r *NodeShiftTree[T]) shift(t T, exact bool) (T, bool, DimensionFlag) {
428+
func (r *NodeShiftTree[T]) shift(t T, exact, delegeeFallback bool) (T, bool, DimensionFlag) {
426429
// TODO1 exact.
427430
exact = true
428-
return r.shifter.Shift(t, r.dims, exact)
431+
return r.shifter.Shift(t, r.dims, exact, delegeeFallback)
429432
}
430433

431434
func (r *NodeShiftTree[T]) get(s string) (T, bool) {
@@ -435,7 +438,7 @@ func (r *NodeShiftTree[T]) get(s string) (T, bool) {
435438
var t T
436439
return t, false
437440
}
438-
t, ok, _ := r.shift(v.(T), true)
441+
t, ok, _ := r.shift(v.(T), true, false) // TODO1
439442
return t, ok
440443
}
441444

0 commit comments

Comments
 (0)