menu_cache.go (2228B)
1 // Copyright 2021 The Hugo Authors. All rights reserved.
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 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 //
14 package navigation
15
16 import (
17 "sync"
18 )
19
20 type menuCacheEntry struct {
21 in []Menu
22 out Menu
23 }
24
25 func (entry menuCacheEntry) matches(menuList []Menu) bool {
26 if len(entry.in) != len(menuList) {
27 return false
28 }
29 for i, m := range menuList {
30 if !menuEqual(m, entry.in[i]) {
31 return false
32 }
33 }
34
35 return true
36 }
37
38 func newMenuCache() *menuCache {
39 return &menuCache{m: make(map[string][]menuCacheEntry)}
40 }
41
42 func (c *menuCache) clear() {
43 c.Lock()
44 defer c.Unlock()
45 c.m = make(map[string][]menuCacheEntry)
46 }
47
48 type menuCache struct {
49 sync.RWMutex
50 m map[string][]menuCacheEntry
51 }
52
53 func menuEqual(m1, m2 Menu) bool {
54 if m1 == nil && m2 == nil {
55 return true
56 }
57
58 if m1 == nil || m2 == nil {
59 return false
60 }
61
62 if len(m1) != len(m2) {
63 return false
64 }
65
66 if len(m1) == 0 {
67 return true
68 }
69
70 for i := 0; i < len(m1); i++ {
71 if m1[i] != m2[i] {
72 return false
73 }
74 }
75 return true
76 }
77
78 func (c *menuCache) get(key string, apply func(m Menu), menuLists ...Menu) (Menu, bool) {
79 return c.getP(key, func(m *Menu) {
80 if apply != nil {
81 apply(*m)
82 }
83 }, menuLists...)
84 }
85
86 func (c *menuCache) getP(key string, apply func(m *Menu), menuLists ...Menu) (Menu, bool) {
87 c.Lock()
88 defer c.Unlock()
89
90 if cached, ok := c.m[key]; ok {
91 for _, entry := range cached {
92 if entry.matches(menuLists) {
93 return entry.out, true
94 }
95 }
96 }
97
98 m := menuLists[0]
99 menuCopy := append(Menu(nil), m...)
100
101 if apply != nil {
102 apply(&menuCopy)
103 }
104
105 entry := menuCacheEntry{in: menuLists, out: menuCopy}
106 if v, ok := c.m[key]; ok {
107 c.m[key] = append(v, entry)
108 } else {
109 c.m[key] = []menuCacheEntry{entry}
110 }
111
112 return menuCopy, false
113 }