mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-14 20:37:55 -05:00
all: Rework page store, add a dynacache, improve partial rebuilds, and some general spring cleaning
There are some breaking changes in this commit, see #11455. Closes #11455 Closes #11549 This fixes a set of bugs (see issue list) and it is also paying some technical debt accumulated over the years. We now build with Staticcheck enabled in the CI build. The performance should be about the same as before for regular sized Hugo sites, but it should perform and scale much better to larger data sets, as objects that uses lots of memory (e.g. rendered Markdown, big JSON files read into maps with transform.Unmarshal etc.) will now get automatically garbage collected if needed. Performance on partial rebuilds when running the server in fast render mode should be the same, but the change detection should be much more accurate. A list of the notable new features: * A new dependency tracker that covers (almost) all of Hugo's API and is used to do fine grained partial rebuilds when running the server. * A new and simpler tree document store which allows fast lookups and prefix-walking in all dimensions (e.g. language) concurrently. * You can now configure an upper memory limit allowing for much larger data sets and/or running on lower specced PCs. We have lifted the "no resources in sub folders" restriction for branch bundles (e.g. sections). Memory Limit * Hugos will, by default, set aside a quarter of the total system memory, but you can set this via the OS environment variable HUGO_MEMORYLIMIT (in gigabytes). This is backed by a partitioned LRU cache used throughout Hugo. A cache that gets dynamically resized in low memory situations, allowing Go's Garbage Collector to free the memory. New Dependency Tracker: Hugo has had a rule based coarse grained approach to server rebuilds that has worked mostly pretty well, but there have been some surprises (e.g. stale content). This is now revamped with a new dependency tracker that can quickly calculate the delta given a changed resource (e.g. a content file, template, JS file etc.). This handles transitive relations, e.g. $page -> js.Build -> JS import, or $page1.Content -> render hook -> site.GetPage -> $page2.Title, or $page1.Content -> shortcode -> partial -> site.RegularPages -> $page2.Content -> shortcode ..., and should also handle changes to aggregated values (e.g. site.Lastmod) effectively. This covers all of Hugo's API with 2 known exceptions (a list that may not be fully exhaustive): Changes to files loaded with template func os.ReadFile may not be handled correctly. We recommend loading resources with resources.Get Changes to Hugo objects (e.g. Page) passed in the template context to lang.Translate may not be detected correctly. We recommend having simple i18n templates without too much data context passed in other than simple types such as strings and numbers. Note that the cachebuster configuration (when A changes then rebuild B) works well with the above, but we recommend that you revise that configuration, as it in most situations should not be needed. One example where it is still needed is with TailwindCSS and using changes to hugo_stats.json to trigger new CSS rebuilds. Document Store: Previously, a little simplified, we split the document store (where we store pages and resources) in a tree per language. This worked pretty well, but the structure made some operations harder than they needed to be. We have now restructured it into one Radix tree for all languages. Internally the language is considered to be a dimension of that tree, and the tree can be viewed in all dimensions concurrently. This makes some operations re. language simpler (e.g. finding translations is just a slice range), but the idea is that it should also be relatively inexpensive to add more dimensions if needed (e.g. role). Fixes #10169 Fixes #10364 Fixes #10482 Fixes #10630 Fixes #10656 Fixes #10694 Fixes #10918 Fixes #11262 Fixes #11439 Fixes #11453 Fixes #11457 Fixes #11466 Fixes #11540 Fixes #11551 Fixes #11556 Fixes #11654 Fixes #11661 Fixes #11663 Fixes #11664 Fixes #11669 Fixes #11671 Fixes #11807 Fixes #11808 Fixes #11809 Fixes #11815 Fixes #11840 Fixes #11853 Fixes #11860 Fixes #11883 Fixes #11904 Fixes #7388 Fixes #7425 Fixes #7436 Fixes #7544 Fixes #7882 Fixes #7960 Fixes #8255 Fixes #8307 Fixes #8863 Fixes #8927 Fixes #9192 Fixes #9324
This commit is contained in:
parent
5fd1e74903
commit
7285e74090
437 changed files with 19304 additions and 18384 deletions
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
@ -87,6 +87,12 @@ jobs:
|
|||
curl -LJO "https://github.com/sass/dart-sass/releases/download/${env:SASS_VERSION}/dart-sass-${env:SASS_VERSION}-windows-x64.zip";
|
||||
Expand-Archive -Path "dart-sass-${env:SASS_VERSION}-windows-x64.zip" -DestinationPath .;
|
||||
echo "$env:GITHUB_WORKSPACE/dart-sass/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install staticcheck
|
||||
run: go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Run staticcheck
|
||||
run: staticcheck ./...
|
||||
- if: matrix.os != 'windows-latest'
|
||||
name: Check
|
||||
run: |
|
||||
|
|
550
cache/dynacache/dynacache.go
vendored
Normal file
550
cache/dynacache/dynacache.go
vendored
Normal file
|
@ -0,0 +1,550 @@
|
|||
// Copyright 2024 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 dynacache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bep/lazycache"
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/rungroup"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
)
|
||||
|
||||
const minMaxSize = 10
|
||||
|
||||
// New creates a new cache.
|
||||
func New(opts Options) *Cache {
|
||||
if opts.CheckInterval == 0 {
|
||||
opts.CheckInterval = time.Second * 2
|
||||
}
|
||||
|
||||
if opts.MaxSize == 0 {
|
||||
opts.MaxSize = 100000
|
||||
}
|
||||
if opts.Log == nil {
|
||||
panic("nil Log")
|
||||
}
|
||||
|
||||
if opts.MinMaxSize == 0 {
|
||||
opts.MinMaxSize = 30
|
||||
}
|
||||
|
||||
stats := &stats{
|
||||
opts: opts,
|
||||
adjustmentFactor: 1.0,
|
||||
currentMaxSize: opts.MaxSize,
|
||||
availableMemory: config.GetMemoryLimit(),
|
||||
}
|
||||
|
||||
infol := opts.Log.InfoCommand("dynacache")
|
||||
|
||||
c := &Cache{
|
||||
partitions: make(map[string]PartitionManager),
|
||||
opts: opts,
|
||||
stats: stats,
|
||||
infol: infol,
|
||||
}
|
||||
|
||||
c.stop = c.start()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Options for the cache.
|
||||
type Options struct {
|
||||
Log loggers.Logger
|
||||
CheckInterval time.Duration
|
||||
MaxSize int
|
||||
MinMaxSize int
|
||||
Running bool
|
||||
}
|
||||
|
||||
// Options for a partition.
|
||||
type OptionsPartition struct {
|
||||
// When to clear the this partition.
|
||||
ClearWhen ClearWhen
|
||||
|
||||
// Weight is a number between 1 and 100 that indicates how, in general, how big this partition may get.
|
||||
Weight int
|
||||
}
|
||||
|
||||
func (o OptionsPartition) WeightFraction() float64 {
|
||||
return float64(o.Weight) / 100
|
||||
}
|
||||
|
||||
func (o OptionsPartition) CalculateMaxSize(maxSizePerPartition int) int {
|
||||
return int(math.Floor(float64(maxSizePerPartition) * o.WeightFraction()))
|
||||
}
|
||||
|
||||
// A dynamic partitioned cache.
|
||||
type Cache struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
partitions map[string]PartitionManager
|
||||
opts Options
|
||||
infol logg.LevelLogger
|
||||
|
||||
stats *stats
|
||||
stopOnce sync.Once
|
||||
stop func()
|
||||
}
|
||||
|
||||
// ClearMatching clears all partition for which the predicate returns true.
|
||||
func (c *Cache) ClearMatching(predicate func(k, v any) bool) {
|
||||
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
|
||||
NumWorkers: len(c.partitions),
|
||||
Handle: func(ctx context.Context, partition PartitionManager) error {
|
||||
partition.clearMatching(predicate)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
for _, p := range c.partitions {
|
||||
g.Enqueue(p)
|
||||
}
|
||||
|
||||
g.Wait()
|
||||
}
|
||||
|
||||
// ClearOnRebuild prepares the cache for a new rebuild taking the given changeset into account.
|
||||
func (c *Cache) ClearOnRebuild(changeset ...identity.Identity) {
|
||||
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
|
||||
NumWorkers: len(c.partitions),
|
||||
Handle: func(ctx context.Context, partition PartitionManager) error {
|
||||
partition.clearOnRebuild(changeset...)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
for _, p := range c.partitions {
|
||||
g.Enqueue(p)
|
||||
}
|
||||
|
||||
g.Wait()
|
||||
|
||||
// Clear any entries marked as stale above.
|
||||
g = rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
|
||||
NumWorkers: len(c.partitions),
|
||||
Handle: func(ctx context.Context, partition PartitionManager) error {
|
||||
partition.clearStale()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
for _, p := range c.partitions {
|
||||
g.Enqueue(p)
|
||||
}
|
||||
|
||||
g.Wait()
|
||||
}
|
||||
|
||||
type keysProvider interface {
|
||||
Keys() []string
|
||||
}
|
||||
|
||||
// Keys returns a list of keys in all partitions.
|
||||
func (c *Cache) Keys(predicate func(s string) bool) []string {
|
||||
if predicate == nil {
|
||||
predicate = func(s string) bool { return true }
|
||||
}
|
||||
var keys []string
|
||||
for pn, g := range c.partitions {
|
||||
pkeys := g.(keysProvider).Keys()
|
||||
for _, k := range pkeys {
|
||||
p := path.Join(pn, k)
|
||||
if predicate(p) {
|
||||
keys = append(keys, p)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func calculateMaxSizePerPartition(maxItemsTotal, totalWeightQuantity, numPartitions int) int {
|
||||
if numPartitions == 0 {
|
||||
panic("numPartitions must be > 0")
|
||||
}
|
||||
if totalWeightQuantity == 0 {
|
||||
panic("totalWeightQuantity must be > 0")
|
||||
}
|
||||
|
||||
avgWeight := float64(totalWeightQuantity) / float64(numPartitions)
|
||||
return int(math.Floor(float64(maxItemsTotal) / float64(numPartitions) * (100.0 / avgWeight)))
|
||||
}
|
||||
|
||||
// Stop stops the cache.
|
||||
func (c *Cache) Stop() {
|
||||
c.stopOnce.Do(func() {
|
||||
c.stop()
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) adjustCurrentMaxSize() {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if len(c.partitions) == 0 {
|
||||
return
|
||||
}
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
s := c.stats
|
||||
s.memstatsCurrent = m
|
||||
// fmt.Printf("\n\nAvailable = %v\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\nMaxSize = %d\nAdjustmentFactor=%f\n\n", helpers.FormatByteCount(s.availableMemory), helpers.FormatByteCount(m.Alloc), helpers.FormatByteCount(m.TotalAlloc), helpers.FormatByteCount(m.Sys), m.NumGC, c.stats.currentMaxSize, s.adjustmentFactor)
|
||||
|
||||
if s.availableMemory >= s.memstatsCurrent.Alloc {
|
||||
if s.adjustmentFactor <= 1.0 {
|
||||
s.adjustmentFactor += 0.2
|
||||
}
|
||||
} else {
|
||||
// We're low on memory.
|
||||
s.adjustmentFactor -= 0.4
|
||||
}
|
||||
|
||||
if s.adjustmentFactor <= 0 {
|
||||
s.adjustmentFactor = 0.05
|
||||
}
|
||||
|
||||
if !s.adjustCurrentMaxSize() {
|
||||
return
|
||||
}
|
||||
|
||||
totalWeight := 0
|
||||
for _, pm := range c.partitions {
|
||||
totalWeight += pm.getOptions().Weight
|
||||
}
|
||||
|
||||
maxSizePerPartition := calculateMaxSizePerPartition(c.stats.currentMaxSize, totalWeight, len(c.partitions))
|
||||
|
||||
evicted := 0
|
||||
for _, p := range c.partitions {
|
||||
evicted += p.adjustMaxSize(p.getOptions().CalculateMaxSize(maxSizePerPartition))
|
||||
}
|
||||
|
||||
if evicted > 0 {
|
||||
c.infol.
|
||||
WithFields(
|
||||
logg.Fields{
|
||||
{Name: "evicted", Value: evicted},
|
||||
{Name: "numGC", Value: m.NumGC},
|
||||
{Name: "limit", Value: helpers.FormatByteCount(c.stats.availableMemory)},
|
||||
{Name: "alloc", Value: helpers.FormatByteCount(m.Alloc)},
|
||||
{Name: "totalAlloc", Value: helpers.FormatByteCount(m.TotalAlloc)},
|
||||
},
|
||||
).Logf("adjusted partitions' max size")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) start() func() {
|
||||
ticker := time.NewTicker(c.opts.CheckInterval)
|
||||
quit := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
c.adjustCurrentMaxSize()
|
||||
case <-quit:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
close(quit)
|
||||
}
|
||||
}
|
||||
|
||||
var partitionNameRe = regexp.MustCompile(`^\/[a-zA-Z0-9]{4}(\/[a-zA-Z0-9]+)?(\/[a-zA-Z0-9]+)?`)
|
||||
|
||||
// GetOrCreatePartition gets or creates a partition with the given name.
|
||||
func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts OptionsPartition) *Partition[K, V] {
|
||||
if c == nil {
|
||||
panic("nil Cache")
|
||||
}
|
||||
if opts.Weight < 1 || opts.Weight > 100 {
|
||||
panic("invalid Weight, must be between 1 and 100")
|
||||
}
|
||||
|
||||
if partitionNameRe.FindString(name) != name {
|
||||
panic(fmt.Sprintf("invalid partition name %q", name))
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
p, found := c.partitions[name]
|
||||
c.mu.RUnlock()
|
||||
if found {
|
||||
return p.(*Partition[K, V])
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Double check.
|
||||
p, found = c.partitions[name]
|
||||
if found {
|
||||
return p.(*Partition[K, V])
|
||||
}
|
||||
|
||||
// At this point, we don't know the the number of partitions or their configuration, but
|
||||
// this will be re-adjusted later.
|
||||
const numberOfPartitionsEstimate = 10
|
||||
maxSize := opts.CalculateMaxSize(c.opts.MaxSize / numberOfPartitionsEstimate)
|
||||
|
||||
// Create a new partition and cache it.
|
||||
partition := &Partition[K, V]{
|
||||
c: lazycache.New(lazycache.Options[K, V]{MaxEntries: maxSize}),
|
||||
maxSize: maxSize,
|
||||
trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name),
|
||||
opts: opts,
|
||||
}
|
||||
c.partitions[name] = partition
|
||||
|
||||
return partition
|
||||
}
|
||||
|
||||
// Partition is a partition in the cache.
|
||||
type Partition[K comparable, V any] struct {
|
||||
c *lazycache.Cache[K, V]
|
||||
|
||||
zero V
|
||||
|
||||
trace logg.LevelLogger
|
||||
opts OptionsPartition
|
||||
|
||||
maxSize int
|
||||
}
|
||||
|
||||
// GetOrCreate gets or creates a value for the given key.
|
||||
func (p *Partition[K, V]) GetOrCreate(key K, create func(key K) (V, error)) (V, error) {
|
||||
v, _, err := p.c.GetOrCreate(key, create)
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetOrCreateWitTimeout gets or creates a value for the given key and times out if the create function
|
||||
// takes too long.
|
||||
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
resultch := make(chan V, 1)
|
||||
errch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
v, _, err := p.c.GetOrCreate(key, create)
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
resultch <- v
|
||||
}()
|
||||
|
||||
select {
|
||||
case v := <-resultch:
|
||||
return v, nil
|
||||
case err := <-errch:
|
||||
return p.zero, err
|
||||
case <-time.After(duration):
|
||||
return p.zero, &herrors.TimeoutError{
|
||||
Duration: duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) clearMatching(predicate func(k, v any) bool) {
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
if predicate(key, v) {
|
||||
p.trace.Log(
|
||||
logg.StringFunc(
|
||||
func() string {
|
||||
return fmt.Sprintf("clearing cache key %v", key)
|
||||
},
|
||||
),
|
||||
)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
|
||||
opts := p.getOptions()
|
||||
if opts.ClearWhen == ClearNever {
|
||||
return
|
||||
}
|
||||
|
||||
if opts.ClearWhen == ClearOnRebuild {
|
||||
// Clear all.
|
||||
p.Clear()
|
||||
return
|
||||
}
|
||||
|
||||
depsFinder := identity.NewFinder(identity.FinderConfig{})
|
||||
|
||||
shouldDelete := func(key K, v V) bool {
|
||||
// We always clear elements marked as stale.
|
||||
if resource.IsStaleAny(v) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Now check if this entry has changed based on the changeset
|
||||
// based on filesystem events.
|
||||
if len(changeset) == 0 {
|
||||
// Nothing changed.
|
||||
return false
|
||||
}
|
||||
|
||||
var probablyDependent bool
|
||||
identity.WalkIdentitiesShallow(v, func(level int, id2 identity.Identity) bool {
|
||||
for _, id := range changeset {
|
||||
if r := depsFinder.Contains(id, id2, -1); r > 0 {
|
||||
// It's probably dependent, evict from cache.
|
||||
probablyDependent = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return probablyDependent
|
||||
}
|
||||
|
||||
// First pass.
|
||||
// Second pass needs to be done in a separate loop to catch any
|
||||
// elements marked as stale in the other partitions.
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
if shouldDelete(key, v) {
|
||||
p.trace.Log(
|
||||
logg.StringFunc(
|
||||
func() string {
|
||||
return fmt.Sprintf("first pass: clearing cache key %v", key)
|
||||
},
|
||||
),
|
||||
)
|
||||
resource.MarkStale(v)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) Keys() []K {
|
||||
var keys []K
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
keys = append(keys, key)
|
||||
return false
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) clearStale() {
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
isStale := resource.IsStaleAny(v)
|
||||
if isStale {
|
||||
p.trace.Log(
|
||||
logg.StringFunc(
|
||||
func() string {
|
||||
return fmt.Sprintf("second pass: clearing cache key %v", key)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return isStale
|
||||
})
|
||||
}
|
||||
|
||||
// adjustMaxSize adjusts the max size of the and returns the number of items evicted.
|
||||
func (p *Partition[K, V]) adjustMaxSize(newMaxSize int) int {
|
||||
if newMaxSize < minMaxSize {
|
||||
newMaxSize = minMaxSize
|
||||
}
|
||||
p.maxSize = newMaxSize
|
||||
// fmt.Println("Adjusting max size of partition from", oldMaxSize, "to", newMaxSize)
|
||||
return p.c.Resize(newMaxSize)
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) getMaxSize() int {
|
||||
return p.maxSize
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) getOptions() OptionsPartition {
|
||||
return p.opts
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) Clear() {
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) Get(ctx context.Context, key K) (V, bool) {
|
||||
return p.c.Get(key)
|
||||
}
|
||||
|
||||
type PartitionManager interface {
|
||||
adjustMaxSize(addend int) int
|
||||
getMaxSize() int
|
||||
getOptions() OptionsPartition
|
||||
clearOnRebuild(changeset ...identity.Identity)
|
||||
clearMatching(predicate func(k, v any) bool)
|
||||
clearStale()
|
||||
}
|
||||
|
||||
const (
|
||||
ClearOnRebuild ClearWhen = iota + 1
|
||||
ClearOnChange
|
||||
ClearNever
|
||||
)
|
||||
|
||||
type ClearWhen int
|
||||
|
||||
type stats struct {
|
||||
opts Options
|
||||
memstatsCurrent runtime.MemStats
|
||||
currentMaxSize int
|
||||
availableMemory uint64
|
||||
|
||||
adjustmentFactor float64
|
||||
}
|
||||
|
||||
func (s *stats) adjustCurrentMaxSize() bool {
|
||||
newCurrentMaxSize := int(math.Floor(float64(s.opts.MaxSize) * s.adjustmentFactor))
|
||||
|
||||
if newCurrentMaxSize < s.opts.MaxSize {
|
||||
newCurrentMaxSize = int(s.opts.MinMaxSize)
|
||||
}
|
||||
changed := newCurrentMaxSize != s.currentMaxSize
|
||||
s.currentMaxSize = newCurrentMaxSize
|
||||
return changed
|
||||
}
|
||||
|
||||
// CleanKey turns s into a format suitable for a cache key for this package.
|
||||
// The key will be a Unix-styled path with a leading slash but no trailing slash.
|
||||
func CleanKey(s string) string {
|
||||
return path.Clean(paths.ToSlashPreserveLeading(s))
|
||||
}
|
175
cache/dynacache/dynacache_test.go
vendored
Normal file
175
cache/dynacache/dynacache_test.go
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
// Copyright 2024 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 dynacache
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.StaleInfo = (*testItem)(nil)
|
||||
_ identity.Identity = (*testItem)(nil)
|
||||
)
|
||||
|
||||
type testItem struct {
|
||||
name string
|
||||
isStale bool
|
||||
}
|
||||
|
||||
func (t testItem) IsStale() bool {
|
||||
return t.isStale
|
||||
}
|
||||
|
||||
func (t testItem) IdentifierBase() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
cache := New(Options{
|
||||
Log: loggers.NewDefault(),
|
||||
})
|
||||
|
||||
c.Cleanup(func() {
|
||||
cache.Stop()
|
||||
})
|
||||
|
||||
opts := OptionsPartition{Weight: 30}
|
||||
|
||||
c.Assert(cache, qt.Not(qt.IsNil))
|
||||
|
||||
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts)
|
||||
c.Assert(p1, qt.Not(qt.IsNil))
|
||||
|
||||
p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts)
|
||||
|
||||
c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "foo bar", opts) }, qt.PanicMatches, ".*invalid partition name.*")
|
||||
c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 1234}) }, qt.PanicMatches, ".*invalid Weight.*")
|
||||
|
||||
c.Assert(p2, qt.Equals, p1)
|
||||
|
||||
p3 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", opts)
|
||||
c.Assert(p3, qt.Not(qt.IsNil))
|
||||
c.Assert(p3, qt.Not(qt.Equals), p1)
|
||||
|
||||
c.Assert(func() { New(Options{}) }, qt.PanicMatches, ".*nil Log.*")
|
||||
}
|
||||
|
||||
func TestCalculateMaxSizePerPartition(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(calculateMaxSizePerPartition(1000, 500, 5), qt.Equals, 200)
|
||||
c.Assert(calculateMaxSizePerPartition(1000, 250, 5), qt.Equals, 400)
|
||||
c.Assert(func() { calculateMaxSizePerPartition(1000, 250, 0) }, qt.PanicMatches, ".*must be > 0.*")
|
||||
c.Assert(func() { calculateMaxSizePerPartition(1000, 0, 1) }, qt.PanicMatches, ".*must be > 0.*")
|
||||
}
|
||||
|
||||
func TestCleanKey(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(CleanKey("a/b/c"), qt.Equals, "/a/b/c")
|
||||
c.Assert(CleanKey("/a/b/c"), qt.Equals, "/a/b/c")
|
||||
c.Assert(CleanKey("a/b/c/"), qt.Equals, "/a/b/c")
|
||||
c.Assert(CleanKey(filepath.FromSlash("/a/b/c/")), qt.Equals, "/a/b/c")
|
||||
}
|
||||
|
||||
func newTestCache(t *testing.T) *Cache {
|
||||
cache := New(
|
||||
Options{
|
||||
Log: loggers.NewDefault(),
|
||||
},
|
||||
)
|
||||
|
||||
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
|
||||
p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 30, ClearWhen: ClearOnChange})
|
||||
|
||||
p1.GetOrCreate("clearOnRebuild", func(string) (testItem, error) {
|
||||
return testItem{}, nil
|
||||
})
|
||||
|
||||
p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
isStale: true,
|
||||
}, nil
|
||||
})
|
||||
|
||||
p2.GetOrCreate("clearBecauseIdentityChanged", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
name: "changed",
|
||||
}, nil
|
||||
})
|
||||
|
||||
p2.GetOrCreate("clearNever", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
isStale: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
t.Cleanup(func() {
|
||||
cache.Stop()
|
||||
})
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func TestClear(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
predicateAll := func(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
cache := newTestCache(t)
|
||||
|
||||
c.Assert(cache.Keys(predicateAll), qt.HasLen, 4)
|
||||
|
||||
cache.ClearOnRebuild()
|
||||
|
||||
// Stale items are always cleared.
|
||||
c.Assert(cache.Keys(predicateAll), qt.HasLen, 2)
|
||||
|
||||
cache = newTestCache(t)
|
||||
cache.ClearOnRebuild(identity.StringIdentity("changed"))
|
||||
|
||||
c.Assert(cache.Keys(nil), qt.HasLen, 1)
|
||||
|
||||
cache = newTestCache(t)
|
||||
|
||||
cache.ClearMatching(func(k, v any) bool {
|
||||
return k.(string) == "clearOnRebuild"
|
||||
})
|
||||
|
||||
c.Assert(cache.Keys(predicateAll), qt.HasLen, 3)
|
||||
|
||||
cache.adjustCurrentMaxSize()
|
||||
}
|
||||
|
||||
func TestAdjustCurrentMaxSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
cache := newTestCache(t)
|
||||
alloc := cache.stats.memstatsCurrent.Alloc
|
||||
cache.adjustCurrentMaxSize()
|
||||
c.Assert(cache.stats.memstatsCurrent.Alloc, qt.Not(qt.Equals), alloc)
|
||||
}
|
8
cache/filecache/filecache.go
vendored
8
cache/filecache/filecache.go
vendored
|
@ -24,6 +24,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
|
@ -109,7 +110,7 @@ func (l *lockedFile) Close() error {
|
|||
func (c *Cache) init() error {
|
||||
c.initOnce.Do(func() {
|
||||
// Create the base dir if it does not exist.
|
||||
if err := c.Fs.MkdirAll("", 0777); err != nil && !os.IsExist(err) {
|
||||
if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(err) {
|
||||
c.initErr = err
|
||||
}
|
||||
})
|
||||
|
@ -146,7 +147,8 @@ func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) {
|
|||
// it when done.
|
||||
func (c *Cache) ReadOrCreate(id string,
|
||||
read func(info ItemInfo, r io.ReadSeeker) error,
|
||||
create func(info ItemInfo, w io.WriteCloser) error) (info ItemInfo, err error) {
|
||||
create func(info ItemInfo, w io.WriteCloser) error,
|
||||
) (info ItemInfo, err error) {
|
||||
if err := c.init(); err != nil {
|
||||
return ItemInfo{}, err
|
||||
}
|
||||
|
@ -380,7 +382,7 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
|
|||
|
||||
baseDir := v.DirCompiled
|
||||
|
||||
bfs := afero.NewBasePathFs(cfs, baseDir)
|
||||
bfs := hugofs.NewBasePathFs(cfs, baseDir)
|
||||
|
||||
var pruneAllRootDir string
|
||||
if k == CacheKeyModules {
|
||||
|
|
12
cache/filecache/filecache_test.go
vendored
12
cache/filecache/filecache_test.go
vendored
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -17,7 +17,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -86,17 +85,8 @@ dir = ":cacheDir/c"
|
|||
cache := caches.Get("GetJSON")
|
||||
c.Assert(cache, qt.Not(qt.IsNil))
|
||||
|
||||
bfs, ok := cache.Fs.(*afero.BasePathFs)
|
||||
c.Assert(ok, qt.Equals, true)
|
||||
filename, err := bfs.RealPath("key")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
cache = caches.Get("Images")
|
||||
c.Assert(cache, qt.Not(qt.IsNil))
|
||||
bfs, ok = cache.Fs.(*afero.BasePathFs)
|
||||
c.Assert(ok, qt.Equals, true)
|
||||
filename, _ = bfs.RealPath("key")
|
||||
c.Assert(filename, qt.Equals, filepath.FromSlash("_gen/images/key"))
|
||||
|
||||
rf := func(s string) func() (io.ReadCloser, error) {
|
||||
return func() (io.ReadCloser, error) {
|
||||
|
|
8
cache/filecache/integration_test.go
vendored
8
cache/filecache/integration_test.go
vendored
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -15,7 +15,6 @@ package filecache_test
|
|||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -47,7 +46,6 @@ title: "Home"
|
|||
_, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images"))
|
||||
|
||||
b.Assert(err, qt.IsNil)
|
||||
|
||||
}
|
||||
|
||||
func TestPruneImages(t *testing.T) {
|
||||
|
@ -55,6 +53,7 @@ func TestPruneImages(t *testing.T) {
|
|||
// TODO(bep)
|
||||
t.Skip("skip flaky test on CI server")
|
||||
}
|
||||
t.Skip("skip flaky test")
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
|
@ -92,7 +91,7 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
|
|||
|
||||
// TODO(bep) we need a way to test full rebuilds.
|
||||
// For now, just sleep a little so the cache elements expires.
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build()
|
||||
|
||||
|
@ -104,5 +103,4 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
|
|||
b.Assert(err, qt.Not(qt.IsNil))
|
||||
_, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
|
||||
b.Assert(err, qt.IsNil)
|
||||
|
||||
}
|
||||
|
|
78
cache/namedmemcache/named_cache.go
vendored
78
cache/namedmemcache/named_cache.go
vendored
|
@ -1,78 +0,0 @@
|
|||
// Copyright 2018 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 namedmemcache provides a memory cache with a named lock. This is suitable
|
||||
// for situations where creating the cached resource can be time consuming or otherwise
|
||||
// resource hungry, or in situations where a "once only per key" is a requirement.
|
||||
package namedmemcache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/BurntSushi/locker"
|
||||
)
|
||||
|
||||
// Cache holds the cached values.
|
||||
type Cache struct {
|
||||
nlocker *locker.Locker
|
||||
cache map[string]cacheEntry
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
value any
|
||||
err error
|
||||
}
|
||||
|
||||
// New creates a new cache.
|
||||
func New() *Cache {
|
||||
return &Cache{
|
||||
nlocker: locker.NewLocker(),
|
||||
cache: make(map[string]cacheEntry),
|
||||
}
|
||||
}
|
||||
|
||||
// Clear clears the cache state.
|
||||
func (c *Cache) Clear() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.cache = make(map[string]cacheEntry)
|
||||
c.nlocker = locker.NewLocker()
|
||||
}
|
||||
|
||||
// GetOrCreate tries to get the value with the given cache key, if not found
|
||||
// create will be called and cached.
|
||||
// This method is thread safe. It also guarantees that the create func for a given
|
||||
// key is invoked only once for this cache.
|
||||
func (c *Cache) GetOrCreate(key string, create func() (any, error)) (any, error) {
|
||||
c.mu.RLock()
|
||||
entry, found := c.cache[key]
|
||||
c.mu.RUnlock()
|
||||
|
||||
if found {
|
||||
return entry.value, entry.err
|
||||
}
|
||||
|
||||
c.nlocker.Lock(key)
|
||||
defer c.nlocker.Unlock(key)
|
||||
|
||||
// Create it.
|
||||
value, err := create()
|
||||
|
||||
c.mu.Lock()
|
||||
c.cache[key] = cacheEntry{value: value, err: err}
|
||||
c.mu.Unlock()
|
||||
|
||||
return value, err
|
||||
}
|
80
cache/namedmemcache/named_cache_test.go
vendored
80
cache/namedmemcache/named_cache_test.go
vendored
|
@ -1,80 +0,0 @@
|
|||
// Copyright 2018 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 namedmemcache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestNamedCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
cache := New()
|
||||
|
||||
counter := 0
|
||||
create := func() (any, error) {
|
||||
counter++
|
||||
return counter, nil
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
v1, err := cache.GetOrCreate("a1", create)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v1, qt.Equals, 1)
|
||||
v2, err := cache.GetOrCreate("a2", create)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v2, qt.Equals, 2)
|
||||
}
|
||||
|
||||
cache.Clear()
|
||||
|
||||
v3, err := cache.GetOrCreate("a2", create)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v3, qt.Equals, 3)
|
||||
}
|
||||
|
||||
func TestNamedCacheConcurrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
cache := New()
|
||||
|
||||
create := func(i int) func() (any, error) {
|
||||
return func() (any, error) {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
id := fmt.Sprintf("id%d", j)
|
||||
v, err := cache.GetOrCreate(id, create(j))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.Equals, j)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -259,7 +259,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
|
|||
publishDirStatic := cfg.GetString("publishDirStatic")
|
||||
workingDir := cfg.GetString("workingDir")
|
||||
absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic)
|
||||
staticFs := afero.NewBasePathFs(afero.NewOsFs(), absPublishDirStatic)
|
||||
staticFs := hugofs.NewBasePathFs(afero.NewOsFs(), absPublishDirStatic)
|
||||
|
||||
// Serve from both the static and dynamic fs,
|
||||
// the first will take priority.
|
||||
|
@ -405,8 +405,14 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
|||
return err
|
||||
}
|
||||
|
||||
r.commonConfigs = lazycache.New[int32, *commonConfig](lazycache.Options{MaxEntries: 5})
|
||||
r.hugoSites = lazycache.New[int32, *hugolib.HugoSites](lazycache.Options{MaxEntries: 5})
|
||||
r.commonConfigs = lazycache.New(lazycache.Options[int32, *commonConfig]{MaxEntries: 5})
|
||||
// We don't want to keep stale HugoSites in memory longer than needed.
|
||||
r.hugoSites = lazycache.New(lazycache.Options[int32, *hugolib.HugoSites]{
|
||||
MaxEntries: 1,
|
||||
OnEvict: func(key int32, value *hugolib.HugoSites) {
|
||||
value.Close()
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -37,5 +37,4 @@ func newExec() (*simplecobra.Exec, error) {
|
|||
}
|
||||
|
||||
return simplecobra.New(rootCmd)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -37,7 +37,6 @@ func newConfigCommand() *configCommand {
|
|||
&configMountsCommand{},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type configCommand struct {
|
||||
|
@ -190,7 +189,6 @@ func (m *configModMounts) MarshalJSON() ([]byte, error) {
|
|||
Dir: m.m.Dir(),
|
||||
Mounts: mounts,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
type configMountsCommand struct {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -134,7 +134,7 @@ func (c *convertCommand) convertAndSavePage(p page.Page, site *hugolib.Site, tar
|
|||
}
|
||||
}
|
||||
|
||||
if p.File().IsZero() {
|
||||
if p.File() == nil {
|
||||
// No content file.
|
||||
return nil
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ func (c *convertCommand) convertContents(format metadecoders.Format) error {
|
|||
|
||||
var pagesBackedByFile page.Pages
|
||||
for _, p := range site.AllPages() {
|
||||
if p.File().IsZero() {
|
||||
if p.File() == nil {
|
||||
continue
|
||||
}
|
||||
pagesBackedByFile = append(pagesBackedByFile, p)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -14,7 +14,7 @@
|
|||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
|
||||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -38,7 +38,6 @@ import (
|
|||
)
|
||||
|
||||
func newDeployCommand() simplecobra.Commander {
|
||||
|
||||
return &simpleCommand{
|
||||
name: "deploy",
|
||||
short: "Deploy your site to a Cloud provider.",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -14,7 +14,7 @@
|
|||
//go:build nodeploy
|
||||
// +build nodeploy
|
||||
|
||||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -101,7 +101,7 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
|||
}
|
||||
if found, _ := helpers.Exists(genmandir, hugofs.Os); !found {
|
||||
r.Println("Directory", genmandir, "does not exist, creating...")
|
||||
if err := hugofs.Os.MkdirAll(genmandir, 0777); err != nil {
|
||||
if err := hugofs.Os.MkdirAll(genmandir, 0o777); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ url: %s
|
|||
}
|
||||
if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found {
|
||||
r.Println("Directory", gendocdir, "does not exist, creating...")
|
||||
if err := hugofs.Os.MkdirAll(gendocdir, 0777); err != nil {
|
||||
if err := hugofs.Os.MkdirAll(gendocdir, 0o777); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,6 @@ url: %s
|
|||
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var docsHelperTarget string
|
||||
|
@ -241,7 +240,6 @@ url: %s
|
|||
newDocsHelper(),
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type genCommand struct {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -110,12 +110,11 @@ func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.P
|
|||
})
|
||||
|
||||
return cfg
|
||||
|
||||
}
|
||||
|
||||
func mkdir(x ...string) {
|
||||
p := filepath.Join(x...)
|
||||
err := os.MkdirAll(p, 0777) // before umask
|
||||
err := os.MkdirAll(p, 0o777) // before umask
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -24,6 +24,7 @@ import (
|
|||
"runtime/trace"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/bep/logg"
|
||||
|
@ -34,6 +35,7 @@ import (
|
|||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/terminal"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
@ -83,7 +85,6 @@ func (c *hugoBuilder) withConf(fn func(conf *commonConfig)) {
|
|||
c.confmu.Lock()
|
||||
defer c.confmu.Unlock()
|
||||
fn(c.conf)
|
||||
|
||||
}
|
||||
|
||||
type hugoBuilderErrState struct {
|
||||
|
@ -135,46 +136,12 @@ func (c *hugoBuilder) errCount() int {
|
|||
|
||||
// getDirList provides NewWatcher() with a list of directories to watch for changes.
|
||||
func (c *hugoBuilder) getDirList() ([]string, error) {
|
||||
var filenames []string
|
||||
|
||||
walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("walker: ", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
if fi.Name() == ".git" ||
|
||||
fi.Name() == "node_modules" || fi.Name() == "bower_components" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
filenames = append(filenames, fi.Meta().Filename)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
h, err := c.hugo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
watchFiles := h.PathSpec.BaseFs.WatchDirs()
|
||||
for _, fi := range watchFiles {
|
||||
if !fi.IsDir() {
|
||||
filenames = append(filenames, fi.Meta().Filename)
|
||||
continue
|
||||
}
|
||||
|
||||
w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.r.logger, Info: fi, WalkFn: walkFn})
|
||||
if err := w.Walk(); err != nil {
|
||||
c.r.logger.Errorln("walker: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
filenames = helpers.UniqueStringsSorted(filenames)
|
||||
|
||||
return filenames, nil
|
||||
return helpers.UniqueStringsSorted(h.PathSpec.BaseFs.WatchFilenames()), nil
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) initCPUProfile() (func(), error) {
|
||||
|
@ -441,7 +408,7 @@ func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
|
|||
}
|
||||
|
||||
func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
|
||||
infol := c.r.logger.InfoCommand("copy static")
|
||||
infol := c.r.logger.InfoCommand("static")
|
||||
publishDir := helpers.FilePathSeparator
|
||||
|
||||
if sourceFs.PublishFolder != "" {
|
||||
|
@ -467,11 +434,11 @@ func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint
|
|||
if syncer.Delete {
|
||||
infol.Logf("removing all files from destination that don't exist in static dirs")
|
||||
|
||||
syncer.DeleteFilter = func(f os.FileInfo) bool {
|
||||
syncer.DeleteFilter = func(f fsync.FileInfo) bool {
|
||||
return f.IsDir() && strings.HasPrefix(f.Name(), ".")
|
||||
}
|
||||
}
|
||||
infol.Logf("syncing static files to %s", publishDir)
|
||||
start := time.Now()
|
||||
|
||||
// because we are using a baseFs (to get the union right).
|
||||
// set sync src to root
|
||||
|
@ -479,9 +446,10 @@ func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
loggers.TimeTrackf(infol, start, nil, "syncing static files to %s", publishDir)
|
||||
|
||||
// Sync runs Stat 3 times for every source file (which sounds much)
|
||||
numFiles := fs.statCounter / 3
|
||||
// Sync runs Stat 2 times for every source file.
|
||||
numFiles := fs.statCounter / 2
|
||||
|
||||
return numFiles, err
|
||||
}
|
||||
|
@ -652,13 +620,31 @@ func (c *hugoBuilder) handleBuildErr(err error, msg string) {
|
|||
func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
staticSyncer *staticSyncer,
|
||||
evs []fsnotify.Event,
|
||||
configSet map[string]bool) {
|
||||
configSet map[string]bool,
|
||||
) {
|
||||
defer func() {
|
||||
c.errState.setWasErr(false)
|
||||
}()
|
||||
|
||||
var isHandled bool
|
||||
|
||||
// Filter out ghost events (from deleted, renamed directories).
|
||||
// This seems to be a bug in fsnotify, or possibly MacOS.
|
||||
var n int
|
||||
for _, ev := range evs {
|
||||
keep := true
|
||||
if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
|
||||
if _, err := os.Stat(ev.Name); err != nil {
|
||||
keep = false
|
||||
}
|
||||
}
|
||||
if keep {
|
||||
evs[n] = ev
|
||||
n++
|
||||
}
|
||||
}
|
||||
evs = evs[:n]
|
||||
|
||||
for _, ev := range evs {
|
||||
isConfig := configSet[ev.Name]
|
||||
configChangeType := configChangeConfig
|
||||
|
@ -726,48 +712,25 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
|||
return
|
||||
}
|
||||
|
||||
c.r.logger.Infoln("Received System Events:", evs)
|
||||
c.r.logger.Debugln("Received System Events:", evs)
|
||||
|
||||
staticEvents := []fsnotify.Event{}
|
||||
dynamicEvents := []fsnotify.Event{}
|
||||
|
||||
filtered := []fsnotify.Event{}
|
||||
h, err := c.hugo()
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("Error getting the Hugo object:", err)
|
||||
return
|
||||
}
|
||||
n = 0
|
||||
for _, ev := range evs {
|
||||
if h.ShouldSkipFileChangeEvent(ev) {
|
||||
continue
|
||||
}
|
||||
// Check the most specific first, i.e. files.
|
||||
contentMapped := h.ContentChanges.GetSymbolicLinkMappings(ev.Name)
|
||||
if len(contentMapped) > 0 {
|
||||
for _, mapped := range contentMapped {
|
||||
filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for any symbolic directory mapping.
|
||||
|
||||
dir, name := filepath.Split(ev.Name)
|
||||
|
||||
contentMapped = h.ContentChanges.GetSymbolicLinkMappings(dir)
|
||||
|
||||
if len(contentMapped) == 0 {
|
||||
filtered = append(filtered, ev)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, mapped := range contentMapped {
|
||||
mappedFilename := filepath.Join(mapped, name)
|
||||
filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
|
||||
}
|
||||
evs[n] = ev
|
||||
n++
|
||||
}
|
||||
|
||||
evs = filtered
|
||||
evs = evs[:n]
|
||||
|
||||
for _, ev := range evs {
|
||||
ext := filepath.Ext(ev.Name)
|
||||
|
@ -788,6 +751,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
|||
if istemp {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.Deps.SourceSpec.IgnoreFile(ev.Name) {
|
||||
continue
|
||||
}
|
||||
|
@ -811,7 +775,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
|||
continue
|
||||
}
|
||||
|
||||
walkAdder := func(path string, f hugofs.FileMetaInfo, err error) error {
|
||||
walkAdder := func(path string, f hugofs.FileMetaInfo) error {
|
||||
if f.IsDir() {
|
||||
c.r.logger.Println("adding created directory to watchlist", path)
|
||||
if err := watcher.Add(path); err != nil {
|
||||
|
@ -827,11 +791,10 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
|||
}
|
||||
|
||||
// recursively add new directories to watch list
|
||||
// When mkdir -p is used, only the top directory triggers an event (at least on OSX)
|
||||
if ev.Op&fsnotify.Create == fsnotify.Create {
|
||||
if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Rename) {
|
||||
c.withConf(func(conf *commonConfig) {
|
||||
if s, err := conf.fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
|
||||
_ = helpers.SymbolicWalk(conf.fs.Source, ev.Name, walkAdder)
|
||||
_ = helpers.Walk(conf.fs.Source, ev.Name, walkAdder)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -872,7 +835,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
|||
return
|
||||
}
|
||||
path := h.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
|
||||
path = h.RelURL(helpers.ToSlashTrimLeading(path), false)
|
||||
path = h.RelURL(paths.ToSlashTrimLeading(path), false)
|
||||
|
||||
livereload.RefreshPath(path)
|
||||
} else {
|
||||
|
@ -909,7 +872,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
|||
// Nothing has changed.
|
||||
return
|
||||
} else if len(changed) == 1 {
|
||||
pathToRefresh := h.PathSpec.RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
|
||||
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(changed[0]), false)
|
||||
livereload.RefreshPath(pathToRefresh)
|
||||
} else {
|
||||
livereload.ForceRefresh()
|
||||
|
@ -944,7 +907,6 @@ func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) {
|
|||
var err error
|
||||
h, err = c.r.HugFromConfig(conf)
|
||||
return err
|
||||
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1000,6 +962,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
|
|||
}
|
||||
|
||||
if len(conf.configs.LoadingInfo.ConfigFiles) == 0 {
|
||||
//lint:ignore ST1005 end user message.
|
||||
return errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\nRun `hugo help new` for details.")
|
||||
}
|
||||
|
||||
|
@ -1011,15 +974,16 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
|
|||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
var rebuildCounter atomic.Uint64
|
||||
|
||||
func (c *hugoBuilder) printChangeDetected(typ string) {
|
||||
msg := "\nChange"
|
||||
if typ != "" {
|
||||
msg += " of " + typ
|
||||
}
|
||||
msg += " detected, rebuilding site."
|
||||
msg += fmt.Sprintf(" detected, rebuilding site (#%d).", rebuildCounter.Add(1))
|
||||
|
||||
c.r.logger.Println(msg)
|
||||
const layout = "2006-01-02 15:04:05.000 -0700"
|
||||
|
@ -1034,25 +998,12 @@ func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
|
|||
}
|
||||
}
|
||||
c.errState.setBuildErr(nil)
|
||||
visited := c.visitedURLs.PeekAllSet()
|
||||
h, err := c.hugo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.fastRenderMode {
|
||||
c.withConf(func(conf *commonConfig) {
|
||||
// Make sure we always render the home pages
|
||||
for _, l := range conf.configs.ConfigLangs() {
|
||||
langPath := l.LanguagePrefix()
|
||||
if langPath != "" {
|
||||
langPath = langPath + "/"
|
||||
}
|
||||
home := h.PrependBasePath("/"+langPath, false)
|
||||
visited[home] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: visited, ErrRecovery: c.errState.wasErr()}, events...)
|
||||
|
||||
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) reloadConfig() error {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -23,7 +23,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -66,7 +65,6 @@ Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root
|
|||
}
|
||||
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
type importCommand struct {
|
||||
|
@ -312,7 +310,7 @@ func (c *importCommand) convertJekyllPost(path, relPath, targetDir string, draft
|
|||
|
||||
targetFile := filepath.Join(targetDir, relPath)
|
||||
targetParentDir := filepath.Dir(targetFile)
|
||||
os.MkdirAll(targetParentDir, 0777)
|
||||
os.MkdirAll(targetParentDir, 0o777)
|
||||
|
||||
contentBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
@ -398,7 +396,6 @@ func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyl
|
|||
}
|
||||
|
||||
func (c *importCommand) importFromJekyll(args []string) error {
|
||||
|
||||
jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
|
||||
if err != nil {
|
||||
return newUserError("path error:", args[0])
|
||||
|
@ -429,11 +426,7 @@ func (c *importCommand) importFromJekyll(args []string) error {
|
|||
c.r.Println("Importing...")
|
||||
|
||||
fileCount := 0
|
||||
callback := func(path string, fi hugofs.FileMetaInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
callback := func(path string, fi hugofs.FileMetaInfo) error {
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
@ -462,7 +455,7 @@ func (c *importCommand) importFromJekyll(args []string) error {
|
|||
|
||||
for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs {
|
||||
if hasAnyPostInDir {
|
||||
if err = helpers.SymbolicWalk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
|
||||
if err = helpers.Walk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -31,7 +31,6 @@ import (
|
|||
|
||||
// newListCommand creates a new list command and its subcommands.
|
||||
func newListCommand() *listCommand {
|
||||
|
||||
createRecord := func(workingDir string, p page.Page) []string {
|
||||
return []string{
|
||||
filepath.ToSlash(strings.TrimPrefix(p.File().Filename(), workingDir+string(os.PathSeparator))),
|
||||
|
@ -83,7 +82,6 @@ func newListCommand() *listCommand {
|
|||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
return &listCommand{
|
||||
|
@ -94,11 +92,10 @@ func newListCommand() *listCommand {
|
|||
long: `List all of the drafts in your content directory.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !p.Draft() || p.File().IsZero() {
|
||||
if !p.Draft() || p.File() == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
return list(cd, r, shouldInclude,
|
||||
"buildDrafts", true,
|
||||
|
@ -113,11 +110,10 @@ func newListCommand() *listCommand {
|
|||
long: `List all of the posts in your content directory which will be posted in the future.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !resource.IsFuture(p) || p.File().IsZero() {
|
||||
if !resource.IsFuture(p) || p.File() == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
return list(cd, r, shouldInclude,
|
||||
"buildFuture", true,
|
||||
|
@ -131,7 +127,7 @@ func newListCommand() *listCommand {
|
|||
long: `List all of the posts in your content directory which has already expired.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !resource.IsExpired(p) || p.File().IsZero() {
|
||||
if !resource.IsExpired(p) || p.File() == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -148,14 +144,13 @@ func newListCommand() *listCommand {
|
|||
long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
return !p.File().IsZero()
|
||||
return p.File() != nil
|
||||
}
|
||||
return list(cd, r, shouldInclude, "buildDrafts", true, "buildFuture", true, "buildExpired", true)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type listCommand struct {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -69,7 +69,7 @@ so this may/will change in future versions of Hugo.
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return npm.Pack(h.BaseFs.SourceFs, h.BaseFs.Assets.Dirs)
|
||||
return npm.Pack(h.BaseFs.ProjectSourceFs, h.BaseFs.AssetsWithDuplicatesPreserved.Fs)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -64,7 +64,6 @@ Ensure you run this within the root directory of your site.`,
|
|||
cmd.Flags().String("editor", "", "edit new content with this editor, if provided")
|
||||
cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists")
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
|
@ -143,7 +142,6 @@ according to your needs.`,
|
|||
}
|
||||
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
type newCommand struct {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -24,7 +24,6 @@ import (
|
|||
// Note: This is a command only meant for internal use and must be run
|
||||
// via "go run -tags release main.go release" on the actual code base that is in the release.
|
||||
func newReleaseCommand() simplecobra.Commander {
|
||||
|
||||
var (
|
||||
step int
|
||||
skipPush bool
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -27,20 +27,19 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/bep/mclib"
|
||||
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bep/mclib"
|
||||
|
||||
"github.com/bep/debounce"
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
|
@ -83,10 +82,14 @@ const (
|
|||
)
|
||||
|
||||
func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
|
||||
var visitedURLs *types.EvictingStringQueue
|
||||
if s != nil && !s.disableFastRender {
|
||||
visitedURLs = types.NewEvictingStringQueue(20)
|
||||
}
|
||||
return &hugoBuilder{
|
||||
r: r,
|
||||
s: s,
|
||||
visitedURLs: types.NewEvictingStringQueue(100),
|
||||
visitedURLs: visitedURLs,
|
||||
fullRebuildSem: semaphore.NewWeighted(1),
|
||||
debounce: debounce.New(4 * time.Second),
|
||||
onConfigLoaded: func(reloaded bool) error {
|
||||
|
@ -120,7 +123,6 @@ func newServerCommand() *serverCommand {
|
|||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Flags().BoolVar(&uninstall, "uninstall", false, "Uninstall the local CA (but do not delete it).")
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -219,7 +221,7 @@ func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
|
|||
}
|
||||
|
||||
type fileServer struct {
|
||||
baseURLs []string
|
||||
baseURLs []urls.BaseURL
|
||||
roots []string
|
||||
errorTemplate func(err any) (io.Reader, error)
|
||||
c *serverCommand
|
||||
|
@ -255,12 +257,6 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
|||
r.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")
|
||||
}
|
||||
|
||||
// We're only interested in the path
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, nil, "", "", fmt.Errorf("invalid baseURL: %w", err)
|
||||
}
|
||||
|
||||
decorate := func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if f.c.showErrorInBrowser {
|
||||
|
@ -280,7 +276,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
|||
port = lrport
|
||||
}
|
||||
})
|
||||
lr := *u
|
||||
lr := baseURL.URL()
|
||||
lr.Host = fmt.Sprintf("%s:%d", lr.Hostname(), port)
|
||||
fmt.Fprint(w, injectLiveReloadScript(r, lr))
|
||||
|
||||
|
@ -311,7 +307,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
|||
// This matches Netlify's behaviour and is needed for SPA behaviour.
|
||||
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
|
||||
if !redirect.Force {
|
||||
path := filepath.Clean(strings.TrimPrefix(requestURI, u.Path))
|
||||
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
|
||||
if root != "" {
|
||||
path = filepath.Join(root, path)
|
||||
}
|
||||
|
@ -338,7 +334,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
|||
switch redirect.Status {
|
||||
case 404:
|
||||
w.WriteHeader(404)
|
||||
file, err := fs.Open(strings.TrimPrefix(redirect.To, u.Path))
|
||||
file, err := fs.Open(strings.TrimPrefix(redirect.To, baseURL.Path()))
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
io.Copy(w, file)
|
||||
|
@ -347,7 +343,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
|||
}
|
||||
return
|
||||
case 200:
|
||||
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, u.Path)); r2 != nil {
|
||||
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, baseURL.Path())); r2 != nil {
|
||||
requestURI = redirect.To
|
||||
r = r2
|
||||
}
|
||||
|
@ -385,10 +381,10 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
|||
|
||||
fileserver := decorate(http.FileServer(fs))
|
||||
mu := http.NewServeMux()
|
||||
if u.Path == "" || u.Path == "/" {
|
||||
if baseURL.Path() == "" || baseURL.Path() == "/" {
|
||||
mu.Handle("/", fileserver)
|
||||
} else {
|
||||
mu.Handle(u.Path, http.StripPrefix(u.Path, fileserver))
|
||||
mu.Handle(baseURL.Path(), http.StripPrefix(baseURL.Path(), fileserver))
|
||||
}
|
||||
if r.IsTestRun() {
|
||||
var shutDownOnce sync.Once
|
||||
|
@ -401,7 +397,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
|||
|
||||
endpoint := net.JoinHostPort(f.c.serverInterface, strconv.Itoa(port))
|
||||
|
||||
return mu, listener, u.String(), endpoint, nil
|
||||
return mu, listener, baseURL.String(), endpoint, nil
|
||||
}
|
||||
|
||||
func (f *fileServer) rewriteRequest(r *http.Request, toPath string) *http.Request {
|
||||
|
@ -469,7 +465,6 @@ func (c *serverCommand) Name() string {
|
|||
}
|
||||
|
||||
func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
|
||||
// Watch runs its own server as part of the routine
|
||||
if c.serverWatch {
|
||||
|
||||
|
@ -676,7 +671,7 @@ func (c *serverCommand) createCertificates(conf *commonConfig) error {
|
|||
|
||||
// Create the directory if it doesn't exist.
|
||||
if _, err := os.Stat(keyDir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(keyDir, 0777); err != nil {
|
||||
if err := os.MkdirAll(keyDir, 0o777); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -701,7 +696,6 @@ func (c *serverCommand) createCertificates(conf *commonConfig) error {
|
|||
// Yes, this is unfortunate, but it's currently the only way to use Mkcert as a library.
|
||||
os.Args = []string{"-cert-file", c.tlsCertFile, "-key-file", c.tlsKeyFile, hostname}
|
||||
return mclib.RunMain()
|
||||
|
||||
}
|
||||
|
||||
func (c *serverCommand) verifyCert(rootPEM, certPEM []byte, name string) error {
|
||||
|
@ -831,9 +825,9 @@ func (c *serverCommand) partialReRender(urls ...string) error {
|
|||
c.errState.setWasErr(false)
|
||||
}()
|
||||
c.errState.setBuildErr(nil)
|
||||
visited := make(map[string]bool)
|
||||
visited := types.NewEvictingStringQueue(len(urls))
|
||||
for _, url := range urls {
|
||||
visited[url] = true
|
||||
visited.Add(url)
|
||||
}
|
||||
|
||||
h, err := c.hugo()
|
||||
|
@ -846,7 +840,7 @@ func (c *serverCommand) partialReRender(urls ...string) error {
|
|||
|
||||
func (c *serverCommand) serve() error {
|
||||
var (
|
||||
baseURLs []string
|
||||
baseURLs []urls.BaseURL
|
||||
roots []string
|
||||
h *hugolib.HugoSites
|
||||
)
|
||||
|
@ -863,18 +857,17 @@ func (c *serverCommand) serve() error {
|
|||
|
||||
if isMultiHost {
|
||||
for _, l := range conf.configs.ConfigLangs() {
|
||||
baseURLs = append(baseURLs, l.BaseURL().String())
|
||||
baseURLs = append(baseURLs, l.BaseURL())
|
||||
roots = append(roots, l.Language().Lang)
|
||||
}
|
||||
} else {
|
||||
l := conf.configs.GetFirstLanguageConfig()
|
||||
baseURLs = []string{l.BaseURL().String()}
|
||||
baseURLs = []urls.BaseURL{l.BaseURL()}
|
||||
roots = []string{""}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -946,13 +939,9 @@ func (c *serverCommand) serve() error {
|
|||
servers = append(servers, srv)
|
||||
|
||||
if doLiveReload {
|
||||
u, err := url.Parse(helpers.SanitizeURL(baseURLs[i]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mu.HandleFunc(u.Path+"/livereload.js", livereload.ServeJS)
|
||||
mu.HandleFunc(u.Path+"/livereload", livereload.Handler)
|
||||
baseURL := baseURLs[i]
|
||||
mu.HandleFunc(baseURL.Path()+"livereload.js", livereload.ServeJS)
|
||||
mu.HandleFunc(baseURL.Path()+"livereload", livereload.Handler)
|
||||
}
|
||||
c.r.Printf("Web Server is available at %s (bind address %s) %s\n", serverURL, c.serverInterface, roots[i])
|
||||
wg1.Go(func() error {
|
||||
|
@ -971,8 +960,12 @@ func (c *serverCommand) serve() error {
|
|||
if c.r.IsTestRun() {
|
||||
// Write a .ready file to disk to signal ready status.
|
||||
// This is where the test is run from.
|
||||
var baseURLs []string
|
||||
for _, baseURL := range srv.baseURLs {
|
||||
baseURLs = append(baseURLs, baseURL.String())
|
||||
}
|
||||
testInfo := map[string]any{
|
||||
"baseURLs": srv.baseURLs,
|
||||
"baseURLs": baseURLs,
|
||||
}
|
||||
|
||||
dir := os.Getenv("WORK")
|
||||
|
@ -983,7 +976,7 @@ func (c *serverCommand) serve() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(readyFile, b, 0777)
|
||||
err = os.WriteFile(readyFile, b, 0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1167,7 +1160,7 @@ func cleanErrorLog(content string) string {
|
|||
return strings.Join(keep, ": ")
|
||||
}
|
||||
|
||||
func injectLiveReloadScript(src io.Reader, baseURL url.URL) string {
|
||||
func injectLiveReloadScript(src io.Reader, baseURL *url.URL) string {
|
||||
var b bytes.Buffer
|
||||
chain := transform.Chain{livereloadinject.New(baseURL)}
|
||||
chain.Apply(&b, src)
|
||||
|
|
|
@ -20,3 +20,24 @@ const (
|
|||
ErrRemoteGetJSON = "error-remote-getjson"
|
||||
ErrRemoteGetCSV = "error-remote-getcsv"
|
||||
)
|
||||
|
||||
// Field/method names with special meaning.
|
||||
const (
|
||||
FieldRelPermalink = "RelPermalink"
|
||||
FieldPermalink = "Permalink"
|
||||
)
|
||||
|
||||
// IsFieldRelOrPermalink returns whether the given name is a RelPermalink or Permalink.
|
||||
func IsFieldRelOrPermalink(name string) bool {
|
||||
return name == FieldRelPermalink || name == FieldPermalink
|
||||
}
|
||||
|
||||
// Resource transformations.
|
||||
const (
|
||||
ResourceTransformationFingerprint = "fingerprint"
|
||||
)
|
||||
|
||||
// IsResourceTransformationLinkChange returns whether the given name is a resource transformation that changes the permalink based on the content.
|
||||
func IsResourceTransformationPermalinkHash(name string) bool {
|
||||
return name == ResourceTransformationFingerprint
|
||||
}
|
||||
|
|
46
common/hcontext/context.go
Normal file
46
common/hcontext/context.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2024 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 hcontext
|
||||
|
||||
import "context"
|
||||
|
||||
// ContextDispatcher is a generic interface for setting and getting values from a context.
|
||||
type ContextDispatcher[T any] interface {
|
||||
Set(ctx context.Context, value T) context.Context
|
||||
Get(ctx context.Context) T
|
||||
}
|
||||
|
||||
// NewContextDispatcher creates a new ContextDispatcher with the given key.
|
||||
func NewContextDispatcher[T any, R comparable](key R) ContextDispatcher[T] {
|
||||
return keyInContext[T, R]{
|
||||
id: key,
|
||||
}
|
||||
}
|
||||
|
||||
type keyInContext[T any, R comparable] struct {
|
||||
zero T
|
||||
id R
|
||||
}
|
||||
|
||||
func (f keyInContext[T, R]) Get(ctx context.Context) T {
|
||||
v := ctx.Value(f.id)
|
||||
if v == nil {
|
||||
return f.zero
|
||||
}
|
||||
return v.(T)
|
||||
}
|
||||
|
||||
func (f keyInContext[T, R]) Set(ctx context.Context, value T) context.Context {
|
||||
return context.WithValue(ctx, f.id, value)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -74,7 +74,6 @@ func ContainsMatcher(text string) func(m LineMatcher) int {
|
|||
// ErrorContext contains contextual information about an error. This will
|
||||
// typically be the lines surrounding some problem in a file.
|
||||
type ErrorContext struct {
|
||||
|
||||
// If a match will contain the matched line and up to 2 lines before and after.
|
||||
// Will be empty if no match.
|
||||
Lines []string
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -21,6 +21,7 @@ import (
|
|||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PrintStackTrace prints the current stacktrace to w.
|
||||
|
@ -47,6 +48,24 @@ func Recover(args ...any) {
|
|||
}
|
||||
}
|
||||
|
||||
// IsTimeoutError returns true if the given error is or contains a TimeoutError.
|
||||
func IsTimeoutError(err error) bool {
|
||||
return errors.Is(err, &TimeoutError{})
|
||||
}
|
||||
|
||||
type TimeoutError struct {
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
func (e *TimeoutError) Error() string {
|
||||
return fmt.Sprintf("timeout after %s", e.Duration)
|
||||
}
|
||||
|
||||
func (e *TimeoutError) Is(target error) bool {
|
||||
_, ok := target.(*TimeoutError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
|
||||
func IsFeatureNotAvailableError(err error) bool {
|
||||
return errors.Is(err, &FeatureNotAvailableError{})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -42,5 +42,4 @@ func TestIsFeatureNotAvailableError(t *testing.T) {
|
|||
c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true)
|
||||
c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true)
|
||||
c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -15,13 +15,13 @@ package herrors
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
godartsassv1 "github.com/bep/godartsass"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
godartsassv1 "github.com/bep/godartsass"
|
||||
|
||||
"github.com/bep/godartsass/v2"
|
||||
"github.com/bep/golibsass/libsass/libsasserrors"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
@ -29,8 +29,6 @@ import (
|
|||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/tdewolff/parse/v2"
|
||||
|
||||
"errors"
|
||||
)
|
||||
|
||||
// FileError represents an error when handling a file: Parsing a config file,
|
||||
|
@ -48,6 +46,9 @@ type FileError interface {
|
|||
|
||||
// UpdateContent updates the error with a new ErrorContext from the content of the file.
|
||||
UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError
|
||||
|
||||
// SetFilename sets the filename of the error.
|
||||
SetFilename(filename string) FileError
|
||||
}
|
||||
|
||||
// Unwrapper can unwrap errors created with fmt.Errorf.
|
||||
|
@ -60,6 +61,11 @@ var (
|
|||
_ Unwrapper = (*fileError)(nil)
|
||||
)
|
||||
|
||||
func (fe *fileError) SetFilename(filename string) FileError {
|
||||
fe.position.Filename = filename
|
||||
return fe
|
||||
}
|
||||
|
||||
func (fe *fileError) UpdatePosition(pos text.Position) FileError {
|
||||
oldFilename := fe.Position().Filename
|
||||
if pos.Filename != "" && fe.fileType == "" {
|
||||
|
@ -115,7 +121,6 @@ func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileE
|
|||
}
|
||||
|
||||
return fe
|
||||
|
||||
}
|
||||
|
||||
type fileError struct {
|
||||
|
@ -181,7 +186,6 @@ func NewFileErrorFromName(err error, name string) FileError {
|
|||
}
|
||||
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
|
||||
}
|
||||
|
||||
// NewFileErrorFromPos will use the filename and line number from pos to create a new FileError, wrapping err.
|
||||
|
@ -192,7 +196,6 @@ func NewFileErrorFromPos(err error, pos text.Position) FileError {
|
|||
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename))
|
||||
}
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
|
||||
}
|
||||
|
||||
func NewFileErrorFromFileInErr(err error, fs afero.Fs, linematcher LineMatcherFn) FileError {
|
||||
|
@ -249,7 +252,6 @@ func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
|
|||
}); ok {
|
||||
realFilename = s.Filename()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
f, err2 := fs.Open(filename)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -14,12 +14,11 @@
|
|||
package herrors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
@ -48,7 +47,6 @@ func TestNewFileError(t *testing.T) {
|
|||
c.Assert(errorContext.Lines, qt.DeepEquals, []string{"line 30", "line 31", "line 32", "line 33", "line 34"})
|
||||
c.Assert(errorContext.LinesPos, qt.Equals, 2)
|
||||
c.Assert(errorContext.ChromaLexer, qt.Equals, "go-html-template")
|
||||
|
||||
}
|
||||
|
||||
func TestNewFileErrorExtractFromMessage(t *testing.T) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
|
@ -23,6 +23,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
)
|
||||
|
||||
|
@ -188,6 +189,20 @@ func IsTime(tp reflect.Type) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IsValid returns whether v is not nil and a valid value.
|
||||
func IsValid(v reflect.Value) bool {
|
||||
if !v.IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return !v.IsNil()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AsTime returns v as a time.Time if possible.
|
||||
// The given location is only used if the value implements AsTimeProvider (e.g. go-toml local).
|
||||
// A zero Time and false is returned if this isn't possible.
|
||||
|
@ -217,7 +232,7 @@ func CallMethodByName(cxt context.Context, name string, v reflect.Value) []refle
|
|||
panic("not supported")
|
||||
}
|
||||
first := tp.In(0)
|
||||
if first.Implements(ContextInterface) {
|
||||
if IsContextType(first) {
|
||||
args = append(args, reflect.ValueOf(cxt))
|
||||
}
|
||||
}
|
||||
|
@ -236,4 +251,24 @@ func indirectInterface(v reflect.Value) reflect.Value {
|
|||
return v.Elem()
|
||||
}
|
||||
|
||||
var ContextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
var contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
|
||||
var isContextCache = maps.NewCache[reflect.Type, bool]()
|
||||
|
||||
type k string
|
||||
|
||||
var contextTypeValue = reflect.TypeOf(context.WithValue(context.Background(), k("key"), 32))
|
||||
|
||||
// IsContextType returns whether tp is a context.Context type.
|
||||
func IsContextType(tp reflect.Type) bool {
|
||||
if tp == contextTypeValue {
|
||||
return true
|
||||
}
|
||||
if tp == contextInterface {
|
||||
return true
|
||||
}
|
||||
|
||||
return isContextCache.GetOrCreate(tp, func() bool {
|
||||
return tp.Implements(contextInterface)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package hreflect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -40,6 +41,42 @@ func TestGetMethodByName(t *testing.T) {
|
|||
c.Assert(GetMethodIndexByName(tp, "Foo"), qt.Equals, -1)
|
||||
}
|
||||
|
||||
func TestIsContextType(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
type k string
|
||||
ctx := context.Background()
|
||||
valueCtx := context.WithValue(ctx, k("key"), 32)
|
||||
c.Assert(IsContextType(reflect.TypeOf(ctx)), qt.IsTrue)
|
||||
c.Assert(IsContextType(reflect.TypeOf(valueCtx)), qt.IsTrue)
|
||||
}
|
||||
|
||||
func BenchmarkIsContextType(b *testing.B) {
|
||||
type k string
|
||||
b.Run("value", func(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
ctxs := make([]reflect.Type, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctxs[i] = reflect.TypeOf(context.WithValue(ctx, k("key"), i))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if !IsContextType(ctxs[i]) {
|
||||
b.Fatal("not context")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("background", func(b *testing.B) {
|
||||
var ctxt reflect.Type = reflect.TypeOf(context.Background())
|
||||
for i := 0; i < b.N; i++ {
|
||||
if !IsContextType(ctxt) {
|
||||
b.Fatal("not context")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIsTruthFul(b *testing.B) {
|
||||
v := reflect.ValueOf("Hugo")
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -122,3 +122,8 @@ func InSlicEqualFold(arr []string, el string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Tuple struct {
|
||||
First string
|
||||
Second string
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -33,7 +33,6 @@ func TestStringEqualFold(t *testing.T) {
|
|||
c.Assert(StringEqualFold(s1).EqualFold("b"), qt.Equals, false)
|
||||
c.Assert(StringEqualFold(s1).Eq(s2), qt.Equals, true)
|
||||
c.Assert(StringEqualFold(s1).Eq("b"), qt.Equals, false)
|
||||
|
||||
}
|
||||
|
||||
func TestGetOrCompileRegexp(t *testing.T) {
|
||||
|
@ -42,7 +41,6 @@ func TestGetOrCompileRegexp(t *testing.T) {
|
|||
re, err := GetOrCompileRegexp(`\d+`)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(re.MatchString("123"), qt.Equals, true)
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkGetOrCompileRegexp(b *testing.B) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -16,6 +16,7 @@ package hugio
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
@ -60,12 +61,16 @@ func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool
|
|||
return fmt.Errorf("%q is not a directory", from)
|
||||
}
|
||||
|
||||
err = fs.MkdirAll(to, 0777) // before umask
|
||||
err = fs.MkdirAll(to, 0o777) // before umask
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries, _ := afero.ReadDir(fs, from)
|
||||
d, err := fs.Open(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entries, _ := d.(iofs.ReadDirFile).ReadDir(-1)
|
||||
for _, entry := range entries {
|
||||
fromFilename := filepath.Join(from, entry.Name())
|
||||
toFilename := filepath.Join(to, entry.Name())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package hugio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
@ -57,3 +58,22 @@ func NewReadSeekerNoOpCloser(r ReadSeeker) ReadSeekerNoOpCloser {
|
|||
func NewReadSeekerNoOpCloserFromString(content string) ReadSeekerNoOpCloser {
|
||||
return ReadSeekerNoOpCloser{strings.NewReader(content)}
|
||||
}
|
||||
|
||||
// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
|
||||
// from the given bytes slice.
|
||||
func NewReadSeekerNoOpCloserFromBytes(content []byte) ReadSeekerNoOpCloser {
|
||||
return ReadSeekerNoOpCloser{bytes.NewReader(content)}
|
||||
}
|
||||
|
||||
// NewReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
|
||||
// The ReadSeeker will be seeked to the beginning before returned.
|
||||
func NewOpenReadSeekCloser(r ReadSeekCloser) OpenReadSeekCloser {
|
||||
return func() (ReadSeekCloser, error) {
|
||||
r.Seek(0, io.SeekStart)
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
// OpenReadSeekCloser allows setting some other way (than reading from a filesystem)
|
||||
// to open or create a ReadSeekCloser.
|
||||
type OpenReadSeekCloser func() (ReadSeekCloser, error)
|
||||
|
|
|
@ -35,6 +35,8 @@ import (
|
|||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
iofs "io/fs"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
)
|
||||
|
@ -159,7 +161,12 @@ func GetExecEnviron(workDir string, cfg config.AllProvider, fs afero.Fs) []strin
|
|||
config.SetEnvVars(&env, "HUGO_PUBLISHDIR", filepath.Join(workDir, cfg.BaseConfig().PublishDir))
|
||||
|
||||
if fs != nil {
|
||||
fis, err := afero.ReadDir(fs, files.FolderJSConfig)
|
||||
var fis []iofs.DirEntry
|
||||
d, err := fs.Open(files.FolderJSConfig)
|
||||
if err == nil {
|
||||
fis, err = d.(iofs.ReadDirFile).ReadDir(-1)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
for _, fi := range fis {
|
||||
key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_"))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
|
@ -27,10 +27,9 @@ import (
|
|||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var bold = color.New(color.Bold)
|
||||
|
||||
// levelColor mapping.
|
||||
var levelColor = [...]*color.Color{
|
||||
logg.LevelTrace: color.New(color.FgWhite),
|
||||
logg.LevelDebug: color.New(color.FgWhite),
|
||||
logg.LevelInfo: color.New(color.FgBlue),
|
||||
logg.LevelWarn: color.New(color.FgYellow),
|
||||
|
@ -39,6 +38,7 @@ var levelColor = [...]*color.Color{
|
|||
|
||||
// levelString mapping.
|
||||
var levelString = [...]string{
|
||||
logg.LevelTrace: "TRACE",
|
||||
logg.LevelDebug: "DEBUG",
|
||||
logg.LevelInfo: "INFO ",
|
||||
logg.LevelWarn: "WARN ",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
|
@ -69,7 +69,7 @@ func (h *logLevelCounter) HandleLog(e *logg.Entry) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var stopError = fmt.Errorf("stop")
|
||||
var errStop = fmt.Errorf("stop")
|
||||
|
||||
type logOnceHandler struct {
|
||||
threshold logg.Level
|
||||
|
@ -87,7 +87,7 @@ func (h *logOnceHandler) HandleLog(e *logg.Entry) error {
|
|||
defer h.mu.Unlock()
|
||||
hash := identity.HashUint64(e.Level, e.Message, e.Fields)
|
||||
if h.seen[hash] {
|
||||
return stopError
|
||||
return errStop
|
||||
}
|
||||
h.seen[hash] = true
|
||||
return nil
|
||||
|
@ -107,7 +107,7 @@ type stopHandler struct {
|
|||
func (h *stopHandler) HandleLog(e *logg.Entry) error {
|
||||
for _, handler := range h.handlers {
|
||||
if err := handler.HandleLog(e); err != nil {
|
||||
if err == stopError {
|
||||
if err == errStop {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
|
@ -124,26 +124,13 @@ func (h *suppressStatementsHandler) HandleLog(e *logg.Entry) error {
|
|||
for _, field := range e.Fields {
|
||||
if field.Name == FieldNameStatementID {
|
||||
if h.statements[field.Value.(string)] {
|
||||
return stopError
|
||||
return errStop
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// replacer creates a new log handler that does string replacement in log messages.
|
||||
func replacer(repl *strings.Replacer) logg.Handler {
|
||||
return logg.HandlerFunc(func(e *logg.Entry) error {
|
||||
e.Message = repl.Replace(e.Message)
|
||||
for i, field := range e.Fields {
|
||||
if s, ok := field.Value.(string); ok {
|
||||
e.Fields[i].Value = repl.Replace(s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// whiteSpaceTrimmer creates a new log handler that trims whitespace from log messages and string fields.
|
||||
func whiteSpaceTrimmer() logg.Handler {
|
||||
return logg.HandlerFunc(func(e *logg.Entry) error {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
|
@ -81,7 +81,7 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
|
|||
if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, " %s %q", field.Name, field.Value)
|
||||
fmt.Fprintf(w, " %s %v", field.Name, field.Value)
|
||||
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
|
@ -68,11 +68,24 @@ func New(opts Options) Logger {
|
|||
errorsw := &strings.Builder{}
|
||||
logCounters := newLogLevelCounter()
|
||||
handlers := []logg.Handler{
|
||||
whiteSpaceTrimmer(),
|
||||
logHandler,
|
||||
logCounters,
|
||||
}
|
||||
|
||||
if opts.Level == logg.LevelTrace {
|
||||
// Trace is used during development only, and it's useful to
|
||||
// only see the trace messages.
|
||||
handlers = append(handlers,
|
||||
logg.HandlerFunc(func(e *logg.Entry) error {
|
||||
if e.Level != logg.LevelTrace {
|
||||
return logg.ErrStopLogEntry
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
handlers = append(handlers, whiteSpaceTrimmer(), logHandler)
|
||||
|
||||
if opts.HandlerPost != nil {
|
||||
var hookHandler logg.HandlerFunc = func(e *logg.Entry) error {
|
||||
opts.HandlerPost(e)
|
||||
|
@ -127,6 +140,7 @@ func New(opts Options) Logger {
|
|||
out: opts.Stdout,
|
||||
level: opts.Level,
|
||||
logger: logger,
|
||||
tracel: l.WithLevel(logg.LevelTrace),
|
||||
debugl: l.WithLevel(logg.LevelDebug),
|
||||
infol: l.WithLevel(logg.LevelInfo),
|
||||
warnl: l.WithLevel(logg.LevelWarn),
|
||||
|
@ -145,11 +159,22 @@ func NewDefault() Logger {
|
|||
return New(opts)
|
||||
}
|
||||
|
||||
func NewTrace() Logger {
|
||||
opts := Options{
|
||||
DistinctLevel: logg.LevelWarn,
|
||||
Level: logg.LevelTrace,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stdout,
|
||||
}
|
||||
return New(opts)
|
||||
}
|
||||
|
||||
func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
|
||||
return logWriter{l: l}
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Debug() logg.LevelLogger
|
||||
Debugf(format string, v ...any)
|
||||
Debugln(v ...any)
|
||||
Error() logg.LevelLogger
|
||||
|
@ -174,6 +199,7 @@ type Logger interface {
|
|||
Warnf(format string, v ...any)
|
||||
Warnln(v ...any)
|
||||
Deprecatef(fail bool, format string, v ...any)
|
||||
Trace(s logg.StringFunc)
|
||||
}
|
||||
|
||||
type logAdapter struct {
|
||||
|
@ -183,12 +209,17 @@ type logAdapter struct {
|
|||
out io.Writer
|
||||
level logg.Level
|
||||
logger logg.Logger
|
||||
tracel logg.LevelLogger
|
||||
debugl logg.LevelLogger
|
||||
infol logg.LevelLogger
|
||||
warnl logg.LevelLogger
|
||||
errorl logg.LevelLogger
|
||||
}
|
||||
|
||||
func (l *logAdapter) Debug() logg.LevelLogger {
|
||||
return l.debugl
|
||||
}
|
||||
|
||||
func (l *logAdapter) Debugf(format string, v ...any) {
|
||||
l.debugl.Logf(format, v...)
|
||||
}
|
||||
|
@ -294,6 +325,10 @@ func (l *logAdapter) Errorsf(id, format string, v ...any) {
|
|||
l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Trace(s logg.StringFunc) {
|
||||
l.tracel.Log(s)
|
||||
}
|
||||
|
||||
func (l *logAdapter) sprint(v ...any) string {
|
||||
return strings.TrimRight(fmt.Sprintln(v...), "\n")
|
||||
}
|
||||
|
@ -315,3 +350,19 @@ func (w logWriter) Write(p []byte) (n int, err error) {
|
|||
w.l.Log(logg.String(string(p)))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TimeTrackf(l logg.LevelLogger, start time.Time, fields logg.Fields, format string, a ...any) {
|
||||
elapsed := time.Since(start)
|
||||
if fields != nil {
|
||||
l = l.WithFields(fields)
|
||||
}
|
||||
l.WithField("duration", elapsed).Logf(format, a...)
|
||||
}
|
||||
|
||||
func TimeTrackfn(fn func() (logg.LevelLogger, error)) error {
|
||||
start := time.Now()
|
||||
l, err := fn()
|
||||
elapsed := time.Since(start)
|
||||
l.WithField("duration", elapsed).Logf("")
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
|
|
90
common/maps/cache.go
Normal file
90
common/maps/cache.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2024 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 maps
|
||||
|
||||
import "sync"
|
||||
|
||||
// Cache is a simple thread safe cache backed by a map.
|
||||
type Cache[K comparable, T any] struct {
|
||||
m map[K]T
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCache creates a new Cache.
|
||||
func NewCache[K comparable, T any]() *Cache[K, T] {
|
||||
return &Cache[K, T]{m: make(map[K]T)}
|
||||
}
|
||||
|
||||
// Delete deletes the given key from the cache.
|
||||
func (c *Cache[K, T]) Get(key K) (T, bool) {
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
c.RUnlock()
|
||||
return v, found
|
||||
}
|
||||
|
||||
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
|
||||
func (c *Cache[K, T]) GetOrCreate(key K, create func() T) T {
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
c.RUnlock()
|
||||
if found {
|
||||
return v
|
||||
}
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
v, found = c.m[key]
|
||||
if found {
|
||||
return v
|
||||
}
|
||||
v = create()
|
||||
c.m[key] = v
|
||||
return v
|
||||
}
|
||||
|
||||
// Set sets the given key to the given value.
|
||||
func (c *Cache[K, T]) Set(key K, value T) {
|
||||
c.Lock()
|
||||
c.m[key] = value
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// SliceCache is a simple thread safe cache backed by a map.
|
||||
type SliceCache[T any] struct {
|
||||
m map[string][]T
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewSliceCache[T any]() *SliceCache[T] {
|
||||
return &SliceCache[T]{m: make(map[string][]T)}
|
||||
}
|
||||
|
||||
func (c *SliceCache[T]) Get(key string) ([]T, bool) {
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
c.RUnlock()
|
||||
return v, found
|
||||
}
|
||||
|
||||
func (c *SliceCache[T]) Append(key string, values ...T) {
|
||||
c.Lock()
|
||||
c.m[key] = append(c.m[key], values...)
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
func (c *SliceCache[T]) Reset() {
|
||||
c.Lock()
|
||||
c.m = make(map[string][]T)
|
||||
c.Unlock()
|
||||
}
|
|
@ -29,7 +29,7 @@ func ToStringMapE(in any) (map[string]any, error) {
|
|||
case Params:
|
||||
return vv, nil
|
||||
case map[string]string:
|
||||
var m = map[string]any{}
|
||||
m := map[string]any{}
|
||||
for k, v := range vv {
|
||||
m[k] = v
|
||||
}
|
||||
|
@ -192,21 +192,20 @@ func (KeyRenamer) keyPath(k1, k2 string) string {
|
|||
}
|
||||
|
||||
func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
|
||||
for key, val := range m {
|
||||
keyPath := r.keyPath(parentKeyPath, key)
|
||||
switch val.(type) {
|
||||
for k, v := range m {
|
||||
keyPath := r.keyPath(parentKeyPath, k)
|
||||
switch vv := v.(type) {
|
||||
case map[any]any:
|
||||
val = cast.ToStringMap(val)
|
||||
r.renamePath(keyPath, val.(map[string]any))
|
||||
r.renamePath(keyPath, cast.ToStringMap(vv))
|
||||
case map[string]any:
|
||||
r.renamePath(keyPath, val.(map[string]any))
|
||||
r.renamePath(keyPath, vv)
|
||||
}
|
||||
|
||||
newKey := r.getNewKey(keyPath)
|
||||
|
||||
if newKey != "" {
|
||||
delete(m, key)
|
||||
m[newKey] = val
|
||||
delete(m, k)
|
||||
m[newKey] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func SetParams(dst, src Params) {
|
|||
|
||||
// IsZero returns true if p is considered empty.
|
||||
func (p Params) IsZero() bool {
|
||||
if p == nil || len(p) == 0 {
|
||||
if len(p) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,6 @@ func (p Params) IsZero() bool {
|
|||
}
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
|
||||
|
@ -93,7 +92,7 @@ func MergeParams(dst, src Params) {
|
|||
func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
|
||||
ns, found := p.GetMergeStrategy()
|
||||
|
||||
var ms = ns
|
||||
ms := ns
|
||||
if !found && ps != "" {
|
||||
ms = ps
|
||||
}
|
||||
|
@ -248,7 +247,7 @@ const (
|
|||
// CleanConfigStringMapString removes any processing instructions from m,
|
||||
// m will never be modified.
|
||||
func CleanConfigStringMapString(m map[string]string) map[string]string {
|
||||
if m == nil || len(m) == 0 {
|
||||
if len(m) == 0 {
|
||||
return m
|
||||
}
|
||||
if _, found := m[MergeStrategyKey]; !found {
|
||||
|
@ -267,7 +266,7 @@ func CleanConfigStringMapString(m map[string]string) map[string]string {
|
|||
// CleanConfigStringMap is the same as CleanConfigStringMapString but for
|
||||
// map[string]any.
|
||||
func CleanConfigStringMap(m map[string]any) map[string]any {
|
||||
if m == nil || len(m) == 0 {
|
||||
if len(m) == 0 {
|
||||
return m
|
||||
}
|
||||
if _, found := m[MergeStrategyKey]; !found {
|
||||
|
@ -291,7 +290,6 @@ func CleanConfigStringMap(m map[string]any) map[string]any {
|
|||
|
||||
}
|
||||
return m2
|
||||
|
||||
}
|
||||
|
||||
func toMergeStrategy(v any) ParamsMergeStrategy {
|
||||
|
|
|
@ -16,14 +16,18 @@ package paths
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// FilePathSeparator as defined by os.Separator.
|
||||
const FilePathSeparator = string(filepath.Separator)
|
||||
const (
|
||||
FilePathSeparator = string(filepath.Separator)
|
||||
slash = "/"
|
||||
)
|
||||
|
||||
// filepathPathBridge is a bridge for common functionality in filepath vs path
|
||||
type filepathPathBridge interface {
|
||||
|
@ -72,6 +76,30 @@ func AbsPathify(workingDir, inPath string) string {
|
|||
return filepath.Join(workingDir, inPath)
|
||||
}
|
||||
|
||||
// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
|
||||
// there.
|
||||
func AddTrailingSlash(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// AddLeadingSlash adds a leading Unix styled slash (/) if not already
|
||||
// there.
|
||||
func AddLeadingSlash(path string) string {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// AddTrailingAndLeadingSlash adds a leading and trailing Unix styled slash (/) if not already
|
||||
// there.
|
||||
func AddLeadingAndTrailingSlash(path string) string {
|
||||
return AddTrailingSlash(AddLeadingSlash(path))
|
||||
}
|
||||
|
||||
// MakeTitle converts the path given to a suitable title, trimming whitespace
|
||||
// and replacing hyphens with whitespace.
|
||||
func MakeTitle(inpath string) string {
|
||||
|
@ -94,43 +122,6 @@ func makePathRelative(inPath string, possibleDirectories ...string) (string, err
|
|||
return inPath, errors.New("can't extract relative path, unknown prefix")
|
||||
}
|
||||
|
||||
// Should be good enough for Hugo.
|
||||
var isFileRe = regexp.MustCompile(`.*\..{1,6}$`)
|
||||
|
||||
// GetDottedRelativePath expects a relative path starting after the content directory.
|
||||
// It returns a relative path with dots ("..") navigating up the path structure.
|
||||
func GetDottedRelativePath(inPath string) string {
|
||||
inPath = path.Clean(filepath.ToSlash(inPath))
|
||||
|
||||
if inPath == "." {
|
||||
return "./"
|
||||
}
|
||||
|
||||
if !isFileRe.MatchString(inPath) && !strings.HasSuffix(inPath, "/") {
|
||||
inPath += "/"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(inPath, "/") {
|
||||
inPath = "/" + inPath
|
||||
}
|
||||
|
||||
dir, _ := filepath.Split(inPath)
|
||||
|
||||
sectionCount := strings.Count(dir, "/")
|
||||
|
||||
if sectionCount == 0 || dir == "/" {
|
||||
return "./"
|
||||
}
|
||||
|
||||
var dottedPath string
|
||||
|
||||
for i := 1; i < sectionCount; i++ {
|
||||
dottedPath += "../"
|
||||
}
|
||||
|
||||
return dottedPath
|
||||
}
|
||||
|
||||
// ExtNoDelimiter takes a path and returns the extension, excluding the delimiter, i.e. "md".
|
||||
func ExtNoDelimiter(in string) string {
|
||||
return strings.TrimPrefix(Ext(in), ".")
|
||||
|
@ -167,12 +158,6 @@ func Filename(in string) (name string) {
|
|||
return
|
||||
}
|
||||
|
||||
// PathNoExt takes a path, strips out the extension,
|
||||
// and returns the name of the file.
|
||||
func PathNoExt(in string) string {
|
||||
return strings.TrimSuffix(in, path.Ext(in))
|
||||
}
|
||||
|
||||
// FileAndExt returns the filename and any extension of a file path as
|
||||
// two separate strings.
|
||||
//
|
||||
|
@ -252,16 +237,125 @@ func prettifyPath(in string, b filepathPathBridge) string {
|
|||
return b.Join(b.Dir(in), name, "index"+ext)
|
||||
}
|
||||
|
||||
type NamedSlice struct {
|
||||
Name string
|
||||
Slice []string
|
||||
// CommonDir returns the common directory of the given paths.
|
||||
func CommonDir(path1, path2 string) string {
|
||||
if path1 == "" || path2 == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
p1 := strings.Split(path1, "/")
|
||||
p2 := strings.Split(path2, "/")
|
||||
|
||||
var common []string
|
||||
|
||||
for i := 0; i < len(p1) && i < len(p2); i++ {
|
||||
if p1[i] == p2[i] {
|
||||
common = append(common, p1[i])
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(common, "/")
|
||||
}
|
||||
|
||||
func (n NamedSlice) String() string {
|
||||
if len(n.Slice) == 0 {
|
||||
return n.Name
|
||||
// Sanitize sanitizes string to be used in Hugo's file paths and URLs, allowing only
|
||||
// a predefined set of special Unicode characters.
|
||||
//
|
||||
// Spaces will be replaced with a single hyphen.
|
||||
//
|
||||
// This function is the core function used to normalize paths in Hugo.
|
||||
//
|
||||
// Note that this is the first common step for URL/path sanitation,
|
||||
// the final URL/path may end up looking differently if the user has stricter rules defined (e.g. removePathAccents=true).
|
||||
func Sanitize(s string) string {
|
||||
var willChange bool
|
||||
for i, r := range s {
|
||||
willChange = !isAllowedPathCharacter(s, i, r)
|
||||
if willChange {
|
||||
break
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
|
||||
|
||||
if !willChange {
|
||||
// Prevent allocation when nothing changes.
|
||||
return s
|
||||
}
|
||||
|
||||
target := make([]rune, 0, len(s))
|
||||
var (
|
||||
prependHyphen bool
|
||||
wasHyphen bool
|
||||
)
|
||||
|
||||
for i, r := range s {
|
||||
isAllowed := isAllowedPathCharacter(s, i, r)
|
||||
|
||||
if isAllowed {
|
||||
// track explicit hyphen in input; no need to add a new hyphen if
|
||||
// we just saw one.
|
||||
wasHyphen = r == '-'
|
||||
|
||||
if prependHyphen {
|
||||
// if currently have a hyphen, don't prepend an extra one
|
||||
if !wasHyphen {
|
||||
target = append(target, '-')
|
||||
}
|
||||
prependHyphen = false
|
||||
}
|
||||
target = append(target, r)
|
||||
} else if len(target) > 0 && !wasHyphen && unicode.IsSpace(r) {
|
||||
prependHyphen = true
|
||||
}
|
||||
}
|
||||
|
||||
return string(target)
|
||||
}
|
||||
|
||||
func isAllowedPathCharacter(s string, i int, r rune) bool {
|
||||
if r == ' ' {
|
||||
return false
|
||||
}
|
||||
// Check for the most likely first (faster).
|
||||
isAllowed := unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
isAllowed = isAllowed || r == '.' || r == '/' || r == '\\' || r == '_' || r == '#' || r == '+' || r == '~' || r == '-' || r == '@'
|
||||
isAllowed = isAllowed || unicode.IsMark(r)
|
||||
isAllowed = isAllowed || (r == '%' && i+2 < len(s) && ishex(s[i+1]) && ishex(s[i+2]))
|
||||
return isAllowed
|
||||
}
|
||||
|
||||
// From https://golang.org/src/net/url/url.go
|
||||
func ishex(c byte) bool {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var slashFunc = func(r rune) bool {
|
||||
return r == '/'
|
||||
}
|
||||
|
||||
// Dir behaves like path.Dir without the path.Clean step.
|
||||
//
|
||||
// The returned path ends in a slash only if it is the root "/".
|
||||
func Dir(s string) string {
|
||||
dir, _ := path.Split(s)
|
||||
if len(dir) > 1 && dir[len(dir)-1] == '/' {
|
||||
return dir[:len(dir)-1]
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// FieldsSlash cuts s into fields separated with '/'.
|
||||
func FieldsSlash(s string) []string {
|
||||
f := strings.FieldsFunc(s, slashFunc)
|
||||
return f
|
||||
}
|
||||
|
||||
// DirFile holds the result from path.Split.
|
||||
|
@ -274,3 +368,27 @@ type DirFile struct {
|
|||
func (df DirFile) String() string {
|
||||
return fmt.Sprintf("%s|%s", df.Dir, df.File)
|
||||
}
|
||||
|
||||
// PathEscape escapes unicode letters in pth.
|
||||
// Use URLEscape to escape full URLs including scheme, query etc.
|
||||
// This is slightly faster for the common case.
|
||||
// Note, there is a url.PathEscape function, but that also
|
||||
// escapes /.
|
||||
func PathEscape(pth string) string {
|
||||
u, err := url.Parse(pth)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u.EscapedPath()
|
||||
}
|
||||
|
||||
// ToSlashTrimLeading is just a filepath.ToSlash with an added / prefix trimmer.
|
||||
func ToSlashTrimLeading(s string) string {
|
||||
return strings.TrimPrefix(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
||||
// ToSlashPreserveLeading converts the path given to a forward slash separated path
|
||||
// and preserves the leading slash if present trimming any trailing slash.
|
||||
func ToSlashPreserveLeading(s string) string {
|
||||
return "/" + strings.Trim(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -75,44 +75,6 @@ func TestMakePathRelative(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetDottedRelativePath(t *testing.T) {
|
||||
// on Windows this will receive both kinds, both country and western ...
|
||||
for _, f := range []func(string) string{filepath.FromSlash, func(s string) string { return s }} {
|
||||
doTestGetDottedRelativePath(f, t)
|
||||
}
|
||||
}
|
||||
|
||||
func doTestGetDottedRelativePath(urlFixer func(string) string, t *testing.T) {
|
||||
type test struct {
|
||||
input, expected string
|
||||
}
|
||||
data := []test{
|
||||
{"", "./"},
|
||||
{urlFixer("/"), "./"},
|
||||
{urlFixer("post"), "../"},
|
||||
{urlFixer("/post"), "../"},
|
||||
{urlFixer("post/"), "../"},
|
||||
{urlFixer("tags/foo.html"), "../"},
|
||||
{urlFixer("/tags/foo.html"), "../"},
|
||||
{urlFixer("/post/"), "../"},
|
||||
{urlFixer("////post/////"), "../"},
|
||||
{urlFixer("/foo/bar/index.html"), "../../"},
|
||||
{urlFixer("/foo/bar/foo/"), "../../../"},
|
||||
{urlFixer("/foo/bar/foo"), "../../../"},
|
||||
{urlFixer("foo/bar/foo/"), "../../../"},
|
||||
{urlFixer("foo/bar/foo/bar"), "../../../../"},
|
||||
{"404.html", "./"},
|
||||
{"404.xml", "./"},
|
||||
{"/404.html", "./"},
|
||||
}
|
||||
for i, d := range data {
|
||||
output := GetDottedRelativePath(d.input)
|
||||
if d.expected != output {
|
||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeTitle(t *testing.T) {
|
||||
type test struct {
|
||||
input, expected string
|
||||
|
@ -226,3 +188,77 @@ func TestFileAndExt(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitize(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{" Foo bar ", "Foo-bar"},
|
||||
{"Foo.Bar/foo_Bar-Foo", "Foo.Bar/foo_Bar-Foo"},
|
||||
{"fOO,bar:foobAR", "fOObarfoobAR"},
|
||||
{"FOo/BaR.html", "FOo/BaR.html"},
|
||||
{"FOo/Ba---R.html", "FOo/Ba---R.html"}, /// See #10104
|
||||
{"FOo/Ba R.html", "FOo/Ba-R.html"},
|
||||
{"трям/трям", "трям/трям"},
|
||||
{"은행", "은행"},
|
||||
{"Банковский кассир", "Банковский-кассир"},
|
||||
// Issue #1488
|
||||
{"संस्कृत", "संस्कृत"},
|
||||
{"a%C3%B1ame", "a%C3%B1ame"}, // Issue #1292
|
||||
{"this+is+a+test", "this+is+a+test"}, // Issue #1290
|
||||
{"~foo", "~foo"}, // Issue #2177
|
||||
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
c.Assert(Sanitize(test.input), qt.Equals, test.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSanitize(b *testing.B) {
|
||||
const (
|
||||
allAlowedPath = "foo/bar"
|
||||
spacePath = "foo bar"
|
||||
)
|
||||
|
||||
// This should not allocate any memory.
|
||||
b.Run("All allowed", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
got := Sanitize(allAlowedPath)
|
||||
if got != allAlowedPath {
|
||||
b.Fatal(got)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// This will allocate some memory.
|
||||
b.Run("Spaces", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
got := Sanitize(spacePath)
|
||||
if got != "foo-bar" {
|
||||
b.Fatal(got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDir(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
c.Assert(Dir("/a/b/c/d"), qt.Equals, "/a/b/c")
|
||||
c.Assert(Dir("/a"), qt.Equals, "/")
|
||||
c.Assert(Dir("/"), qt.Equals, "/")
|
||||
c.Assert(Dir(""), qt.Equals, "")
|
||||
}
|
||||
|
||||
func TestFieldsSlash(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(FieldsSlash("a/b/c"), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(FieldsSlash("/a/b/c"), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(FieldsSlash("/a/b/c/"), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(FieldsSlash("a/b/c/"), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(FieldsSlash("/"), qt.DeepEquals, []string{})
|
||||
c.Assert(FieldsSlash(""), qt.DeepEquals, []string{})
|
||||
}
|
||||
|
|
494
common/paths/pathparser.go
Normal file
494
common/paths/pathparser.go
Normal file
|
@ -0,0 +1,494 @@
|
|||
// Copyright 2024 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 paths
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
)
|
||||
|
||||
var defaultPathParser PathParser
|
||||
|
||||
// PathParser parses a path into a Path.
|
||||
type PathParser struct {
|
||||
// Maps the language code to its index in the languages/sites slice.
|
||||
LanguageIndex map[string]int
|
||||
}
|
||||
|
||||
// Parse parses component c with path s into Path using the default path parser.
|
||||
func Parse(c, s string) *Path {
|
||||
return defaultPathParser.Parse(c, s)
|
||||
}
|
||||
|
||||
// NormalizePathString returns a normalized path string using the very basic Hugo rules.
|
||||
func NormalizePathStringBasic(s string) string {
|
||||
// All lower case.
|
||||
s = strings.ToLower(s)
|
||||
|
||||
// Replace spaces with hyphens.
|
||||
s = strings.ReplaceAll(s, " ", "-")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Parse parses component c with path s into Path using Hugo's content path rules.
|
||||
func (parser PathParser) Parse(c, s string) *Path {
|
||||
p, err := parser.parse(c, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
ss := NormalizePathStringBasic(s)
|
||||
|
||||
p, err := pp.doParse(component, ss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s != ss {
|
||||
var err error
|
||||
// Preserve the original case for titles etc.
|
||||
p.unnormalized, err = pp.doParse(component, s)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
p.unnormalized = p
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (pp *PathParser) doParse(component, s string) (*Path, error) {
|
||||
p := &Path{
|
||||
component: component,
|
||||
posContainerLow: -1,
|
||||
posContainerHigh: -1,
|
||||
posSectionHigh: -1,
|
||||
posIdentifierLanguage: -1,
|
||||
}
|
||||
|
||||
hasLang := pp.LanguageIndex != nil
|
||||
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
s = path.Clean(filepath.ToSlash(s))
|
||||
if s == "." {
|
||||
s = ""
|
||||
}
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
s = "/"
|
||||
}
|
||||
|
||||
// Leading slash, no trailing slash.
|
||||
if !strings.HasPrefix(s, "/") {
|
||||
s = "/" + s
|
||||
}
|
||||
|
||||
if s != "/" && s[len(s)-1] == '/' {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
|
||||
p.s = s
|
||||
slashCount := 0
|
||||
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
c := s[i]
|
||||
|
||||
switch c {
|
||||
case '.':
|
||||
if p.posContainerHigh == -1 {
|
||||
var high int
|
||||
if len(p.identifiers) > 0 {
|
||||
high = p.identifiers[len(p.identifiers)-1].Low - 1
|
||||
} else {
|
||||
high = len(p.s)
|
||||
}
|
||||
id := types.LowHigh{Low: i + 1, High: high}
|
||||
if len(p.identifiers) == 0 {
|
||||
p.identifiers = append(p.identifiers, id)
|
||||
} else if len(p.identifiers) == 1 {
|
||||
// Check for a valid language.
|
||||
s := p.s[id.Low:id.High]
|
||||
|
||||
if hasLang {
|
||||
if _, found := pp.LanguageIndex[s]; found {
|
||||
p.posIdentifierLanguage = 1
|
||||
p.identifiers = append(p.identifiers, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case '/':
|
||||
slashCount++
|
||||
if p.posContainerHigh == -1 {
|
||||
p.posContainerHigh = i + 1
|
||||
} else if p.posContainerLow == -1 {
|
||||
p.posContainerLow = i + 1
|
||||
}
|
||||
if i > 0 {
|
||||
p.posSectionHigh = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && files.IsContentExt(p.Ext())
|
||||
|
||||
if isContent {
|
||||
id := p.identifiers[len(p.identifiers)-1]
|
||||
b := p.s[p.posContainerHigh : id.Low-1]
|
||||
switch b {
|
||||
case "index":
|
||||
p.bundleType = PathTypeLeaf
|
||||
case "_index":
|
||||
p.bundleType = PathTypeBranch
|
||||
default:
|
||||
p.bundleType = PathTypeContentSingle
|
||||
}
|
||||
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func ModifyPathBundleTypeResource(p *Path) {
|
||||
if p.IsContent() {
|
||||
p.bundleType = PathTypeContentResource
|
||||
} else {
|
||||
p.bundleType = PathTypeFile
|
||||
}
|
||||
}
|
||||
|
||||
type PathType int
|
||||
|
||||
const (
|
||||
// A generic resource, e.g. a JSON file.
|
||||
PathTypeFile PathType = iota
|
||||
|
||||
// All below are content files.
|
||||
// A resource of a content type with front matter.
|
||||
PathTypeContentResource
|
||||
|
||||
// E.g. /blog/my-post.md
|
||||
PathTypeContentSingle
|
||||
|
||||
// All bewlow are bundled content files.
|
||||
|
||||
// Leaf bundles, e.g. /blog/my-post/index.md
|
||||
PathTypeLeaf
|
||||
|
||||
// Branch bundles, e.g. /blog/_index.md
|
||||
PathTypeBranch
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
s string
|
||||
|
||||
posContainerLow int
|
||||
posContainerHigh int
|
||||
posSectionHigh int
|
||||
|
||||
component string
|
||||
bundleType PathType
|
||||
|
||||
identifiers []types.LowHigh
|
||||
|
||||
posIdentifierLanguage int
|
||||
|
||||
trimLeadingSlash bool
|
||||
|
||||
unnormalized *Path
|
||||
}
|
||||
|
||||
// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
|
||||
func (p Path) TrimLeadingSlash() *Path {
|
||||
p.trimLeadingSlash = true
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *Path) norm(s string) string {
|
||||
if p.trimLeadingSlash {
|
||||
s = strings.TrimPrefix(s, "/")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// IdentifierBase satifies identity.Identity.
|
||||
func (p *Path) IdentifierBase() string {
|
||||
return p.Base()[1:]
|
||||
}
|
||||
|
||||
// Component returns the component for this path (e.g. "content").
|
||||
func (p *Path) Component() string {
|
||||
return p.component
|
||||
}
|
||||
|
||||
// Container returns the base name of the container directory for this path.
|
||||
func (p *Path) Container() string {
|
||||
if p.posContainerLow == -1 {
|
||||
return ""
|
||||
}
|
||||
return p.norm(p.s[p.posContainerLow : p.posContainerHigh-1])
|
||||
}
|
||||
|
||||
// ContainerDir returns the container directory for this path.
|
||||
// For content bundles this will be the parent directory.
|
||||
func (p *Path) ContainerDir() string {
|
||||
if p.posContainerLow == -1 || !p.IsBundle() {
|
||||
return p.Dir()
|
||||
}
|
||||
return p.norm(p.s[:p.posContainerLow-1])
|
||||
}
|
||||
|
||||
// Section returns the first path element (section).
|
||||
func (p *Path) Section() string {
|
||||
if p.posSectionHigh <= 0 {
|
||||
return ""
|
||||
}
|
||||
return p.norm(p.s[1:p.posSectionHigh])
|
||||
}
|
||||
|
||||
// IsContent returns true if the path is a content file (e.g. mypost.md).
|
||||
// Note that this will also return true for content files in a bundle.
|
||||
func (p *Path) IsContent() bool {
|
||||
return p.BundleType() >= PathTypeContentResource
|
||||
}
|
||||
|
||||
// isContentPage returns true if the path is a content file (e.g. mypost.md),
|
||||
// but nof if inside a leaf bundle.
|
||||
func (p *Path) isContentPage() bool {
|
||||
return p.BundleType() >= PathTypeContentSingle
|
||||
}
|
||||
|
||||
// Name returns the last element of path.
|
||||
func (p *Path) Name() string {
|
||||
if p.posContainerHigh > 0 {
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
return p.s
|
||||
}
|
||||
|
||||
// Name returns the last element of path withhout any extension.
|
||||
func (p *Path) NameNoExt() string {
|
||||
if i := p.identifierIndex(0); i != -1 {
|
||||
return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
|
||||
}
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
|
||||
// Name returns the last element of path withhout any language identifier.
|
||||
func (p *Path) NameNoLang() string {
|
||||
i := p.identifierIndex(p.posIdentifierLanguage)
|
||||
if i == -1 {
|
||||
return p.Name()
|
||||
}
|
||||
|
||||
return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
|
||||
}
|
||||
|
||||
// BaseNameNoIdentifier returns the logcical base name for a resource without any idenifier (e.g. no extension).
|
||||
// For bundles this will be the containing directory's name, e.g. "blog".
|
||||
func (p *Path) BaseNameNoIdentifier() string {
|
||||
if p.IsBundle() {
|
||||
return p.Container()
|
||||
}
|
||||
return p.NameNoIdentifier()
|
||||
}
|
||||
|
||||
// NameNoIdentifier returns the last element of path withhout any identifier (e.g. no extension).
|
||||
func (p *Path) NameNoIdentifier() string {
|
||||
if len(p.identifiers) > 0 {
|
||||
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
|
||||
}
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
|
||||
// Dir returns all but the last element of path, typically the path's directory.
|
||||
func (p *Path) Dir() (d string) {
|
||||
if p.posContainerHigh > 0 {
|
||||
d = p.s[:p.posContainerHigh-1]
|
||||
}
|
||||
if d == "" {
|
||||
d = "/"
|
||||
}
|
||||
d = p.norm(d)
|
||||
return
|
||||
}
|
||||
|
||||
// Path returns the full path.
|
||||
func (p *Path) Path() (d string) {
|
||||
return p.norm(p.s)
|
||||
}
|
||||
|
||||
// Unmormalized returns the Path with the original case preserved.
|
||||
func (p *Path) Unmormalized() *Path {
|
||||
return p.unnormalized
|
||||
}
|
||||
|
||||
// PathNoLang returns the Path but with any language identifier removed.
|
||||
func (p *Path) PathNoLang() string {
|
||||
return p.base(true, false)
|
||||
}
|
||||
|
||||
// PathNoIdentifier returns the Path but with any identifier (ext, lang) removed.
|
||||
func (p *Path) PathNoIdentifier() string {
|
||||
return p.base(false, false)
|
||||
}
|
||||
|
||||
// PathRel returns the path relativeto the given owner.
|
||||
func (p *Path) PathRel(owner *Path) string {
|
||||
ob := owner.Base()
|
||||
if !strings.HasSuffix(ob, "/") {
|
||||
ob += "/"
|
||||
}
|
||||
return strings.TrimPrefix(p.Path(), ob)
|
||||
}
|
||||
|
||||
// BaseRel returns the base path relative to the given owner.
|
||||
func (p *Path) BaseRel(owner *Path) string {
|
||||
ob := owner.Base()
|
||||
if ob == "/" {
|
||||
ob = ""
|
||||
}
|
||||
return p.Base()[len(ob)+1:]
|
||||
}
|
||||
|
||||
// For content files, Base returns the path without any identifiers (extension, language code etc.).
|
||||
// Any 'index' as the last path element is ignored.
|
||||
//
|
||||
// For other files (Resources), any extension is kept.
|
||||
func (p *Path) Base() string {
|
||||
return p.base(!p.isContentPage(), p.IsBundle())
|
||||
}
|
||||
|
||||
// BaseNoLeadingSlash returns the base path without the leading slash.
|
||||
func (p *Path) BaseNoLeadingSlash() string {
|
||||
return p.Base()[1:]
|
||||
}
|
||||
|
||||
func (p *Path) base(preserveExt, isBundle bool) string {
|
||||
if len(p.identifiers) == 0 {
|
||||
return p.norm(p.s)
|
||||
}
|
||||
|
||||
if preserveExt && len(p.identifiers) == 1 {
|
||||
// Preserve extension.
|
||||
return p.norm(p.s)
|
||||
}
|
||||
|
||||
id := p.identifiers[len(p.identifiers)-1]
|
||||
high := id.Low - 1
|
||||
|
||||
if isBundle {
|
||||
high = p.posContainerHigh - 1
|
||||
}
|
||||
|
||||
if high == 0 {
|
||||
high++
|
||||
}
|
||||
|
||||
if !preserveExt {
|
||||
return p.norm(p.s[:high])
|
||||
}
|
||||
|
||||
// For txt files etc. we want to preserve the extension.
|
||||
id = p.identifiers[0]
|
||||
|
||||
return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
|
||||
}
|
||||
|
||||
func (p *Path) Ext() string {
|
||||
return p.identifierAsString(0)
|
||||
}
|
||||
|
||||
func (p *Path) Lang() string {
|
||||
return p.identifierAsString(1)
|
||||
}
|
||||
|
||||
func (p *Path) Identifier(i int) string {
|
||||
return p.identifierAsString(i)
|
||||
}
|
||||
|
||||
func (p *Path) Identifiers() []string {
|
||||
ids := make([]string, len(p.identifiers))
|
||||
for i, id := range p.identifiers {
|
||||
ids[i] = p.s[id.Low:id.High]
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (p *Path) IsHTML() bool {
|
||||
return files.IsHTML(p.Ext())
|
||||
}
|
||||
|
||||
func (p *Path) BundleType() PathType {
|
||||
return p.bundleType
|
||||
}
|
||||
|
||||
func (p *Path) IsBundle() bool {
|
||||
return p.bundleType >= PathTypeLeaf
|
||||
}
|
||||
|
||||
func (p *Path) IsBranchBundle() bool {
|
||||
return p.bundleType == PathTypeBranch
|
||||
}
|
||||
|
||||
func (p *Path) IsLeafBundle() bool {
|
||||
return p.bundleType == PathTypeLeaf
|
||||
}
|
||||
|
||||
func (p *Path) identifierAsString(i int) string {
|
||||
i = p.identifierIndex(i)
|
||||
if i == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
id := p.identifiers[i]
|
||||
return p.s[id.Low:id.High]
|
||||
}
|
||||
|
||||
func (p *Path) identifierIndex(i int) int {
|
||||
if i < 0 || i >= len(p.identifiers) {
|
||||
return -1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// HasExt returns true if the Unix styled path has an extension.
|
||||
func HasExt(p string) bool {
|
||||
for i := len(p) - 1; i >= 0; i-- {
|
||||
if p[i] == '.' {
|
||||
return true
|
||||
}
|
||||
if p[i] == '/' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
351
common/paths/pathparser_test.go
Normal file
351
common/paths/pathparser_test.go
Normal file
|
@ -0,0 +1,351 @@
|
|||
// Copyright 2024 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 paths
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
var testParser = &PathParser{
|
||||
LanguageIndex: map[string]int{
|
||||
"no": 0,
|
||||
"en": 1,
|
||||
},
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
assert func(c *qt.C, p *Path)
|
||||
}{
|
||||
{
|
||||
"Basic text file",
|
||||
"/a/b.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Name(), qt.Equals, "b.txt")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b.txt")
|
||||
c.Assert(p.Container(), qt.Equals, "a")
|
||||
c.Assert(p.Dir(), qt.Equals, "/a")
|
||||
c.Assert(p.Ext(), qt.Equals, "txt")
|
||||
c.Assert(p.IsContent(), qt.IsFalse)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Basic text file, upper case",
|
||||
"/A/B.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Name(), qt.Equals, "b.txt")
|
||||
c.Assert(p.NameNoExt(), qt.Equals, "b")
|
||||
c.Assert(p.NameNoIdentifier(), qt.Equals, "b")
|
||||
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b.txt")
|
||||
c.Assert(p.Ext(), qt.Equals, "txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Basic text file, 1 space in dir",
|
||||
"/a b/c.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a-b/c.txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Basic text file, 2 spaces in dir",
|
||||
"/a b/c.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a--b/c.txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Basic text file, 1 space in filename",
|
||||
"/a/b c.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b-c.txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Basic text file, 2 spaces in filename",
|
||||
"/a/b c.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b--c.txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Basic text file, mixed case and spaces, unnormalized",
|
||||
"/a/Foo BAR.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
pp := p.Unmormalized()
|
||||
c.Assert(pp, qt.IsNotNil)
|
||||
c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "Foo BAR")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Basic Markdown file",
|
||||
"/a/b/c.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.IsContent(), qt.IsTrue)
|
||||
c.Assert(p.IsLeafBundle(), qt.IsFalse)
|
||||
c.Assert(p.Name(), qt.Equals, "c.md")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b/c")
|
||||
c.Assert(p.Section(), qt.Equals, "a")
|
||||
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "c")
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/c.md")
|
||||
c.Assert(p.Dir(), qt.Equals, "/a/b")
|
||||
c.Assert(p.Container(), qt.Equals, "b")
|
||||
c.Assert(p.ContainerDir(), qt.Equals, "/a/b")
|
||||
c.Assert(p.Ext(), qt.Equals, "md")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Content resource",
|
||||
"/a/b.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Name(), qt.Equals, "b.md")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b")
|
||||
c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b")
|
||||
c.Assert(p.Section(), qt.Equals, "a")
|
||||
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
|
||||
|
||||
// Reclassify it as a content resource.
|
||||
ModifyPathBundleTypeResource(p)
|
||||
c.Assert(p.BundleType(), qt.Equals, PathTypeContentResource)
|
||||
c.Assert(p.IsContent(), qt.IsTrue)
|
||||
c.Assert(p.Name(), qt.Equals, "b.md")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b.md")
|
||||
},
|
||||
},
|
||||
{
|
||||
"No ext",
|
||||
"/a/b",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Name(), qt.Equals, "b")
|
||||
c.Assert(p.NameNoExt(), qt.Equals, "b")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b")
|
||||
c.Assert(p.Ext(), qt.Equals, "")
|
||||
},
|
||||
},
|
||||
{
|
||||
"No ext, trailing slash",
|
||||
"/a/b/",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Name(), qt.Equals, "b")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b")
|
||||
c.Assert(p.Ext(), qt.Equals, "")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Identifiers",
|
||||
"/a/b.a.b.no.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Name(), qt.Equals, "b.a.b.no.txt")
|
||||
c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
|
||||
c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
|
||||
c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
|
||||
c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
|
||||
c.Assert(p.Ext(), qt.Equals, "txt")
|
||||
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Home branch cundle",
|
||||
"/_index.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/")
|
||||
c.Assert(p.Path(), qt.Equals, "/_index.md")
|
||||
c.Assert(p.Container(), qt.Equals, "")
|
||||
c.Assert(p.ContainerDir(), qt.Equals, "/")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Index content file in root",
|
||||
"/a/index.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a")
|
||||
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "a")
|
||||
c.Assert(p.Container(), qt.Equals, "a")
|
||||
c.Assert(p.Container(), qt.Equals, "a")
|
||||
c.Assert(p.ContainerDir(), qt.Equals, "")
|
||||
c.Assert(p.Dir(), qt.Equals, "/a")
|
||||
c.Assert(p.Ext(), qt.Equals, "md")
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
|
||||
c.Assert(p.IsBranchBundle(), qt.IsFalse)
|
||||
c.Assert(p.IsBundle(), qt.IsTrue)
|
||||
c.Assert(p.IsLeafBundle(), qt.IsTrue)
|
||||
c.Assert(p.Lang(), qt.Equals, "")
|
||||
c.Assert(p.NameNoExt(), qt.Equals, "index")
|
||||
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
|
||||
c.Assert(p.NameNoLang(), qt.Equals, "index.md")
|
||||
c.Assert(p.Section(), qt.Equals, "")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Index content file with lang",
|
||||
"/a/b/index.no.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b")
|
||||
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
|
||||
c.Assert(p.Container(), qt.Equals, "b")
|
||||
c.Assert(p.ContainerDir(), qt.Equals, "/a")
|
||||
c.Assert(p.Dir(), qt.Equals, "/a/b")
|
||||
c.Assert(p.Ext(), qt.Equals, "md")
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
|
||||
c.Assert(p.IsBranchBundle(), qt.IsFalse)
|
||||
c.Assert(p.IsBundle(), qt.IsTrue)
|
||||
c.Assert(p.IsLeafBundle(), qt.IsTrue)
|
||||
c.Assert(p.Lang(), qt.Equals, "no")
|
||||
c.Assert(p.NameNoExt(), qt.Equals, "index.no")
|
||||
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
|
||||
c.Assert(p.NameNoLang(), qt.Equals, "index.md")
|
||||
c.Assert(p.PathNoLang(), qt.Equals, "/a/b/index.md")
|
||||
c.Assert(p.Section(), qt.Equals, "a")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Index branch content file",
|
||||
"/a/b/_index.no.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b")
|
||||
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
|
||||
c.Assert(p.Container(), qt.Equals, "b")
|
||||
c.Assert(p.ContainerDir(), qt.Equals, "/a")
|
||||
c.Assert(p.Ext(), qt.Equals, "md")
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
|
||||
c.Assert(p.IsBranchBundle(), qt.IsTrue)
|
||||
c.Assert(p.IsBundle(), qt.IsTrue)
|
||||
c.Assert(p.IsLeafBundle(), qt.IsFalse)
|
||||
c.Assert(p.NameNoExt(), qt.Equals, "_index.no")
|
||||
c.Assert(p.NameNoLang(), qt.Equals, "_index.md")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Index root no slash",
|
||||
"_index.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/")
|
||||
c.Assert(p.Ext(), qt.Equals, "md")
|
||||
c.Assert(p.Name(), qt.Equals, "_index.md")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Index root",
|
||||
"/_index.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/")
|
||||
c.Assert(p.Ext(), qt.Equals, "md")
|
||||
c.Assert(p.Name(), qt.Equals, "_index.md")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Index first",
|
||||
"/a/_index.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Section(), qt.Equals, "a")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Index text file",
|
||||
"/a/b/index.no.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b/index.txt")
|
||||
c.Assert(p.Ext(), qt.Equals, "txt")
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
|
||||
c.Assert(p.IsLeafBundle(), qt.IsFalse)
|
||||
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b/index")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/")
|
||||
c.Assert(p.Ext(), qt.Equals, "")
|
||||
c.Assert(p.Name(), qt.Equals, "")
|
||||
c.Assert(p.Path(), qt.Equals, "/")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Slash",
|
||||
"/",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/")
|
||||
c.Assert(p.Ext(), qt.Equals, "")
|
||||
c.Assert(p.Name(), qt.Equals, "")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Trim Leading Slash bundle",
|
||||
"foo/bar/index.no.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Path(), qt.Equals, "/foo/bar/index.no.md")
|
||||
pp := p.TrimLeadingSlash()
|
||||
c.Assert(pp.Path(), qt.Equals, "foo/bar/index.no.md")
|
||||
c.Assert(pp.PathNoLang(), qt.Equals, "foo/bar/index.md")
|
||||
c.Assert(pp.Base(), qt.Equals, "foo/bar")
|
||||
c.Assert(pp.Dir(), qt.Equals, "foo/bar")
|
||||
c.Assert(pp.ContainerDir(), qt.Equals, "foo")
|
||||
c.Assert(pp.Container(), qt.Equals, "bar")
|
||||
c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "bar")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Trim Leading Slash file",
|
||||
"foo/bar.txt",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Path(), qt.Equals, "/foo/bar.txt")
|
||||
pp := p.TrimLeadingSlash()
|
||||
c.Assert(pp.Path(), qt.Equals, "foo/bar.txt")
|
||||
c.Assert(pp.PathNoLang(), qt.Equals, "foo/bar.txt")
|
||||
c.Assert(pp.Base(), qt.Equals, "foo/bar.txt")
|
||||
c.Assert(pp.Dir(), qt.Equals, "foo")
|
||||
c.Assert(pp.ContainerDir(), qt.Equals, "foo")
|
||||
c.Assert(pp.Container(), qt.Equals, "foo")
|
||||
c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "bar")
|
||||
},
|
||||
},
|
||||
{
|
||||
"File separator",
|
||||
filepath.FromSlash("/a/b/c.txt"),
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b/c.txt")
|
||||
c.Assert(p.Ext(), qt.Equals, "txt")
|
||||
c.Assert(p.Name(), qt.Equals, "c.txt")
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
test.assert(c, testParser.Parse(files.ComponentFolderContent, test.path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasExt(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(HasExt("/a/b/c.txt"), qt.IsTrue)
|
||||
c.Assert(HasExt("/a/b.c/d.txt"), qt.IsTrue)
|
||||
c.Assert(HasExt("/a/b/c"), qt.IsFalse)
|
||||
c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
|
||||
}
|
80
common/paths/paths_integration_test.go
Normal file
80
common/paths/paths_integration_test.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2024 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 paths_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestRemovePathAccents(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
removePathAccents = true
|
||||
-- content/διακριτικός.md --
|
||||
-- content/διακριτικός.fr.md --
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Language.Lang }}|Single.
|
||||
-- layouts/_default/list.html --
|
||||
List
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/en/διακριτικός/index.html", "en|Single")
|
||||
b.AssertFileContent("public/fr/διακριτικος/index.html", "fr|Single")
|
||||
}
|
||||
|
||||
func TestDisablePathToLower(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
disablePathToLower = true
|
||||
-- content/MySection/MyPage.md --
|
||||
-- content/MySection/MyPage.fr.md --
|
||||
-- content/MySection/MyBundle/index.md --
|
||||
-- content/MySection/MyBundle/index.fr.md --
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Language.Lang }}|Single.
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Language.Lang }}|List.
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/en/mysection/index.html", "en|List")
|
||||
b.AssertFileContent("public/en/mysection/mypage/index.html", "en|Single")
|
||||
b.AssertFileContent("public/fr/MySection/index.html", "fr|List")
|
||||
b.AssertFileContent("public/fr/MySection/MyPage/index.html", "fr|Single")
|
||||
b.AssertFileContent("public/en/mysection/mybundle/index.html", "en|Single")
|
||||
b.AssertFileContent("public/fr/MySection/MyBundle/index.html", "fr|Single")
|
||||
}
|
27
common/paths/pathtype_string.go
Normal file
27
common/paths/pathtype_string.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Code generated by "stringer -type=PathType"; DO NOT EDIT.
|
||||
|
||||
package paths
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[PathTypeFile-0]
|
||||
_ = x[PathTypeContentResource-1]
|
||||
_ = x[PathTypeContentSingle-2]
|
||||
_ = x[PathTypeLeaf-3]
|
||||
_ = x[PathTypeBranch-4]
|
||||
}
|
||||
|
||||
const _PathType_name = "PathTypeFilePathTypeContentResourcePathTypeContentSinglePathTypeLeafPathTypeBranch"
|
||||
|
||||
var _PathType_index = [...]uint8{0, 12, 35, 56, 68, 82}
|
||||
|
||||
func (i PathType) String() string {
|
||||
if i < 0 || i >= PathType(len(_PathType_index)-1) {
|
||||
return "PathType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _PathType_name[_PathType_index[i]:_PathType_index[i+1]]
|
||||
}
|
|
@ -184,3 +184,13 @@ func UrlToFilename(s string) (string, bool) {
|
|||
|
||||
return p, true
|
||||
}
|
||||
|
||||
// URLEscape escapes unicode letters.
|
||||
func URLEscape(uri string) string {
|
||||
// escape unicode letters
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
|
72
common/predicate/predicate.go
Normal file
72
common/predicate/predicate.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2024 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 predicate
|
||||
|
||||
// P is a predicate function that tests whether a value of type T satisfies some condition.
|
||||
type P[T any] func(T) bool
|
||||
|
||||
// And returns a predicate that is a short-circuiting logical AND of this and the given predicates.
|
||||
func (p P[T]) And(ps ...P[T]) P[T] {
|
||||
return func(v T) bool {
|
||||
for _, pp := range ps {
|
||||
if !pp(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return p(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Or returns a predicate that is a short-circuiting logical OR of this and the given predicates.
|
||||
func (p P[T]) Or(ps ...P[T]) P[T] {
|
||||
return func(v T) bool {
|
||||
for _, pp := range ps {
|
||||
if pp(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return p(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Negate returns a predicate that is a logical negation of this predicate.
|
||||
func (p P[T]) Negate() P[T] {
|
||||
return func(v T) bool {
|
||||
return !p(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter returns a new slice holding only the elements of s that satisfy p.
|
||||
// Filter modifies the contents of the slice s and returns the modified slice, which may have a smaller length.
|
||||
func (p P[T]) Filter(s []T) []T {
|
||||
var n int
|
||||
for _, v := range s {
|
||||
if p(v) {
|
||||
s[n] = v
|
||||
n++
|
||||
}
|
||||
}
|
||||
return s[:n]
|
||||
}
|
||||
|
||||
// FilterCopy returns a new slice holding only the elements of s that satisfy p.
|
||||
func (p P[T]) FilterCopy(s []T) []T {
|
||||
var result []T
|
||||
for _, v := range s {
|
||||
if p(v) {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
83
common/predicate/predicate_test.go
Normal file
83
common/predicate/predicate_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2024 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 predicate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/predicate"
|
||||
)
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var p predicate.P[int] = intP1
|
||||
|
||||
c.Assert(p(1), qt.IsTrue)
|
||||
c.Assert(p(2), qt.IsFalse)
|
||||
|
||||
neg := p.Negate()
|
||||
c.Assert(neg(1), qt.IsFalse)
|
||||
c.Assert(neg(2), qt.IsTrue)
|
||||
|
||||
and := p.And(intP2)
|
||||
c.Assert(and(1), qt.IsFalse)
|
||||
c.Assert(and(2), qt.IsFalse)
|
||||
c.Assert(and(10), qt.IsTrue)
|
||||
|
||||
or := p.Or(intP2)
|
||||
c.Assert(or(1), qt.IsTrue)
|
||||
c.Assert(or(2), qt.IsTrue)
|
||||
c.Assert(or(10), qt.IsTrue)
|
||||
c.Assert(or(11), qt.IsFalse)
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var p predicate.P[int] = intP1
|
||||
p = p.Or(intP2)
|
||||
|
||||
ints := []int{1, 2, 3, 4, 1, 6, 7, 8, 2}
|
||||
|
||||
c.Assert(p.Filter(ints), qt.DeepEquals, []int{1, 2, 1, 2})
|
||||
c.Assert(ints, qt.DeepEquals, []int{1, 2, 1, 2, 1, 6, 7, 8, 2})
|
||||
}
|
||||
|
||||
func TestFilterCopy(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var p predicate.P[int] = intP1
|
||||
p = p.Or(intP2)
|
||||
|
||||
ints := []int{1, 2, 3, 4, 1, 6, 7, 8, 2}
|
||||
|
||||
c.Assert(p.FilterCopy(ints), qt.DeepEquals, []int{1, 2, 1, 2})
|
||||
c.Assert(ints, qt.DeepEquals, []int{1, 2, 3, 4, 1, 6, 7, 8, 2})
|
||||
}
|
||||
|
||||
var intP1 = func(i int) bool {
|
||||
if i == 10 {
|
||||
return true
|
||||
}
|
||||
return i == 1
|
||||
}
|
||||
|
||||
var intP2 = func(i int) bool {
|
||||
if i == 10 {
|
||||
return true
|
||||
}
|
||||
return i == 2
|
||||
}
|
93
common/rungroup/rungroup.go
Normal file
93
common/rungroup/rungroup.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2024 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 rungroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// Group is a group of workers that can be used to enqueue work and wait for
|
||||
// them to finish.
|
||||
type Group[T any] interface {
|
||||
Enqueue(T) error
|
||||
Wait() error
|
||||
}
|
||||
|
||||
type runGroup[T any] struct {
|
||||
ctx context.Context
|
||||
g *errgroup.Group
|
||||
ch chan T
|
||||
}
|
||||
|
||||
// Config is the configuration for a new Group.
|
||||
type Config[T any] struct {
|
||||
NumWorkers int
|
||||
Handle func(context.Context, T) error
|
||||
}
|
||||
|
||||
// Run creates a new Group with the given configuration.
|
||||
func Run[T any](ctx context.Context, cfg Config[T]) Group[T] {
|
||||
if cfg.NumWorkers <= 0 {
|
||||
cfg.NumWorkers = 1
|
||||
}
|
||||
if cfg.Handle == nil {
|
||||
panic("Handle must be set")
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
// Buffered for performance.
|
||||
ch := make(chan T, cfg.NumWorkers)
|
||||
|
||||
for i := 0; i < cfg.NumWorkers; i++ {
|
||||
g.Go(func() error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case v, ok := <-ch:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if err := cfg.Handle(ctx, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return &runGroup[T]{
|
||||
ctx: ctx,
|
||||
g: g,
|
||||
ch: ch,
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue enqueues a new item to be handled by the workers.
|
||||
func (r *runGroup[T]) Enqueue(t T) error {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return nil
|
||||
case r.ch <- t:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for all workers to finish and returns the first error.
|
||||
func (r *runGroup[T]) Wait() error {
|
||||
close(r.ch)
|
||||
return r.g.Wait()
|
||||
}
|
44
common/rungroup/rungroup_test.go
Normal file
44
common/rungroup/rungroup_test.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2024 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 rungroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var result int
|
||||
adder := func(ctx context.Context, i int) error {
|
||||
result += i
|
||||
return nil
|
||||
}
|
||||
|
||||
g := Run[int](
|
||||
context.Background(),
|
||||
Config[int]{
|
||||
Handle: adder,
|
||||
},
|
||||
)
|
||||
|
||||
c.Assert(g, qt.IsNotNil)
|
||||
g.Enqueue(32)
|
||||
g.Enqueue(33)
|
||||
c.Assert(g.Wait(), qt.IsNil)
|
||||
c.Assert(result, qt.Equals, 65)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -35,11 +35,11 @@ func NewEvictingStringQueue(size int) *EvictingStringQueue {
|
|||
}
|
||||
|
||||
// Add adds a new string to the tail of the queue if it's not already there.
|
||||
func (q *EvictingStringQueue) Add(v string) {
|
||||
func (q *EvictingStringQueue) Add(v string) *EvictingStringQueue {
|
||||
q.mu.Lock()
|
||||
if q.set[v] {
|
||||
q.mu.Unlock()
|
||||
return
|
||||
return q
|
||||
}
|
||||
|
||||
if len(q.set) == q.size {
|
||||
|
@ -50,6 +50,17 @@ func (q *EvictingStringQueue) Add(v string) {
|
|||
q.set[v] = true
|
||||
q.vals = append(q.vals, v)
|
||||
q.mu.Unlock()
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *EvictingStringQueue) Len() int {
|
||||
if q == nil {
|
||||
return 0
|
||||
}
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
return len(q.vals)
|
||||
}
|
||||
|
||||
// Contains returns whether the queue contains v.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -92,5 +92,18 @@ type DevMarker interface {
|
|||
DevOnly()
|
||||
}
|
||||
|
||||
// Unwrapper is implemented by types that can unwrap themselves.
|
||||
type Unwrapper interface {
|
||||
// Unwrapv is for internal use only.
|
||||
// It got its slightly odd name to prevent collisions with user types.
|
||||
Unwrapv() any
|
||||
}
|
||||
|
||||
// LowHigh is typically used to represent a slice boundary.
|
||||
type LowHigh struct {
|
||||
Low int
|
||||
High int
|
||||
}
|
||||
|
||||
// This is only used for debugging purposes.
|
||||
var InvocationCounter atomic.Int64
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -23,10 +23,12 @@ import (
|
|||
// A BaseURL in Hugo is normally on the form scheme://path, but the
|
||||
// form scheme: is also valid (mailto:hugo@rules.com).
|
||||
type BaseURL struct {
|
||||
url *url.URL
|
||||
WithPath string
|
||||
WithoutPath string
|
||||
BasePath string
|
||||
url *url.URL
|
||||
WithPath string
|
||||
WithPathNoTrailingSlash string
|
||||
WithoutPath string
|
||||
BasePath string
|
||||
BasePathNoTrailingSlash string
|
||||
}
|
||||
|
||||
func (b BaseURL) String() string {
|
||||
|
@ -92,19 +94,19 @@ func NewBaseURLFromString(b string) (BaseURL, error) {
|
|||
return BaseURL{}, err
|
||||
}
|
||||
return newBaseURLFromURL(u)
|
||||
|
||||
}
|
||||
|
||||
func newBaseURLFromURL(u *url.URL) (BaseURL, error) {
|
||||
baseURL := BaseURL{url: u, WithPath: u.String()}
|
||||
var baseURLNoPath = baseURL.URL()
|
||||
// A baseURL should always have a trailing slash, see #11669.
|
||||
if !strings.HasSuffix(u.Path, "/") {
|
||||
u.Path += "/"
|
||||
}
|
||||
baseURL := BaseURL{url: u, WithPath: u.String(), WithPathNoTrailingSlash: strings.TrimSuffix(u.String(), "/")}
|
||||
baseURLNoPath := baseURL.URL()
|
||||
baseURLNoPath.Path = ""
|
||||
baseURL.WithoutPath = baseURLNoPath.String()
|
||||
|
||||
basePath := u.Path
|
||||
if basePath != "" && basePath != "/" {
|
||||
baseURL.BasePath = basePath
|
||||
}
|
||||
baseURL.BasePath = u.Path
|
||||
baseURL.BasePathNoTrailingSlash = strings.TrimSuffix(u.Path, "/")
|
||||
|
||||
return baseURL, nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -21,17 +21,24 @@ import (
|
|||
|
||||
func TestBaseURL(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
b, err := NewBaseURLFromString("http://example.com")
|
||||
|
||||
b, err := NewBaseURLFromString("http://example.com/")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(b.String(), qt.Equals, "http://example.com")
|
||||
c.Assert(b.String(), qt.Equals, "http://example.com/")
|
||||
|
||||
b, err = NewBaseURLFromString("http://example.com")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(b.String(), qt.Equals, "http://example.com/")
|
||||
c.Assert(b.WithPathNoTrailingSlash, qt.Equals, "http://example.com")
|
||||
c.Assert(b.BasePath, qt.Equals, "/")
|
||||
|
||||
p, err := b.WithProtocol("webcal://")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(p.String(), qt.Equals, "webcal://example.com")
|
||||
c.Assert(p.String(), qt.Equals, "webcal://example.com/")
|
||||
|
||||
p, err = b.WithProtocol("webcal")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(p.String(), qt.Equals, "webcal://example.com")
|
||||
c.Assert(p.String(), qt.Equals, "webcal://example.com/")
|
||||
|
||||
_, err = b.WithProtocol("mailto:")
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
|
@ -57,11 +64,18 @@ func TestBaseURL(t *testing.T) {
|
|||
|
||||
b, err = NewBaseURLFromString("")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(b.String(), qt.Equals, "")
|
||||
c.Assert(b.String(), qt.Equals, "/")
|
||||
|
||||
// BaseURL with sub path
|
||||
b, err = NewBaseURLFromString("http://example.com/sub")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(b.String(), qt.Equals, "http://example.com/sub")
|
||||
c.Assert(b.String(), qt.Equals, "http://example.com/sub/")
|
||||
c.Assert(b.WithPathNoTrailingSlash, qt.Equals, "http://example.com/sub")
|
||||
c.Assert(b.BasePath, qt.Equals, "/sub/")
|
||||
c.Assert(b.BasePathNoTrailingSlash, qt.Equals, "/sub")
|
||||
|
||||
b, err = NewBaseURLFromString("http://example.com/sub/")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(b.String(), qt.Equals, "http://example.com/sub/")
|
||||
c.Assert(b.HostURL(), qt.Equals, "http://example.com")
|
||||
}
|
||||
|
|
|
@ -52,3 +52,16 @@ func Eq(v1, v2 any) bool {
|
|||
|
||||
return v1 == v2
|
||||
}
|
||||
|
||||
// ProbablyEq returns whether v1 is probably equal to v2.
|
||||
func ProbablyEq(v1, v2 any) bool {
|
||||
if Eq(v1, v2) {
|
||||
return true
|
||||
}
|
||||
|
||||
if peqer, ok := v1.(ProbablyEqer); ok {
|
||||
return peqer.ProbablyEq(v2)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/privacy"
|
||||
|
@ -283,12 +284,13 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
|||
|
||||
disabledLangs := make(map[string]bool)
|
||||
for _, lang := range c.DisableLanguages {
|
||||
if lang == c.DefaultContentLanguage {
|
||||
return fmt.Errorf("cannot disable default content language %q", lang)
|
||||
}
|
||||
disabledLangs[lang] = true
|
||||
}
|
||||
for lang, language := range c.Languages {
|
||||
if !language.Disabled && disabledLangs[lang] {
|
||||
language.Disabled = true
|
||||
c.Languages[lang] = language
|
||||
}
|
||||
if language.Disabled {
|
||||
disabledLangs[lang] = true
|
||||
if lang == c.DefaultContentLanguage {
|
||||
|
@ -408,15 +410,19 @@ type ConfigCompiled struct {
|
|||
}
|
||||
|
||||
// This may be set after the config is compiled.
|
||||
func (c *ConfigCompiled) SetMainSectionsIfNotSet(sections []string) {
|
||||
func (c *ConfigCompiled) SetMainSections(sections []string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.MainSections != nil {
|
||||
return
|
||||
}
|
||||
c.MainSections = sections
|
||||
}
|
||||
|
||||
// IsMainSectionsSet returns whether the main sections have been set.
|
||||
func (c *ConfigCompiled) IsMainSectionsSet() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.MainSections != nil
|
||||
}
|
||||
|
||||
// This is set after the config is compiled by the server command.
|
||||
func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
|
||||
c.BaseURL = baseURL
|
||||
|
@ -425,7 +431,6 @@ func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
|
|||
|
||||
// RootConfig holds all the top-level configuration options in Hugo
|
||||
type RootConfig struct {
|
||||
|
||||
// The base URL of the site.
|
||||
// Note that the default value is empty, but Hugo requires a valid URL (e.g. "https://example.com/") to work properly.
|
||||
// <docsmeta>{"identifiers": ["URL"] }</docsmeta>
|
||||
|
@ -648,13 +653,16 @@ type Configs struct {
|
|||
LanguageConfigMap map[string]*Config
|
||||
LanguageConfigSlice []*Config
|
||||
|
||||
IsMultihost bool
|
||||
Languages langs.Languages
|
||||
LanguagesDefaultFirst langs.Languages
|
||||
IsMultihost bool
|
||||
|
||||
Modules modules.Modules
|
||||
ModulesClient *modules.Client
|
||||
|
||||
// All below is set in Init.
|
||||
Languages langs.Languages
|
||||
LanguagesDefaultFirst langs.Languages
|
||||
ContentPathParser paths.PathParser
|
||||
|
||||
configLangs []config.AllProvider
|
||||
}
|
||||
|
||||
|
@ -674,6 +682,58 @@ func (c *Configs) IsZero() bool {
|
|||
}
|
||||
|
||||
func (c *Configs) Init() error {
|
||||
var languages langs.Languages
|
||||
defaultContentLanguage := c.Base.DefaultContentLanguage
|
||||
for k, v := range c.LanguageConfigMap {
|
||||
c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
|
||||
languageConf := v.Languages[k]
|
||||
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
languages = append(languages, language)
|
||||
}
|
||||
|
||||
// Sort the sites by language weight (if set) or lang.
|
||||
sort.Slice(languages, func(i, j int) bool {
|
||||
li := languages[i]
|
||||
lj := languages[j]
|
||||
if li.Weight != lj.Weight {
|
||||
return li.Weight < lj.Weight
|
||||
}
|
||||
return li.Lang < lj.Lang
|
||||
})
|
||||
|
||||
for _, l := range languages {
|
||||
c.LanguageConfigSlice = append(c.LanguageConfigSlice, c.LanguageConfigMap[l.Lang])
|
||||
}
|
||||
|
||||
// Filter out disabled languages.
|
||||
var n int
|
||||
for _, l := range languages {
|
||||
if !l.Disabled {
|
||||
languages[n] = l
|
||||
n++
|
||||
}
|
||||
}
|
||||
languages = languages[:n]
|
||||
|
||||
var languagesDefaultFirst langs.Languages
|
||||
for _, l := range languages {
|
||||
if l.Lang == defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
for _, l := range languages {
|
||||
if l.Lang != defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
|
||||
c.Languages = languages
|
||||
c.LanguagesDefaultFirst = languagesDefaultFirst
|
||||
c.ContentPathParser = paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet()}
|
||||
|
||||
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||
for i, l := range c.LanguagesDefaultFirst {
|
||||
c.configLangs[i] = ConfigLanguage{
|
||||
|
@ -751,7 +811,6 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
|||
}
|
||||
|
||||
langConfigMap := make(map[string]*Config)
|
||||
var langConfigs []*Config
|
||||
|
||||
languagesConfig := cfg.GetStringMap("languages")
|
||||
var isMultiHost bool
|
||||
|
@ -848,65 +907,24 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
|||
}
|
||||
}
|
||||
|
||||
var languages langs.Languages
|
||||
defaultContentLanguage := all.DefaultContentLanguage
|
||||
for k, v := range langConfigMap {
|
||||
languageConf := v.Languages[k]
|
||||
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
languages = append(languages, language)
|
||||
}
|
||||
|
||||
// Sort the sites by language weight (if set) or lang.
|
||||
sort.Slice(languages, func(i, j int) bool {
|
||||
li := languages[i]
|
||||
lj := languages[j]
|
||||
if li.Weight != lj.Weight {
|
||||
return li.Weight < lj.Weight
|
||||
}
|
||||
return li.Lang < lj.Lang
|
||||
})
|
||||
|
||||
for _, l := range languages {
|
||||
langConfigs = append(langConfigs, langConfigMap[l.Lang])
|
||||
}
|
||||
|
||||
var languagesDefaultFirst langs.Languages
|
||||
for _, l := range languages {
|
||||
if l.Lang == defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
for _, l := range languages {
|
||||
if l.Lang != defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
|
||||
bcfg.PublishDir = all.PublishDir
|
||||
res.BaseConfig = bcfg
|
||||
all.CommonDirs.CacheDir = bcfg.CacheDir
|
||||
for _, l := range langConfigs {
|
||||
for _, l := range langConfigMap {
|
||||
l.CommonDirs.CacheDir = bcfg.CacheDir
|
||||
}
|
||||
|
||||
cm := &Configs{
|
||||
Base: all,
|
||||
LanguageConfigMap: langConfigMap,
|
||||
LanguageConfigSlice: langConfigs,
|
||||
LoadingInfo: res,
|
||||
IsMultihost: isMultiHost,
|
||||
Languages: languages,
|
||||
LanguagesDefaultFirst: languagesDefaultFirst,
|
||||
Base: all,
|
||||
LanguageConfigMap: langConfigMap,
|
||||
LoadingInfo: res,
|
||||
IsMultihost: isMultiHost,
|
||||
}
|
||||
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
|
||||
|
||||
var decoderSetups []decodeWeight
|
||||
|
||||
if len(keys) == 0 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -16,6 +16,7 @@ package allconfig
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
|
@ -41,10 +42,15 @@ func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
|
|||
return c.m.LanguagesDefaultFirst
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) PathParser() paths.PathParser {
|
||||
return c.m.ContentPathParser
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) LanguagePrefix() string {
|
||||
if c.DefaultContentLanguageInSubdir() && c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return c.Language().Lang
|
||||
}
|
||||
|
||||
if !c.IsMultiLingual() || c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return ""
|
||||
}
|
||||
|
@ -119,6 +125,10 @@ func (c ConfigLanguage) Quiet() bool {
|
|||
return c.m.Base.Internal.Quiet
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Watching() bool {
|
||||
return c.m.Base.Internal.Watch
|
||||
}
|
||||
|
||||
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
|
||||
func (c ConfigLanguage) GetConfigSection(s string) any {
|
||||
switch s {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -22,7 +22,6 @@ import (
|
|||
// This is is just some helpers used to create some JSON used in the Hugo docs.
|
||||
func init() {
|
||||
docsProvider := func() docshelper.DocProvider {
|
||||
|
||||
cfg := config.New()
|
||||
for configRoot, v := range allDecoderSetups {
|
||||
if v.internalOrDeprecated {
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
)
|
||||
|
||||
func TestDirsMount(t *testing.T) {
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
|
@ -44,7 +43,7 @@ Title: {{ .Title }}
|
|||
hugolib.IntegrationTestConfig{T: t, TxtarString: files},
|
||||
).Build()
|
||||
|
||||
//b.AssertFileContent("public/p1/index.html", "Title: p1")
|
||||
// b.AssertFileContent("public/p1/index.html", "Title: p1")
|
||||
|
||||
sites := b.H.Sites
|
||||
b.Assert(len(sites), qt.Equals, 2)
|
||||
|
@ -58,7 +57,7 @@ Title: {{ .Title }}
|
|||
enConcp := sites[0].Conf
|
||||
enConf := enConcp.GetConfig().(*allconfig.Config)
|
||||
|
||||
b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com")
|
||||
b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com/")
|
||||
modConf := enConf.Module
|
||||
b.Assert(modConf.Mounts, qt.HasLen, 8)
|
||||
b.Assert(modConf.Mounts[0].Source, qt.Equals, filepath.FromSlash("content/en"))
|
||||
|
@ -67,11 +66,9 @@ Title: {{ .Title }}
|
|||
b.Assert(modConf.Mounts[1].Source, qt.Equals, filepath.FromSlash("content/sv"))
|
||||
b.Assert(modConf.Mounts[1].Target, qt.Equals, "content")
|
||||
b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv")
|
||||
|
||||
}
|
||||
|
||||
func TestConfigAliases(t *testing.T) {
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
//lint:ignore ST1005 end user message.
|
||||
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
|
||||
|
||||
func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
|
||||
|
@ -566,15 +567,6 @@ func (l configLoader) deleteMergeStrategies() {
|
|||
})
|
||||
}
|
||||
|
||||
func (l configLoader) loadModulesConfig() (modules.Config, error) {
|
||||
modConfig, err := modules.DecodeConfig(l.cfg)
|
||||
if err != nil {
|
||||
return modules.Config{}, err
|
||||
}
|
||||
|
||||
return modConfig, nil
|
||||
}
|
||||
|
||||
func (l configLoader) wrapFileError(err error, filename string) error {
|
||||
fe := herrors.UnwrapFileError(err)
|
||||
if fe != nil {
|
||||
|
|
|
@ -85,29 +85,22 @@ var defaultBuild = BuildConfig{
|
|||
BuildStats: BuildStats{},
|
||||
|
||||
CacheBusters: []CacheBuster{
|
||||
{
|
||||
Source: `assets/.*\.(js|ts|jsx|tsx)`,
|
||||
Target: `(js|scripts|javascript)`,
|
||||
},
|
||||
{
|
||||
Source: `assets/.*\.(css|sass|scss)$`,
|
||||
Target: cssTargetCachebusterRe,
|
||||
},
|
||||
{
|
||||
Source: `(postcss|tailwind)\.config\.js`,
|
||||
Target: cssTargetCachebusterRe,
|
||||
},
|
||||
// This is deliberately coarse grained; it will cache bust resources with "json" in the cache key when js files changes, which is good.
|
||||
{
|
||||
Source: `assets/.*\.(.*)$`,
|
||||
Target: `$1`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// BuildConfig holds some build related configuration.
|
||||
type BuildConfig struct {
|
||||
UseResourceCacheWhen string // never, fallback, always. Default is fallback
|
||||
// When to use the resource file cache.
|
||||
// One of never, fallback, always. Default is fallback
|
||||
UseResourceCacheWhen string
|
||||
|
||||
// When enabled, will duplicate bundled resource files across languages that
|
||||
// doesn't have a translated version.
|
||||
DuplicateResourceFiles bool
|
||||
|
||||
// When enabled, will collect and write a hugo_stats.json with some build
|
||||
// related aggregated data (e.g. CSS class names).
|
||||
|
@ -373,7 +366,6 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
|
|||
|
||||
return match
|
||||
}
|
||||
|
||||
}
|
||||
return compileErr
|
||||
}
|
||||
|
@ -416,7 +408,6 @@ func DecodeServer(cfg Provider) (Server, error) {
|
|||
Status: 404,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return *s, nil
|
||||
|
|
|
@ -148,21 +148,13 @@ func TestBuildConfigCacheBusters(t *testing.T) {
|
|||
l := loggers.NewDefault()
|
||||
c.Assert(conf.CompileConfig(l), qt.IsNil)
|
||||
|
||||
m, err := conf.MatchCacheBuster(l, "assets/foo/main.js")
|
||||
c.Assert(err, qt.IsNil)
|
||||
m, _ := conf.MatchCacheBuster(l, "tailwind.config.js")
|
||||
c.Assert(m, qt.IsNotNil)
|
||||
c.Assert(m("scripts"), qt.IsTrue)
|
||||
c.Assert(m("asdf"), qt.IsFalse)
|
||||
|
||||
m, _ = conf.MatchCacheBuster(l, "tailwind.config.js")
|
||||
c.Assert(m("css"), qt.IsTrue)
|
||||
c.Assert(m("js"), qt.IsFalse)
|
||||
|
||||
m, err = conf.MatchCacheBuster(l, "assets/foo.json")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m, qt.IsNotNil)
|
||||
c.Assert(m("json"), qt.IsTrue)
|
||||
|
||||
m, _ = conf.MatchCacheBuster(l, "foo.bar")
|
||||
c.Assert(m, qt.IsNil)
|
||||
}
|
||||
|
||||
func TestBuildConfigCacheBusterstTailwindSetup(t *testing.T) {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
|
@ -30,6 +31,7 @@ type AllProvider interface {
|
|||
LanguagePrefix() string
|
||||
BaseURL() urls.BaseURL
|
||||
BaseURLLiveReload() urls.BaseURL
|
||||
PathParser() paths.PathParser
|
||||
Environment() string
|
||||
IsMultihost() bool
|
||||
IsMultiLingual() bool
|
||||
|
@ -54,6 +56,7 @@ type AllProvider interface {
|
|||
BuildFuture() bool
|
||||
BuildDrafts() bool
|
||||
Running() bool
|
||||
Watching() bool
|
||||
PrintUnusedTemplates() bool
|
||||
EnableMissingTranslationPlaceholders() bool
|
||||
TemplateMetrics() bool
|
||||
|
|
|
@ -18,6 +18,12 @@ import (
|
|||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pbnjay/memory"
|
||||
)
|
||||
|
||||
const (
|
||||
gigabyte = 1 << 30
|
||||
)
|
||||
|
||||
// GetNumWorkerMultiplier returns the base value used to calculate the number
|
||||
|
@ -33,6 +39,37 @@ func GetNumWorkerMultiplier() int {
|
|||
return runtime.NumCPU()
|
||||
}
|
||||
|
||||
// GetMemoryLimit returns the upper memory limit in bytes for Hugo's in-memory caches.
|
||||
// Note that this does not represent "all of the memory" that Hugo will use,
|
||||
// so it needs to be set to a lower number than the available system memory.
|
||||
// It will read from the HUGO_MEMORYLIMIT (in Gigabytes) environment variable.
|
||||
// If that is not set, it will set aside a quarter of the total system memory.
|
||||
func GetMemoryLimit() uint64 {
|
||||
if mem := os.Getenv("HUGO_MEMORYLIMIT"); mem != "" {
|
||||
if v := stringToGibabyte(mem); v > 0 {
|
||||
return v
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// There is a FreeMemory function, but as the kernel in most situations
|
||||
// will take whatever memory that is left and use for caching etc.,
|
||||
// that value is not something that we can use.
|
||||
m := memory.TotalMemory()
|
||||
if m != 0 {
|
||||
return uint64(m / 4)
|
||||
}
|
||||
|
||||
return 2 * gigabyte
|
||||
}
|
||||
|
||||
func stringToGibabyte(f string) uint64 {
|
||||
if v, err := strconv.ParseFloat(f, 32); err == nil && v > 0 {
|
||||
return uint64(v * gigabyte)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetEnvVars sets vars on the form key=value in the oldVars slice.
|
||||
func SetEnvVars(oldVars *[]string, keyValues ...string) {
|
||||
for i := 0; i < len(keyValues); i += 2 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -20,7 +20,6 @@ import (
|
|||
)
|
||||
|
||||
func DecodeNamespace[S, C any](configSource any, buildConfig func(any) (C, any, error)) (*ConfigNamespace[S, C], error) {
|
||||
|
||||
// Calculate the hash of the input (not including any defaults applied later).
|
||||
// This allows us to introduce new config options without breaking the hash.
|
||||
h := identity.HashString(configSource)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -26,7 +26,7 @@ func TestNamespace(t *testing.T) {
|
|||
c := qt.New(t)
|
||||
c.Assert(true, qt.Equals, true)
|
||||
|
||||
//ns, err := config.DecodeNamespace[map[string]DocsMediaTypeConfig](in, defaultMediaTypesConfig, buildConfig)
|
||||
// ns, err := config.DecodeNamespace[map[string]DocsMediaTypeConfig](in, defaultMediaTypesConfig, buildConfig)
|
||||
|
||||
ns, err := DecodeNamespace[[]*tstNsExt](
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
|
@ -46,23 +46,15 @@ func TestNamespace(t *testing.T) {
|
|||
c.Assert(ns.SourceHash, qt.Equals, "14368731254619220105")
|
||||
c.Assert(ns.Config, qt.DeepEquals, &tstNsExt{Foo: "bar"})
|
||||
c.Assert(ns.Signature(), qt.DeepEquals, []*tstNsExt(nil))
|
||||
|
||||
}
|
||||
|
||||
type (
|
||||
tstNsExt struct {
|
||||
Foo string
|
||||
}
|
||||
tstNsInt struct {
|
||||
Foo string
|
||||
}
|
||||
)
|
||||
|
||||
func (t *tstNsExt) Init() error {
|
||||
t.Foo = strings.ToUpper(t.Foo)
|
||||
return nil
|
||||
}
|
||||
func (t *tstNsInt) Compile(ext *tstNsExt) error {
|
||||
t.Foo = ext.Foo + " qux"
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -36,7 +36,7 @@ func GetTestConfigs(fs afero.Fs, cfg config.Provider) *allconfig.Configs {
|
|||
// Make sure that the workingDir exists.
|
||||
workingDir := cfg.GetString("workingDir")
|
||||
if workingDir != "" {
|
||||
if err := fs.MkdirAll(workingDir, 0777); err != nil {
|
||||
if err := fs.MkdirAll(workingDir, 0o777); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ func GetTestConfigs(fs afero.Fs, cfg config.Provider) *allconfig.Configs {
|
|||
panic(err)
|
||||
}
|
||||
return configs
|
||||
|
||||
}
|
||||
|
||||
func GetTestConfig(fs afero.Fs, cfg config.Provider) config.AllProvider {
|
||||
|
|
|
@ -16,6 +16,7 @@ package create
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -25,10 +26,9 @@ import (
|
|||
"github.com/gohugoio/hugo/hugofs/glob"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
@ -53,7 +53,7 @@ draft: true
|
|||
// NewContent creates a new content file in h (or a full bundle if the archetype is a directory)
|
||||
// in targetPath.
|
||||
func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error {
|
||||
if h.BaseFs.Content.Dirs == nil {
|
||||
if _, err := h.BaseFs.Content.Fs.Stat(""); err != nil {
|
||||
return errors.New("no existing content directory configured for this project")
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,6 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error
|
|||
}
|
||||
|
||||
return b.buildFile()
|
||||
|
||||
}
|
||||
|
||||
filename, err := withBuildLock()
|
||||
|
@ -116,7 +115,6 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error
|
|||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
type contentBuilder struct {
|
||||
|
@ -128,12 +126,12 @@ type contentBuilder struct {
|
|||
cf hugolib.ContentFactory
|
||||
|
||||
// Builder state
|
||||
archetypeFilename string
|
||||
targetPath string
|
||||
kind string
|
||||
isDir bool
|
||||
dirMap archetypeMap
|
||||
force bool
|
||||
archetypeFi hugofs.FileMetaInfo
|
||||
targetPath string
|
||||
kind string
|
||||
isDir bool
|
||||
dirMap archetypeMap
|
||||
force bool
|
||||
}
|
||||
|
||||
func (b *contentBuilder) buildDir() error {
|
||||
|
@ -146,7 +144,10 @@ func (b *contentBuilder) buildDir() error {
|
|||
var baseDir string
|
||||
|
||||
for _, fi := range b.dirMap.contentFiles {
|
||||
targetFilename := filepath.Join(b.targetPath, strings.TrimPrefix(fi.Meta().Path, b.archetypeFilename))
|
||||
|
||||
targetFilename := filepath.Join(b.targetPath, strings.TrimPrefix(fi.Meta().PathInfo.Path(), b.archetypeFi.Meta().PathInfo.Path()))
|
||||
|
||||
// ===> post/my-post/pages/bio.md
|
||||
abs, err := b.cf.CreateContentPlaceHolder(targetFilename, b.force)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -170,7 +171,6 @@ func (b *contentBuilder) buildDir() error {
|
|||
}
|
||||
return false
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if err := b.h.Build(hugolib.BuildCfg{NoBuildLock: true, SkipRender: true, ContentInclusionFilter: contentInclusionFilter}); err != nil {
|
||||
|
@ -178,22 +178,20 @@ func (b *contentBuilder) buildDir() error {
|
|||
}
|
||||
|
||||
for i, filename := range contentTargetFilenames {
|
||||
if err := b.applyArcheType(filename, b.dirMap.contentFiles[i].Meta().Path); err != nil {
|
||||
if err := b.applyArcheType(filename, b.dirMap.contentFiles[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the rest as is.
|
||||
for _, f := range b.dirMap.otherFiles {
|
||||
meta := f.Meta()
|
||||
filename := meta.Path
|
||||
for _, fi := range b.dirMap.otherFiles {
|
||||
meta := fi.Meta()
|
||||
|
||||
in, err := meta.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open non-content file: %w", err)
|
||||
}
|
||||
|
||||
targetFilename := filepath.Join(baseDir, b.targetPath, strings.TrimPrefix(filename, b.archetypeFilename))
|
||||
targetFilename := filepath.Join(baseDir, b.targetPath, strings.TrimPrefix(fi.Meta().Filename, b.archetypeFi.Meta().Filename))
|
||||
targetDir := filepath.Dir(targetFilename)
|
||||
|
||||
if err := b.sourceFs.MkdirAll(targetDir, 0o777); err != nil && !os.IsExist(err) {
|
||||
|
@ -225,7 +223,7 @@ func (b *contentBuilder) buildFile() (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
usesSite, err := b.usesSiteVar(b.archetypeFilename)
|
||||
usesSite, err := b.usesSiteVar(b.archetypeFi)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -243,7 +241,7 @@ func (b *contentBuilder) buildFile() (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if err := b.applyArcheType(contentPlaceholderAbsFilename, b.archetypeFilename); err != nil {
|
||||
if err := b.applyArcheType(contentPlaceholderAbsFilename, b.archetypeFi); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
@ -264,15 +262,14 @@ func (b *contentBuilder) setArcheTypeFilenameToUse(ext string) {
|
|||
for _, p := range pathsToCheck {
|
||||
fi, err := b.archeTypeFs.Stat(p)
|
||||
if err == nil {
|
||||
b.archetypeFilename = p
|
||||
b.archetypeFi = fi.(hugofs.FileMetaInfo)
|
||||
b.isDir = fi.IsDir()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (b *contentBuilder) applyArcheType(contentFilename, archetypeFilename string) error {
|
||||
func (b *contentBuilder) applyArcheType(contentFilename string, archetypeFi hugofs.FileMetaInfo) error {
|
||||
p := b.h.GetContentPage(contentFilename)
|
||||
if p == nil {
|
||||
panic(fmt.Sprintf("[BUG] no Page found for %q", contentFilename))
|
||||
|
@ -284,32 +281,39 @@ func (b *contentBuilder) applyArcheType(contentFilename, archetypeFilename strin
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
if archetypeFilename == "" {
|
||||
if archetypeFi == nil {
|
||||
return b.cf.ApplyArchetypeTemplate(f, p, b.kind, DefaultArchetypeTemplateTemplate)
|
||||
}
|
||||
|
||||
return b.cf.ApplyArchetypeFilename(f, p, b.kind, archetypeFilename)
|
||||
|
||||
return b.cf.ApplyArchetypeFi(f, p, b.kind, archetypeFi)
|
||||
}
|
||||
|
||||
func (b *contentBuilder) mapArcheTypeDir() error {
|
||||
var m archetypeMap
|
||||
|
||||
walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seen := map[hstrings.Tuple]bool{}
|
||||
|
||||
if fi.IsDir() {
|
||||
walkFn := func(path string, fim hugofs.FileMetaInfo) error {
|
||||
if fim.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
fil := fi.(hugofs.FileMetaInfo)
|
||||
pi := fim.Meta().PathInfo
|
||||
|
||||
if files.IsContentFile(path) {
|
||||
m.contentFiles = append(m.contentFiles, fil)
|
||||
if pi.IsContent() {
|
||||
pathLang := hstrings.Tuple{First: pi.PathNoIdentifier(), Second: fim.Meta().Lang}
|
||||
if seen[pathLang] {
|
||||
// Duplicate content file, e.g. page.md and page.html.
|
||||
// In the regular build, we will filter out the duplicates, but
|
||||
// for archetype folders these are ambiguous and we need to
|
||||
// fail.
|
||||
return fmt.Errorf("duplicate content file found in archetype folder: %q; having both e.g. %s.md and %s.html is ambigous", path, pi.BaseNameNoIdentifier(), pi.BaseNameNoIdentifier())
|
||||
}
|
||||
seen[pathLang] = true
|
||||
m.contentFiles = append(m.contentFiles, fim)
|
||||
if !m.siteUsed {
|
||||
m.siteUsed, err = b.usesSiteVar(path)
|
||||
var err error
|
||||
m.siteUsed, err = b.usesSiteVar(fim)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -317,7 +321,7 @@ func (b *contentBuilder) mapArcheTypeDir() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
m.otherFiles = append(m.otherFiles, fil)
|
||||
m.otherFiles = append(m.otherFiles, fim)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -325,13 +329,13 @@ func (b *contentBuilder) mapArcheTypeDir() error {
|
|||
walkCfg := hugofs.WalkwayConfig{
|
||||
WalkFn: walkFn,
|
||||
Fs: b.archeTypeFs,
|
||||
Root: b.archetypeFilename,
|
||||
Root: filepath.FromSlash(b.archetypeFi.Meta().PathInfo.Path()),
|
||||
}
|
||||
|
||||
w := hugofs.NewWalkway(walkCfg)
|
||||
|
||||
if err := w.Walk(); err != nil {
|
||||
return fmt.Errorf("failed to walk archetype dir %q: %w", b.archetypeFilename, err)
|
||||
return fmt.Errorf("failed to walk archetype dir %q: %w", b.archetypeFi.Meta().Filename, err)
|
||||
}
|
||||
|
||||
b.dirMap = m
|
||||
|
@ -370,17 +374,21 @@ func (b *contentBuilder) openInEditorIfConfigured(filename string) error {
|
|||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (b *contentBuilder) usesSiteVar(filename string) (bool, error) {
|
||||
if filename == "" {
|
||||
func (b *contentBuilder) usesSiteVar(fi hugofs.FileMetaInfo) (bool, error) {
|
||||
if fi == nil {
|
||||
return false, nil
|
||||
}
|
||||
bb, err := afero.ReadFile(b.archeTypeFs, filename)
|
||||
f, err := fi.Meta().Open()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to open archetype file: %w", err)
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
bb, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to read archetype file: %w", err)
|
||||
}
|
||||
|
||||
return bytes.Contains(bb, []byte(".Site")) || bytes.Contains(bb, []byte("site.")), nil
|
||||
|
||||
}
|
||||
|
||||
type archetypeMap struct {
|
||||
|
|
|
@ -114,58 +114,6 @@ func TestNewContentFromFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewContentFromDir(t *testing.T) {
|
||||
mm := afero.NewMemMapFs()
|
||||
c := qt.New(t)
|
||||
|
||||
archetypeDir := filepath.Join("archetypes", "my-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
|
||||
|
||||
archetypeThemeDir := filepath.Join("themes", "mytheme", "archetypes", "my-theme-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeThemeDir, 0o755), qt.IsNil)
|
||||
|
||||
contentFile := `
|
||||
File: %s
|
||||
Site Lang: {{ .Site.Language.Lang }}
|
||||
Name: {{ replace .Name "-" " " | title }}
|
||||
i18n: {{ T "hugo" }}
|
||||
`
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(fmt.Sprintf(contentFile, "index.nn.md")), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "pages", "bio.md"), []byte(fmt.Sprintf(contentFile, "bio.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
|
||||
conf := testconfig.GetTestConfigs(fs.Source, cfg)
|
||||
h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, Fs: fs})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(h.Sites), qt.Equals, 2)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/my-post", false), qt.IsNil)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo2.xml")), `hugo2: {{ printf "no template handling in here" }}`)
|
||||
|
||||
// Content files should get the correct site context.
|
||||
// TODO(bep) archetype check i18n
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `File: index.md`, `Site Lang: en`, `Name: My Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.nn.md")), `File: index.nn.md`, `Site Lang: nn`, `Name: My Post`, `i18n: Hugo Rokkar!`)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Site Lang: en`, `Name: Bio`)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-theme-bundle", "post/my-theme-post", false), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/index.md")), `File: index.md`, `Site Lang: en`, `Name: My Theme Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
|
||||
}
|
||||
|
||||
func TestNewContentFromDirSiteFunction(t *testing.T) {
|
||||
mm := afero.NewMemMapFs()
|
||||
c := qt.New(t)
|
||||
|
@ -206,83 +154,6 @@ site RegularPages: {{ len site.RegularPages }}
|
|||
// Regular files should fall back to the default archetype (we have no regular file archetype).
|
||||
c.Assert(create.NewContent(h, "my-bundle", "mypage.md", false), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "mypage.md")), `draft: true`)
|
||||
|
||||
}
|
||||
|
||||
func TestNewContentFromDirNoSite(t *testing.T) {
|
||||
mm := afero.NewMemMapFs()
|
||||
c := qt.New(t)
|
||||
|
||||
archetypeDir := filepath.Join("archetypes", "my-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
|
||||
|
||||
archetypeThemeDir := filepath.Join("themes", "mytheme", "archetypes", "my-theme-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeThemeDir, 0o755), qt.IsNil)
|
||||
|
||||
contentFile := `
|
||||
File: %s
|
||||
Name: {{ replace .Name "-" " " | title }}
|
||||
i18n: {{ T "hugo" }}
|
||||
`
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(fmt.Sprintf(contentFile, "index.nn.md")), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "pages", "bio.md"), []byte(fmt.Sprintf(contentFile, "bio.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
conf := testconfig.GetTestConfigs(fs.Source, cfg)
|
||||
h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, Fs: fs})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(h.Sites), qt.Equals, 2)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/my-post", false), qt.IsNil)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo2.xml")), `hugo2: {{ printf "no template handling in here" }}`)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `File: index.md`, `Name: My Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.nn.md")), `File: index.nn.md`, `Name: My Post`, `i18n: Hugo Rokkar!`)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Name: Bio`)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-theme-bundle", "post/my-theme-post", false), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/index.md")), `File: index.md`, `Name: My Theme Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
|
||||
}
|
||||
|
||||
func TestNewContentForce(t *testing.T) {
|
||||
mm := afero.NewMemMapFs()
|
||||
c := qt.New(t)
|
||||
|
||||
archetypeDir := filepath.Join("archetypes", "my-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(""), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(""), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
|
||||
conf := testconfig.GetTestConfigs(fs.Source, cfg)
|
||||
h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, Fs: fs})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(h.Sites), qt.Equals, 2)
|
||||
|
||||
// from file
|
||||
c.Assert(create.NewContent(h, "post", "post/my-post.md", false), qt.IsNil)
|
||||
c.Assert(create.NewContent(h, "post", "post/my-post.md", false), qt.IsNotNil)
|
||||
c.Assert(create.NewContent(h, "post", "post/my-post.md", true), qt.IsNil)
|
||||
|
||||
// from dir
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/my-post", false), qt.IsNil)
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/my-post", false), qt.IsNotNil)
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/my-post", true), qt.IsNil)
|
||||
}
|
||||
|
||||
func initFs(fs afero.Fs) error {
|
||||
|
@ -308,7 +179,7 @@ func initFs(fs afero.Fs) error {
|
|||
afero.WriteFile(fs, filename, []byte(`---
|
||||
title: Test
|
||||
---
|
||||
`), 0666)
|
||||
`), 0o666)
|
||||
}
|
||||
|
||||
// create archetype files
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -83,7 +83,7 @@ func copyFiles(createpath string, sourceFs afero.Fs, skeleton embed.FS) error {
|
|||
return fs.WalkDir(skeleton, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
_, slug, _ := strings.Cut(path, "/")
|
||||
if d.IsDir() {
|
||||
return sourceFs.MkdirAll(filepath.Join(createpath, slug), 0777)
|
||||
return sourceFs.MkdirAll(filepath.Join(createpath, slug), 0o777)
|
||||
} else {
|
||||
if filepath.Base(path) != ".gitkeep" {
|
||||
data, _ := fs.ReadFile(skeleton, path)
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
|
@ -33,8 +34,6 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
|
@ -75,7 +74,6 @@ const metaMD5Hash = "md5chksum" // the meta key to store md5hash in
|
|||
|
||||
// New constructs a new *Deployer.
|
||||
func New(cfg config.AllProvider, logger loggers.Logger, localFs afero.Fs) (*Deployer, error) {
|
||||
|
||||
dcfg := cfg.GetConfigSection(deploymentConfigKey).(DeployConfig)
|
||||
targetName := dcfg.Target
|
||||
|
||||
|
@ -675,8 +673,6 @@ func (d *Deployer) findDiffs(localFiles map[string]*localFile, remoteFiles map[s
|
|||
} else if !bytes.Equal(lf.MD5(), remoteFile.MD5) {
|
||||
upload = true
|
||||
reason = reasonMD5Differs
|
||||
} else {
|
||||
// Nope! Leave uploaded = false.
|
||||
}
|
||||
found[path] = true
|
||||
} else {
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
@ -241,7 +242,7 @@ func TestWalkLocal(t *testing.T) {
|
|||
for _, name := range tc.Given {
|
||||
dir, _ := path.Split(name)
|
||||
if dir != "" {
|
||||
if err := fs.MkdirAll(dir, 0755); err != nil {
|
||||
if err := fs.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -530,7 +531,7 @@ func initFsTests(t *testing.T) []*fsTest {
|
|||
membucket := memblob.OpenBucket(nil)
|
||||
t.Cleanup(func() { membucket.Close() })
|
||||
|
||||
filefs := afero.NewBasePathFs(afero.NewOsFs(), tmpfsdir)
|
||||
filefs := hugofs.NewBasePathFs(afero.NewOsFs(), tmpfsdir)
|
||||
filebucket, err := fileblob.OpenBucket(tmpbucketdir, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
21
deps/deps.go
vendored
21
deps/deps.go
vendored
|
@ -11,6 +11,8 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
@ -59,6 +61,9 @@ type Deps struct {
|
|||
// The configuration to use
|
||||
Conf config.AllProvider `json:"-"`
|
||||
|
||||
// The memory cache to use.
|
||||
MemCache *dynacache.Cache
|
||||
|
||||
// The translation func to use
|
||||
Translate func(ctx context.Context, translationID string, templateData any) string `json:"-"`
|
||||
|
||||
|
@ -149,6 +154,10 @@ func (d *Deps) Init() error {
|
|||
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config))
|
||||
}
|
||||
|
||||
if d.MemCache == nil {
|
||||
d.MemCache = dynacache.New(dynacache.Options{Running: d.Conf.Running(), Log: d.Log})
|
||||
}
|
||||
|
||||
if d.PathSpec == nil {
|
||||
hashBytesReceiverFunc := func(name string, match bool) {
|
||||
if !match {
|
||||
|
@ -190,13 +199,16 @@ func (d *Deps) Init() error {
|
|||
}
|
||||
|
||||
var common *resources.SpecCommon
|
||||
var imageCache *resources.ImageCache
|
||||
if d.ResourceSpec != nil {
|
||||
common = d.ResourceSpec.SpecCommon
|
||||
imageCache = d.ResourceSpec.ImageCache
|
||||
}
|
||||
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, imageCache, d.BuildState, d.Log, d, d.ExecHelper)
|
||||
fileCaches, err := filecache.NewCaches(d.PathSpec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file caches from configuration: %w", err)
|
||||
}
|
||||
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create resource spec: %w", err)
|
||||
}
|
||||
|
@ -307,6 +319,9 @@ func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
|
|||
}
|
||||
|
||||
func (d *Deps) Close() error {
|
||||
if d.MemCache != nil {
|
||||
d.MemCache.Stop()
|
||||
}
|
||||
return d.BuildClosers.Close()
|
||||
}
|
||||
|
||||
|
|
18
go.mod
18
go.mod
|
@ -2,9 +2,8 @@ module github.com/gohugoio/hugo
|
|||
|
||||
require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/PuerkitoBio/purell v1.1.1
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c
|
||||
github.com/aws/aws-sdk-go v1.48.6
|
||||
github.com/bep/clocks v0.5.0
|
||||
github.com/bep/debounce v1.2.0
|
||||
|
@ -15,10 +14,10 @@ require (
|
|||
github.com/bep/golibsass v1.1.1
|
||||
github.com/bep/gowebp v0.3.0
|
||||
github.com/bep/helpers v0.4.0
|
||||
github.com/bep/lazycache v0.2.0
|
||||
github.com/bep/lazycache v0.4.0
|
||||
github.com/bep/logg v0.4.0
|
||||
github.com/bep/mclib v1.20400.20402
|
||||
github.com/bep/overlayfs v0.6.0
|
||||
github.com/bep/overlayfs v0.9.1
|
||||
github.com/bep/simplecobra v0.4.0
|
||||
github.com/bep/tmc v0.5.1
|
||||
github.com/clbanning/mxj/v2 v2.7.0
|
||||
|
@ -53,6 +52,7 @@ require (
|
|||
github.com/muesli/smartcrop v0.3.0
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/pelletier/go-toml/v2 v2.1.1
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
|
@ -60,17 +60,17 @@ require (
|
|||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cast v1.6.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/fsync v0.9.0
|
||||
github.com/spf13/fsync v0.10.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/tdewolff/minify/v2 v2.20.13
|
||||
github.com/tdewolff/parse/v2 v2.7.8
|
||||
github.com/yuin/goldmark v1.6.0
|
||||
github.com/yuin/goldmark-emoji v1.0.2
|
||||
go.uber.org/atomic v1.11.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
gocloud.dev v0.34.0
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
|
||||
golang.org/x/image v0.14.0
|
||||
golang.org/x/mod v0.14.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/text v0.14.0
|
||||
|
@ -92,7 +92,6 @@ require (
|
|||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.32 // indirect
|
||||
|
@ -124,7 +123,7 @@ require (
|
|||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
|
@ -142,7 +141,6 @@ require (
|
|||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
|
@ -158,4 +156,4 @@ require (
|
|||
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
|
||||
)
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
|
359
go.sum
359
go.sum
|
@ -1,14 +1,51 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
|
||||
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
|
||||
cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
|
||||
|
@ -28,16 +65,13 @@ github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RX
|
|||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go v1.48.6 h1:hnL/TE3eRigirDLrdRE9AWE1ALZSVLAsC4wK8TGsMqk=
|
||||
github.com/aws/aws-sdk-go v1.48.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.0 h1:INUDpYLt4oiPOJl0XwZDK2OVAVf0Rzo+MGVTv9f+gy8=
|
||||
|
@ -96,14 +130,14 @@ github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY=
|
|||
github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||
github.com/bep/helpers v0.4.0 h1:ab9veaAiWY4ST48Oxp5usaqivDmYdB744fz+tcZ3Ifs=
|
||||
github.com/bep/helpers v0.4.0/go.mod h1:/QpHdmcPagDw7+RjkLFCvnlUc8lQ5kg4KDrEkb2Yyco=
|
||||
github.com/bep/lazycache v0.2.0 h1:HKrlZTrDxHIrNKqmnurH42ryxkngCMYLfBpyu40VcwY=
|
||||
github.com/bep/lazycache v0.2.0/go.mod h1:xUIsoRD824Vx0Q/n57+ZO7kmbEhMBOnTjM/iPixNGbg=
|
||||
github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI=
|
||||
github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
|
||||
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
||||
github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
|
||||
github.com/bep/mclib v1.20400.20402 h1:olpCE2WSPpOAbFE1R4hnftSEmQ34+xzy2HRzd0m69rA=
|
||||
github.com/bep/mclib v1.20400.20402/go.mod h1:pkrk9Kyfqg34Uj6XlDq9tdEFJBiL1FvCoCgVKRzw1EY=
|
||||
github.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo=
|
||||
github.com/bep/overlayfs v0.6.0/go.mod h1:NFjSmn3kCqG7KX2Lmz8qT8VhPPCwZap3UNogXawoQHM=
|
||||
github.com/bep/overlayfs v0.9.1 h1:SL54SV8A3zRkmQ+83Jj4TLE88jadHd5d1L4NpfmqJJs=
|
||||
github.com/bep/overlayfs v0.9.1/go.mod h1:aYY9W7aXQsGcA7V9x/pzeR8LjEgIxbtisZm8Q7zPz40=
|
||||
github.com/bep/simplecobra v0.4.0 h1:ufX/6WcOtEVJdCd7hsztTWURlZkOaWYOD+zCqrM8qUE=
|
||||
github.com/bep/simplecobra v0.4.0/go.mod h1:evSM6iQqRwqpV7W4H4DlYFfe9mZ0x6Hj5GEOnIV7dI4=
|
||||
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
|
||||
|
@ -111,6 +145,9 @@ github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
|
|||
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
|
||||
github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
|
||||
|
@ -118,6 +155,8 @@ github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00=
|
|||
github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -135,6 +174,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
|||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanw/esbuild v0.19.12 h1:p5WGo4o6TCN+kt+uZtYSGS3ZHPa+iIZ0SX+ys8UnP10=
|
||||
github.com/evanw/esbuild v0.19.12/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
|
@ -154,6 +195,9 @@ github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9
|
|||
github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
|
@ -177,29 +221,47 @@ github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95/go.mo
|
|||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
|
@ -208,7 +270,22 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE=
|
||||
github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
|
@ -219,15 +296,22 @@ github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
|||
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/hairyhenderson/go-codeowners v0.4.0 h1:Wx/tRXb07sCyHeC8mXfio710Iu35uAy5KYiBdLHdv4Q=
|
||||
github.com/hairyhenderson/go-codeowners v0.4.0/go.mod h1:iJgZeCt+W/GzXo5uchFCqvVHZY2T4TAIpvuVlKVkLxc=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
|
||||
|
@ -241,6 +325,10 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
|||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
|
@ -283,6 +371,8 @@ github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMim
|
|||
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
|
@ -292,11 +382,13 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzL
|
|||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
|
@ -308,21 +400,25 @@ github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0
|
|||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/fsync v0.9.0 h1:f9CEt3DOB2mnHxZaftmEOFWjABEvKM/xpf3cUwJrGOY=
|
||||
github.com/spf13/fsync v0.9.0/go.mod h1:fNtJEfG3HiltN3y4cPOz6MLjos9+2pIEqLIgszqhp/0=
|
||||
github.com/spf13/fsync v0.10.0 h1:j+zUMN41zWj3sEqueD4mAsPDQwyOvMeJCcrawdmbqXk=
|
||||
github.com/spf13/fsync v0.10.0/go.mod h1:y+B41vYq5i6Boa3Z+BVoPbDeOvxVkNU5OBXhoT8i4TQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
|
@ -335,66 +431,174 @@ github.com/tdewolff/parse/v2 v2.7.8/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1
|
|||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
||||
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
|
||||
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
gocloud.dev v0.34.0 h1:LzlQY+4l2cMtuNfwT2ht4+fiXwWf/NmPTnXUlLmGif4=
|
||||
gocloud.dev v0.34.0/go.mod h1:psKOachbnvY3DAOPbsFVmLIErwsbWPUG2H5i65D38vE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -404,21 +608,68 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
|
@ -428,15 +679,71 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY=
|
||||
google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
|
@ -444,10 +751,21 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.
|
|||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
|
@ -458,6 +776,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
|||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
|
@ -480,8 +799,16 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ=
|
||||
|
|
|
@ -30,7 +30,6 @@ import (
|
|||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||
|
||||
"github.com/gohugoio/hugo/markup"
|
||||
|
||||
|
@ -38,19 +37,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
openingPTag = []byte("<p>")
|
||||
closingPTag = []byte("</p>")
|
||||
paragraphIndicator = []byte("<p")
|
||||
closingIndicator = []byte("</")
|
||||
openingPTag = []byte("<p>")
|
||||
closingPTag = []byte("</p>")
|
||||
)
|
||||
|
||||
// ContentSpec provides functionality to render markdown content.
|
||||
type ContentSpec struct {
|
||||
Converters markup.ConverterProvider
|
||||
anchorNameSanitizer converter.AnchorNameSanitizer
|
||||
getRenderer func(t hooks.RendererType, id any) any
|
||||
|
||||
Cfg config.AllProvider
|
||||
Cfg config.AllProvider
|
||||
}
|
||||
|
||||
// NewContentSpec returns a ContentSpec initialized
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -24,8 +24,6 @@ import (
|
|||
"github.com/gohugoio/hugo/helpers"
|
||||
)
|
||||
|
||||
const tstHTMLContent = "<!DOCTYPE html><html><head><script src=\"http://two/foobar.js\"></script></head><body><nav><ul><li hugo-nav=\"section_0\"></li><li hugo-nav=\"section_1\"></li></ul></nav><article>content <a href=\"http://two/foobar\">foobar</a>. Follow up</article><p>This is some text.<br>And some more.</p></body></html>"
|
||||
|
||||
func TestTrimShortHTML(t *testing.T) {
|
||||
tests := []struct {
|
||||
input, output []byte
|
||||
|
@ -68,7 +66,6 @@ func BenchmarkTestTruncateWordsToWholeSentence(b *testing.B) {
|
|||
}
|
||||
|
||||
func TestTruncateWordsToWholeSentence(t *testing.T) {
|
||||
|
||||
type test struct {
|
||||
input, expected string
|
||||
max int
|
||||
|
@ -101,7 +98,6 @@ func TestTruncateWordsToWholeSentence(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTruncateWordsByRune(t *testing.T) {
|
||||
|
||||
type test struct {
|
||||
input, expected string
|
||||
max int
|
||||
|
|
|
@ -196,6 +196,7 @@ func ReaderContains(r io.Reader, subslice []byte) bool {
|
|||
func GetTitleFunc(style string) func(s string) string {
|
||||
switch strings.ToLower(style) {
|
||||
case "go":
|
||||
//lint:ignore SA1019 keep for now.
|
||||
return strings.Title
|
||||
case "chicago":
|
||||
tc := transform.NewTitleConverter(transform.ChicagoStyle)
|
||||
|
@ -263,10 +264,11 @@ func MD5String(f string) string {
|
|||
return hex.EncodeToString(h.Sum([]byte{}))
|
||||
}
|
||||
|
||||
// MD5FromFileFast creates a MD5 hash from the given file. It only reads parts of
|
||||
// MD5FromReaderFast creates a MD5 hash from the given file. It only reads parts of
|
||||
// the file for speed, so don't use it if the files are very subtly different.
|
||||
// It will not close the file.
|
||||
func MD5FromFileFast(r io.ReadSeeker) (string, error) {
|
||||
// It will return the MD5 hash and the size of r in bytes.
|
||||
func MD5FromReaderFast(r io.ReadSeeker) (string, int64, error) {
|
||||
const (
|
||||
// Do not change once set in stone!
|
||||
maxChunks = 8
|
||||
|
@ -284,7 +286,7 @@ func MD5FromFileFast(r io.ReadSeeker) (string, error) {
|
|||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
return "", 0, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,12 +296,14 @@ func MD5FromFileFast(r io.ReadSeeker) (string, error) {
|
|||
h.Write(buff)
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
return "", 0, err
|
||||
}
|
||||
h.Write(buff)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
size, _ := r.Seek(0, io.SeekEnd)
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), size, nil
|
||||
}
|
||||
|
||||
// MD5FromReader creates a MD5 hash from the given reader.
|
||||
|
@ -328,3 +332,21 @@ func PrintFs(fs afero.Fs, path string, w io.Writer) {
|
|||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// FormatByteCount pretty formats b.
|
||||
func FormatByteCount(bc uint64) string {
|
||||
const (
|
||||
Gigabyte = 1 << 30
|
||||
Megabyte = 1 << 20
|
||||
Kilobyte = 1 << 10
|
||||
)
|
||||
switch {
|
||||
case bc > Gigabyte || -bc > Gigabyte:
|
||||
return fmt.Sprintf("%.2f GB", float64(bc)/Gigabyte)
|
||||
case bc > Megabyte || -bc > Megabyte:
|
||||
return fmt.Sprintf("%.2f MB", float64(bc)/Megabyte)
|
||||
case bc > Kilobyte || -bc > Kilobyte:
|
||||
return fmt.Sprintf("%.2f KB", float64(bc)/Kilobyte)
|
||||
}
|
||||
return fmt.Sprintf("%d B", bc)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -259,19 +259,19 @@ func TestUniqueStringsSorted(t *testing.T) {
|
|||
func TestFastMD5FromFile(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
if err := afero.WriteFile(fs, "small.txt", []byte("abc"), 0777); err != nil {
|
||||
if err := afero.WriteFile(fs, "small.txt", []byte("abc"), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(fs, "small2.txt", []byte("abd"), 0777); err != nil {
|
||||
if err := afero.WriteFile(fs, "small2.txt", []byte("abd"), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(fs, "bigger.txt", []byte(strings.Repeat("a bc d e", 100)), 0777); err != nil {
|
||||
if err := afero.WriteFile(fs, "bigger.txt", []byte(strings.Repeat("a bc d e", 100)), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(fs, "bigger2.txt", []byte(strings.Repeat("c d e f g", 100)), 0777); err != nil {
|
||||
if err := afero.WriteFile(fs, "bigger2.txt", []byte(strings.Repeat("c d e f g", 100)), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -292,19 +292,19 @@ func TestFastMD5FromFile(t *testing.T) {
|
|||
defer bf1.Close()
|
||||
defer bf2.Close()
|
||||
|
||||
m1, err := helpers.MD5FromFileFast(sf1)
|
||||
m1, _, err := helpers.MD5FromReaderFast(sf1)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m1, qt.Equals, "e9c8989b64b71a88b4efb66ad05eea96")
|
||||
|
||||
m2, err := helpers.MD5FromFileFast(sf2)
|
||||
m2, _, err := helpers.MD5FromReaderFast(sf2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m2, qt.Not(qt.Equals), m1)
|
||||
|
||||
m3, err := helpers.MD5FromFileFast(bf1)
|
||||
m3, _, err := helpers.MD5FromReaderFast(bf1)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m3, qt.Not(qt.Equals), m2)
|
||||
|
||||
m4, err := helpers.MD5FromFileFast(bf2)
|
||||
m4, _, err := helpers.MD5FromReaderFast(bf2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m4, qt.Not(qt.Equals), m3)
|
||||
|
||||
|
@ -320,7 +320,7 @@ func BenchmarkMD5FromFileFast(b *testing.B) {
|
|||
b.Run(fmt.Sprintf("full=%t", full), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
if err := afero.WriteFile(fs, "file.txt", []byte(strings.Repeat("1234567890", 2000)), 0777); err != nil {
|
||||
if err := afero.WriteFile(fs, "file.txt", []byte(strings.Repeat("1234567890", 2000)), 0o777); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
f, err := fs.Open("file.txt")
|
||||
|
@ -333,7 +333,7 @@ func BenchmarkMD5FromFileFast(b *testing.B) {
|
|||
b.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if _, err := helpers.MD5FromFileFast(f); err != nil {
|
||||
if _, _, err := helpers.MD5FromReaderFast(f); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -350,7 +350,7 @@ func BenchmarkUniqueStrings(b *testing.B) {
|
|||
for i := 0; i < b.N; i++ {
|
||||
result := helpers.UniqueStrings(input)
|
||||
if len(result) != 6 {
|
||||
b.Fatal(fmt.Sprintf("invalid count: %d", len(result)))
|
||||
b.Fatalf("invalid count: %d", len(result))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -369,7 +369,7 @@ func BenchmarkUniqueStrings(b *testing.B) {
|
|||
|
||||
result := helpers.UniqueStringsReuse(inputc)
|
||||
if len(result) != 6 {
|
||||
b.Fatal(fmt.Sprintf("invalid count: %d", len(result)))
|
||||
b.Fatalf("invalid count: %d", len(result))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -388,7 +388,7 @@ func BenchmarkUniqueStrings(b *testing.B) {
|
|||
|
||||
result := helpers.UniqueStringsSorted(inputc)
|
||||
if len(result) != 6 {
|
||||
b.Fatal(fmt.Sprintf("invalid count: %d", len(result)))
|
||||
b.Fatalf("invalid count: %d", len(result))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
105
helpers/path.go
105
helpers/path.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -23,12 +23,12 @@ import (
|
|||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
|
@ -41,7 +41,11 @@ import (
|
|||
// whilst preserving the original casing of the string.
|
||||
// E.g. Social Media -> Social-Media
|
||||
func (p *PathSpec) MakePath(s string) string {
|
||||
return p.UnicodeSanitize(s)
|
||||
s = paths.Sanitize(s)
|
||||
if p.Cfg.RemovePathAccents() {
|
||||
s = text.RemoveAccentsString(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// MakePathsSanitized applies MakePathSanitized on every item in the slice
|
||||
|
@ -59,74 +63,13 @@ func (p *PathSpec) MakePathSanitized(s string) string {
|
|||
return strings.ToLower(p.MakePath(s))
|
||||
}
|
||||
|
||||
// ToSlashTrimLeading is just a filepath.ToSlaas with an added / prefix trimmer.
|
||||
func ToSlashTrimLeading(s string) string {
|
||||
return strings.TrimPrefix(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
||||
// MakeTitle converts the path given to a suitable title, trimming whitespace
|
||||
// and replacing hyphens with whitespace.
|
||||
func MakeTitle(inpath string) string {
|
||||
return strings.Replace(strings.TrimSpace(inpath), "-", " ", -1)
|
||||
}
|
||||
|
||||
// From https://golang.org/src/net/url/url.go
|
||||
func ishex(c rune) bool {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnicodeSanitize sanitizes string to be used in Hugo URL's, allowing only
|
||||
// a predefined set of special Unicode characters.
|
||||
// If RemovePathAccents configuration flag is enabled, Unicode accents
|
||||
// are also removed.
|
||||
// Hyphens in the original input are maintained.
|
||||
// Spaces will be replaced with a single hyphen, and sequential replacement hyphens will be reduced to one.
|
||||
func (p *PathSpec) UnicodeSanitize(s string) string {
|
||||
if p.Cfg.RemovePathAccents() {
|
||||
s = text.RemoveAccentsString(s)
|
||||
}
|
||||
|
||||
source := []rune(s)
|
||||
target := make([]rune, 0, len(source))
|
||||
var (
|
||||
prependHyphen bool
|
||||
wasHyphen bool
|
||||
)
|
||||
|
||||
for i, r := range source {
|
||||
isAllowed := r == '.' || r == '/' || r == '\\' || r == '_' || r == '#' || r == '+' || r == '~' || r == '-' || r == '@'
|
||||
isAllowed = isAllowed || unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsMark(r)
|
||||
isAllowed = isAllowed || (r == '%' && i+2 < len(source) && ishex(source[i+1]) && ishex(source[i+2]))
|
||||
|
||||
if isAllowed {
|
||||
// track explicit hyphen in input; no need to add a new hyphen if
|
||||
// we just saw one.
|
||||
wasHyphen = r == '-'
|
||||
|
||||
if prependHyphen {
|
||||
// if currently have a hyphen, don't prepend an extra one
|
||||
if !wasHyphen {
|
||||
target = append(target, '-')
|
||||
}
|
||||
prependHyphen = false
|
||||
}
|
||||
target = append(target, r)
|
||||
} else if len(target) > 0 && !wasHyphen && unicode.IsSpace(r) {
|
||||
prependHyphen = true
|
||||
}
|
||||
}
|
||||
|
||||
return string(target)
|
||||
}
|
||||
|
||||
// MakeTitleInPath converts the path given to a suitable title, trimming whitespace
|
||||
func MakePathRelative(inPath string, possibleDirectories ...string) (string, error) {
|
||||
for _, currentPath := range possibleDirectories {
|
||||
if strings.HasPrefix(inPath, currentPath) {
|
||||
|
@ -317,13 +260,12 @@ func FindCWD() (string, error) {
|
|||
return path, nil
|
||||
}
|
||||
|
||||
// SymbolicWalk is like filepath.Walk, but it follows symbolic links.
|
||||
func SymbolicWalk(fs afero.Fs, root string, walker hugofs.WalkFunc) error {
|
||||
// Walk walks the file tree rooted at root, calling walkFn for each file or
|
||||
// directory in the tree, including root.
|
||||
func Walk(fs afero.Fs, root string, walker hugofs.WalkFunc) error {
|
||||
if _, isOs := fs.(*afero.OsFs); isOs {
|
||||
// Mainly to track symlinks.
|
||||
fs = hugofs.NewBaseFileDecorator(fs)
|
||||
}
|
||||
|
||||
w := hugofs.NewWalkway(hugofs.WalkwayConfig{
|
||||
Fs: fs,
|
||||
Root: root,
|
||||
|
@ -333,16 +275,6 @@ func SymbolicWalk(fs afero.Fs, root string, walker hugofs.WalkFunc) error {
|
|||
return w.Walk()
|
||||
}
|
||||
|
||||
// LstatIfPossible can be used to call Lstat if possible, else Stat.
|
||||
func LstatIfPossible(fs afero.Fs, path string) (os.FileInfo, error) {
|
||||
if lstater, ok := fs.(afero.Lstater); ok {
|
||||
fi, _, err := lstater.LstatIfPossible(path)
|
||||
return fi, err
|
||||
}
|
||||
|
||||
return fs.Stat(path)
|
||||
}
|
||||
|
||||
// SafeWriteToDisk is the same as WriteToDisk
|
||||
// but it also checks to see if file/directory already exists.
|
||||
func SafeWriteToDisk(inpath string, r io.Reader, fs afero.Fs) (err error) {
|
||||
|
@ -382,7 +314,7 @@ func OpenFileForWriting(fs afero.Fs, filename string) (afero.File, error) {
|
|||
if !herrors.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if err = fs.MkdirAll(filepath.Dir(filename), 0777); err != nil { // before umask
|
||||
if err = fs.MkdirAll(filepath.Dir(filename), 0o777); err != nil { // before umask
|
||||
return nil, err
|
||||
}
|
||||
f, err = fs.Create(filename)
|
||||
|
@ -402,7 +334,7 @@ func GetCacheDir(fs afero.Fs, cacheDir string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
if !exists {
|
||||
err := fs.MkdirAll(cacheDir, 0777) // Before umask
|
||||
err := fs.MkdirAll(cacheDir, 0o777) // Before umask
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create cache dir: %w", err)
|
||||
}
|
||||
|
@ -417,7 +349,7 @@ func GetCacheDir(fs afero.Fs, cacheDir string) (string, error) {
|
|||
userCacheDir, err := os.UserCacheDir()
|
||||
if err == nil {
|
||||
cacheDir := filepath.Join(userCacheDir, hugoCacheBase)
|
||||
if err := fs.Mkdir(cacheDir, 0777); err == nil || os.IsExist(err) {
|
||||
if err := fs.Mkdir(cacheDir, 0o777); err == nil || os.IsExist(err) {
|
||||
return cacheDir, nil
|
||||
}
|
||||
}
|
||||
|
@ -494,12 +426,3 @@ func IsEmpty(path string, fs afero.Fs) (bool, error) {
|
|||
func Exists(path string, fs afero.Fs) (bool, error) {
|
||||
return afero.Exists(fs, path)
|
||||
}
|
||||
|
||||
// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
|
||||
// there.
|
||||
func AddTrailingSlash(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 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.
|
||||
|
@ -134,7 +134,6 @@ func TestMakePathRelative(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetDottedRelativePath(t *testing.T) {
|
||||
// on Windows this will receive both kinds, both country and western ...
|
||||
for _, f := range []func(string) string{filepath.FromSlash, func(s string) string { return s }} {
|
||||
doTestGetDottedRelativePath(f, t)
|
||||
}
|
||||
|
@ -258,7 +257,7 @@ func createNonZeroSizedFileInTempDir(t *testing.T) *os.File {
|
|||
|
||||
f := createZeroSizedFileInTempDir(t)
|
||||
byteString := []byte("byteString")
|
||||
err := os.WriteFile(f.Name(), byteString, 0644)
|
||||
err := os.WriteFile(f.Name(), byteString, 0o644)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
|
@ -74,9 +74,5 @@ func NewPathSpecWithBaseBaseFsProvided(fs *hugofs.Fs, cfg config.AllProvider, lo
|
|||
|
||||
// PermalinkForBaseURL creates a permalink from the given link and baseURL.
|
||||
func (p *PathSpec) PermalinkForBaseURL(link, baseURL string) string {
|
||||
link = strings.TrimPrefix(link, "/")
|
||||
if !strings.HasSuffix(baseURL, "/") {
|
||||
baseURL += "/"
|
||||
}
|
||||
return baseURL + link
|
||||
return baseURL + strings.TrimPrefix(link, "/")
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue